#! /usr/bin/env python
# encoding: utf-8
# WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file

import os, re
from waflib import Utils, Task, Errors, Logs, Node
from waflib.TaskGen import feature, before_method

re_bibunit = re.compile(r'\\(?P<type>putbib)\[(?P<file>[^\[\]]*)\]', re.M)


def bibunitscan(self):
    node = self.inputs[0]
    nodes = []
    if not node:
        return nodes
    code = node.read()
    for match in re_bibunit.finditer(code):
        path = match.group('file')
        if path:
            found = None
            for k in ('', '.bib'):
                Logs.debug('tex: trying %s%s', path, k)
                fi = node.parent.find_resource(path + k)
                if fi:
                    found = True
                    nodes.append(fi)
            if not found:
                Logs.debug('tex: could not find %s', path)
    Logs.debug('tex: found the following bibunit files: %s', nodes)
    return nodes


exts_deps_tex = ['', '.ltx', '.tex', '.bib', '.pdf', '.png', '.eps', '.ps', '.sty']
exts_tex = ['.ltx', '.tex']
re_tex = re.compile(
    r'\\(?P<type>usepackage|RequirePackage|include|bibliography([^\[\]{}]*)|putbib|includegraphics|input|import|bringin|lstinputlisting)(\[[^\[\]]*\])?{(?P<file>[^{}]*)}',
    re.M
)
g_bibtex_re = re.compile('bibdata', re.M)
g_glossaries_re = re.compile('\\@newglossary', re.M)


