# This file is part of Indico.
# Copyright (C) 2002 - 2021 CERN
#
# Indico is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see the
# LICENSE file for more details.

import codecs
import os
import subprocess
import tempfile
from io import BytesIO
from operator import attrgetter
from zipfile import ZipFile

import markdown
import pkg_resources
from flask import session
from flask.helpers import get_root_path
from jinja2 import Environment, FileSystemLoader, StrictUndefined
from jinja2.ext import Extension
from jinja2.lexer import Token
from pytz import timezone

from indico.core.config import config
from indico.core.logger import Logger
from indico.legacy.pdfinterface.base import escape
from indico.modules.events.abstracts.models.abstracts import AbstractReviewingState, AbstractState
from indico.modules.events.abstracts.models.reviews import AbstractAction
from indico.modules.events.abstracts.settings import BOACorrespondingAuthorType, boa_settings
from indico.modules.events.contributions.util import sort_contribs
from indico.modules.events.util import create_event_logo_tmp_file
from indico.util import mdx_latex
from indico.util.date_time import format_date, format_human_timedelta, format_time
from indico.util.fs import chmod_umask
from indico.util.i18n import _, ngettext
from indico.util.string import render_markdown


class PDFLaTeXBase:
    _table_of_contents = False
    LATEX_TEMPLATE = None

    def __init__(self):
        # Markdown -> LaTeX renderer
        # safe_mode - strip out all HTML
        md = markdown.Markdown(safe_mode='remove')
        self.source_dir = tempfile.mkdtemp(prefix='indico-texgen-', dir=config.TEMP_DIR)
        latex_mdx = mdx_latex.LaTeXExtension(configs={'apply_br': True, 'tmpdir': self.source_dir})
        latex_mdx.extendMarkdown(md, markdown.__dict__)

        def _escape_latex_math(string):
            return mdx_latex.latex_escape(string, ignore_math=True)

        def _convert_markdown(text):
            return RawLatex(render_markdown(text, md=md.convert, escape_latex_math=_escape_latex_math))

        self._args = {'markdown': _convert_markdown}

    def generate(self):
        latex = LatexRunner(self.source_dir, has_toc=self._table_of_contents)
        return latex.run(self.LATEX_TEMPLATE, **self._args)

    def generate_source_archive(self):
        latex = LatexRunner(self.source_dir, has_toc=self._table_of_contents)
        latex.prepare(self.LATEX_TEMPLATE, **self._args)

        buf = BytesIO()
        with ZipFile(buf, 'w', allowZip64=True) as zip_handler:
            for dirpath, dirnames, files in os.walk(self.source_dir, followlinks=True):
                for f in files:
                    if f.startswith('.') or f.endswith(('.py', '.pyc', '.pyo')):
                        continue
                    file_path = os.path.join(dirpath, f)
                    archive_name = os.path.relpath(file_path, self.source_dir)
                    zip_handler.write(os.path.abspath(file_path), archive_name)
        buf.seek(0)
        return buf


class LaTeXRuntimeException(Exception):
    def __init__(self, source_file, log_file):
        super().__init__(f'LaTeX compilation of {source_file} failed')
        self.source_file = source_file
        self.log_file = log_file

    @property
    def message(self):
        return f"Could not compile '{self.source_file}'. Read '{self.log_file}' for details"


class LatexEscapeExtension(Extension):
    """Ensure all strings in Jinja are latex-escaped."""

    def filter_stream(self, stream):
        in_trans = False
        in_variable = False
        for token in stream:
            # Check if we are inside a trans block - we cannot use filters there!
            if token.type == 'block_begin':
                block_name = stream.current.value
                if block_name == 'trans':
                    in_trans = True
                elif block_name == 'endtrans':
                    in_trans = False
            elif token.type == 'variable_begin':
                in_variable = True

            if in_variable and not in_trans:
                if token.type == 'variable_end' or (token.type == 'name' and token.value == 'if'):
                    yield Token(token.lineno, 'pipe', '|')
                    yield Token(token.lineno, 'name', 'latex')

            if token.type == 'variable_end':
                in_variable = False

            # Original token
            yield token


class RawLatex(str):
    pass


def _latex_escape(s, ignore_braces=False):
    if not isinstance(s, str) or isinstance(s, RawLatex):
        return s
    return RawLatex(mdx_latex.latex_escape(s, ignore_braces=ignore_braces))


class LatexRunner:
    """Handle the PDF generation from a chosen LaTeX template."""

    def __init__(self, source_dir, has_toc=False):
        self.source_dir = source_dir
        self.has_toc = has_toc

    def run_latex(self, source_file, log_file=None):
        pdflatex_cmd = [config.XELATEX_PATH,
                        '-no-shell-escape',
                        '-interaction', 'nonstopmode',
                        '-output-directory', self.source_dir,
                        os.path.relpath(source_file, self.source_dir)]

        try:
            # set config which limits location of input/output files
            subprocess.check_call(
                pdflatex_cmd,
                stdout=log_file,
                stderr=subprocess.STDOUT,
                cwd=self.source_dir,
                env=dict(os.environ, TEXMFCNF=f'{os.path.dirname(__file__)}:')
            )
            Logger.get('pdflatex').debug('PDF created successfully!')

        except subprocess.CalledProcessError:
            Logger.get('pdflatex').debug('PDF creation possibly failed (non-zero exit code)!')
            # Only fail if we are in strict mode
            if config.STRICT_LATEX:
                # flush log, go to beginning and read it
                if log_file:
                    log_file.flush()
                raise

    def _render_template(self, template_name, kwargs):
        template_dir = os.path.join(get_root_path('indico'), 'legacy/pdfinterface/latex_templates')
        env = Environment(loader=FileSystemLoader(template_dir),
                          autoescape=False,
                          trim_blocks=True,
                          keep_trailing_newline=True,
                          auto_reload=config.DEBUG,
   
                       extensions=[LatexEscapeExtension],
                          undefined=StrictUndefined,
                          block_start_string=r'\JINJA{', block_end_string='}',
                          variable_start_string=r'\VAR{', variable_end_string='}',
                          comment_start_string=r'\#{', comment_end_string='}')
        env.filters['format_date'] = format_date
        env
.filters['format_time'] = format_time
        env.filters['format_duration'] = lambda delta: format_human_timedelta(delta, 'minutes')
        env.filters['latex'] = _latex_escape
        env.filters['rawlatex'] = RawLatex
        env.filters['markdown'] = kwargs.pop('markdown')
        env.globals['_'] = _
        env.globals['ngettext'] = ngettext
        env.globals['session'] = session
        template = env.get_or_select_template(template_name)
        return template.render(font_dir='fonts/', **kwargs)

    def prepare(self, template_name, **kwargs):
        chmod_umask(self.source_dir, execute=True)
        source_filename = os.path.join(self.source_dir, template_name + '.tex')
        target_filename = os.path.join(self.source_dir, template_name + '.pdf')

        source = self._render_template(template_name + '.tex', kwargs)
        with codecs.open(source_filename, 'wb', encoding='utf-8') as f:
            f.write(source)

        distribution = pkg_resources.get_distribution('indico-fonts')
        font_dir = os.path.join(distribution.location, 'indico_fonts')
        os.symlink(font_dir, os.path.join(self.source_dir, 'fonts'))
        return source_filename, target_filename

    def run(self, template_name, **kwargs):
        if not config.LATEX_ENABLED:
           