class tex(Task.Task):
    bibtex_fun, _ = Task.compile_fun('${BIBTEX} ${BIBTEXFLAGS} ${SRCFILE}', shell=False)
    bibtex_fun.__doc__ = """
	Execute the program **bibtex**
	"""
    makeindex_fun, _ = Task.compile_fun('${MAKEINDEX} ${MAKEINDEXFLAGS} ${SRCFILE}', shell=False)
    makeindex_fun.__doc__ = """
	Execute the program **makeindex**
	"""
    makeglossaries_fun, _ = Task.compile_fun('${MAKEGLOSSARIES} ${SRCFILE}', shell=False)
    makeglossaries_fun.__doc__ = """
	Execute the program **makeglossaries**
	"""

    def exec_command(self, cmd, **kw):
        if self.env.PROMPT_LATEX:
            kw['stdout'] = kw['stderr'] = None
        return super(tex, self).exec_command(cmd, **kw)

    def scan_aux(self, node):
        nodes = [node]
        re_aux = re.compile(r'\\@input{(?P<file>[^{}]*)}', re.M)

        def parse_node(node):
            code = node.read()
            for match in re_aux.finditer(code):
                path = match.group('file')
                found = node.parent.find_or_declare(path)
                if found and found not in nodes:
                    Logs.debug('tex: found aux node %r', found)
                    nodes.append(found)
                    parse_node(found)

        parse_node(node)
        return nodes

    def scan(self):
        node = self.inputs[0]
        nodes = []
        names = []
        seen = []
        if not node:
            return (nodes, names)

        def parse_node(node):
            if node in seen:
                return
            seen.append(node)
            code = node.read()
            for match in re_tex.finditer(code):
                multibib = match.group('type')
                if multibib and multibib.startswith('bibliography'):
                    multibib = multibib[len('bibliography'):]
                    if multibib.startswith('style'):
                        continue
                else:
                    multibib = None
                for path in match.group('file').split(','):
                    if path:
                        add_name = True
                        found = None
                        for k in exts_deps_tex:
                            for up in self.texinputs_nodes:
                                Logs.debug('tex: trying %s%s', path, k)
                                found = up.find_resource(path + k)
                                if found:
                                    break
                            for tsk in self.generator.tasks:
                                if not found or found in tsk.outputs:
                                    break
                            else:
                                nodes.append(found)
                                add_name = False
                                for ext in exts_tex:
                                    if found.name.endswith(ext):
                                        parse_node(found)
                                        break
                            if found and multibib and found.name.endswith('.bib'):
                                try:
                                    self.multibibs.append(found)
                                except AttributeError:
                                    self.multibibs = [found]
                        if add_name:
                            names.append(path)

        parse_node(node)
        for x in nodes:
            x.parent.get_bld().mkdir()
        Logs.debug("tex: found the following : %s and names %s", nodes, names)
        return (nodes, names)

    def check_status(self, msg, retcode):
        if retcode != 0:
            raise Errors.WafError('%r command exit status %r' % (msg, retcode))

    def info(self, *k, **kw):
        try:
            info = self.generator.bld.conf.logger.info
        except AttributeError:
            info = Logs.info
        info(*k, **kw)

    def bibfile(self):
        for aux_node in self.aux_nodes:
            try:
                ct = aux_node.read()
            except EnvironmentError:
                Logs.error('Error reading %s: %r', aux_node.abspath())
                continue
            if g_bibtex_re.findall(ct):
                self.info('calling bibtex')
                self.env.env = {}
                self.env.env.update(os.environ)
                self.env.env.update({'BIBINPUTS': self.texinputs(), 'BSTINPUTS': self.texinputs()})
                self.env.SRCFILE = aux_node.name[:-4]
                self.check_status('error when calling bibtex', self.bibtex_fun())
        for node in getattr(self, 'multibibs', []):
            self.env.env = {}
            self.env.env.update(os.environ)
            self.env.env.update({'BIBINPUTS': self.texinputs(), 'BSTINPUTS': self.texinputs()})
            self.env.SRCFILE = node.name[:-4]
            self.check_status('error when calling bibtex', self.bibtex_fun())

    def bibunits(self):
        try:
            bibunits = bibunitscan(self)
        except OSError:
            Logs.error('error bibunitscan')
        else:
            if bibunits:
                fn = ['bu' + str(i) for i in range(1, len(bibunits) + 1)]
                if fn:
                    self.info('calling bibtex on bibunits')
                for f in fn:
                    self.env.env = {'BIBINPUTS': self.texinputs(), 'BSTINPUTS': self.texinputs()}
                    self.env.SRCFILE = f
                    self.check_status('error when calling bibtex', self.bibtex_fun())

    def makeindex(self):
        self.idx_node = self.inputs[0].change_ext('.idx')
        try:
            idx_path = self.idx_node.abspath()
            os.stat(idx_path)
        except OSError:
            self.info('index file %s absent, not calling makeindex', idx_path)
        else:
            self.info('calling makeindex')
            self.env.SRCFILE = self.idx_node.name
            self.env.env = {}
            self.check_status('error when calling makeindex %s' % idx_path, self.makeindex_fun())

    def bibtopic(self):
        p = self.inputs[0].parent.get_bld()
        if os.path.exists(os.path.join(p.abspath(), 'btaux.aux')):
            self.aux_nodes += p.ant_glob('*[0-9].aux')

    def makeglossaries(self):
        src_file = self.inputs[0].abspath()
        base_file = os.path.basename(src_file)
        base, _ = os.path.splitext(base_file)
        for aux_node in self.aux_nodes:
            try:
                ct = aux_node.read()
            except EnvironmentError:
                Logs.error('Error reading %s: %r', aux_node.abspath())
                continue
            if g_glossaries_re.findall(ct):
                if not self.env.MAKEGLOSSARIES:
                    raise Errors.WafError("The program 'makeglossaries' is missing!")
                Logs.warn('calling makeglossaries')
                self.env.SRCFILE = base
                self.check_status('error when calling makeglossaries %s' % base, self.makeglossaries_fun())
                return

    def texinputs(self):
        return os.pathsep.join([k.abspath() for k in self.texinputs_nodes]) + os.pathsep

    def run(self):
        env = self.env
        if not env.PROMPT_LATEX:
            env.append_value('LATEXFLAGS', '-interaction=batchmode')
            env.append_value('PDFLATEXFLAGS', '-interaction=batchmode')
            env.append_value('XELATEXFLAGS', '-interaction=batchmode')
        self.cwd = self.inputs[0].parent.get_bld()
        self.info('first pass on %s', self.__class__.__name__)
        cur_hash = self.hash_aux_nodes()
        self.call_latex()
        self.hash_aux_nodes()
        self.bibtopic()
        self.bibfile()
        self.bibunits()
        self.makeindex()
        self.makeglossaries()
        for i in range(10):
            prev_hash = cur_hash
            cur_hash = self.hash_aux_nodes()
            if not cur_hash:
                Logs.error('No aux.h to process')
            if cur_hash and cur_hash == prev_hash:
                break
            self.info('calling %s', self.__class__.__name__)
            self.call_latex()

    def hash_aux_nodes(self):
        try:
            self.aux_nodes
        except AttributeError:
            try:
                self.aux_nodes = self.scan_aux(self.inputs[0].change_ext('.aux'))
            except IOError:
                return None
        return Utils.h_list([Utils.h_file(x.abspath()) for x in self.aux_nodes])

    def call_latex(self):
        self.env.env = {}
        self.env.env.update(os.environ)
        self.env.env.update({'TEXINPUTS': self.texinputs()})
        self.env.SRCFILE = self.inputs[0].abspath()
        self.check_status('error when calling latex', self.texfun())


class latex(tex):
    texfun, vars = Task.compile_fun('${LATEX} ${LATEXFLAGS} ${SRCFILE}', shell=False)


class pdflatex(tex):
    texfun, vars = Task.compile_fun('${PDFLATEX} ${PDFLATEXFLAGS} ${SRCFILE}', shell=False)


class xelatex(tex):
    texfun, vars = Task.compile_fun('${XELATEX} ${XELATEXFLAGS} ${SRCFILE}', shell=False)


class dvips(Task.Task):
    run_str = '${DVIPS} ${DVIPSFLAGS} ${SRC} -o ${TGT}'
    color = 'BLUE'
    after = ['latex', 'pdflatex', 'xelatex']


class dvipdf(Task.Task):
    run_str = '${DVIPDF} ${DVIPDFFLAGS} ${SRC} ${TGT}'
    color = 'BLUE'
    after = ['latex', 'pdflatex', 'xelatex']


class pdf2ps(Task.Task):
    run_str = '${PDF2PS} ${PDF2PSFLAGS} ${SRC} ${TGT}'
    color = 'BLUE'
    after = ['latex', 'pdflatex', 'xelatex']


@feature('tex')
@before_method('process_source')
def apply_tex(self):
    if not getattr(self, 'type', None) in ('latex', 'pdflatex', 'xelatex'):
        self.type = 'pdflatex'
    outs = Utils.to_list(getattr(self, 'outs', []))
    try:
        self.generator.bld.conf
    except AttributeError:
        default_prompt = False
    else:
        default_prompt = True
    self.env.PROMPT_LATEX = getattr(self, 'prompt', default_prompt)
    deps_lst = []
    if getattr(self, 'deps', None):
        deps = self.to_list(self.deps)
        for dep in deps:
            if isinstance(dep, str):
                n = self.path.find_resource(dep)
                if not n:
                    self.bld.fatal('Could not find %r for %r' % (dep, self))
                if not n in deps_lst:
                    deps_lst.append(n)
            elif isinstance(dep, Node.Node):
                deps_lst.append(dep)
    for node in self.to_nodes(self.source):
        if self.type == 'latex':
            task = self.create_task('latex', node, node.change_ext('.dvi'))
        elif self.type == 'pdflatex':
            task = self.create_task('pdflatex', node, node.change_ext('.pdf'))
        elif self.type == 'xelatex':
            task = self.create_task('xelatex', node, node.change_ext('.pdf'))
        task.env = self.env
        if deps_lst:
            for n in deps_lst:
                if not n in task.dep_nodes:
                    task.dep_nodes.append(n)
        if hasattr(self, 'texinputs_nodes'):
            task.texinputs_nodes = self.texinputs_nodes
        else:
            task.texinputs_nodes = [node.parent, node.parent.get_bld(), self.path, self.path.get_bld()]
            lst = os.environ.get('TEXINPUTS', '')
            if self.env.TEXINPUTS:
                lst += os.pathsep + self.env.TEXINPUTS
            if lst:
                lst = lst.split(os.pathsep)
            for x in lst:
                if x:
                    if os.path.isabs(x):
                        p = self.bld.root.find_node(x)
                        if p:
                            task.texinputs_nodes.append(p)
                        else:
                            Logs.error('Invalid TEXINPUTS folder %s', x)
                    else:
                        Logs.error('Cannot resolve relative paths in TEXINPUTS %s', x)
        if self.type == 'latex':
            if 'ps' in outs:
                tsk = self.create_task('dvips', task.outputs, node.change_ext('.ps'))
                tsk.env.env = dict(os.environ)
            if 'pdf' in outs:
                tsk = self.create_task('dvipdf', task.outputs, node.change_ext('.pdf'))
                tsk.env.env = dict(os.environ)
        elif self.type == 'pdflatex':
            if 'ps' in outs:
                self.create_task('pdf2ps', task.outputs, node.change_ext('.ps'))
    self.source = []


def configure(self):
    v = self.env
    for p in 'tex latex pdflatex xelatex bibtex dvips dvipdf ps2pdf makeindex pdf2ps makeglossaries'.split():
        try:
            self.find_program(p, var=p.upper())
        except self.errors.ConfigurationError:
            pass
    v.DVIPSFLAGS = '-Ppdf'
