#!/usr/bin/env python3
"""Test the candidate in isolated project-local and personal TEXMF layouts."""
from pathlib import Path
import argparse
import json
import os
import re
import shutil
import subprocess
import tempfile
import time

PKG = Path(__file__).resolve().parents[2]
WORK = PKG.parent
REPORTS = WORK / 'reports'


def copy_runtime(target):
    for p in (PKG/'tex').iterdir():
        shutil.copyfile(p, target/p.name)
    shutil.copyfile(PKG/'fonts/OpenGlyphEbag.otf', target/'OpenGlyphEbag.otf')


def run(engine, args, cwd, env, limit=120):
    started = time.monotonic()
    p = subprocess.run([engine, *args], cwd=cwd, env=env, text=True,
                       capture_output=True, timeout=limit)
    return p, time.monotonic()-started


def main():
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument('--engine', choices=['tectonic','xelatex','lualatex'], required=True)
    ap.add_argument('--manual', action='store_true')
    ap.add_argument('--publish-pdf', action='store_true')
    args = ap.parse_args()
    if not shutil.which(args.engine):
        raise SystemExit('Missing engine: '+args.engine)
    REPORTS.mkdir(exist_ok=True)
    (WORK/'build').mkdir(exist_ok=True)
    results = []
    fixtures = {
        'mixed': (True, None, r'\OpenGlyphLoad{custom}\begin{document}Text \gwglyph{my:n1}, \gwglyph{my:n2}.\footnote{\gwglyph{my:n3}}\section{Heading \gwglyph{my:n4}}\end{document}'),
        'repeat-load': (True, None, r'\OpenGlyphLoad{custom}\OpenGlyphLoad{custom}\OpenGlyphAlias{short}{my:n1}\begin{document}\gwglyph{short}\OpenGlyphSequence{my:n2,my:n3}\end{document}'),
        'unknown': (False, 'Unknown glyph', r'\OpenGlyphLoad{custom}\begin{document}\gwglyph{missing}\end{document}'),
        'duplicate': (False, 'already defined', r'\OpenGlyphLoad{custom}\OpenGlyphAlias{my:n1}{my:n2}\begin{document}x\end{document}'),
        'missing-font': (False, 'Cannot find', r'\OpenGlyphDeclareFont{bad}{no-such-font.otf}\begin{document}x\end{document}'),
        'missing-cmap': (False, 'does not contain glyph', r'\OpenGlyphLoad{custom}\OpenGlyphDeclare{bad}{my}{0041}\begin{document}\gwglyph{bad}\end{document}'),
        'bad-id': (False, 'Invalid ASCII identifier', r'\OpenGlyphLoad{custom}\OpenGlyphAlias{bad/id}{my:n1}\begin{document}x\end{document}'),
        'existence': (True, None, r'\OpenGlyphLoad{custom}\begin{document}\OpenGlyphIfExistsTF{my:n1}{YES}{\errmessage{miss}}\OpenGlyphIfExistsTF{missing}{\errmessage{unexpected}}{NO}\end{document}'),
        'namespaces': (True, None, r'\OpenGlyphLoad{custom}\OpenGlyphLoad{second}\begin{document}\gwglyph{my:n1}\gwglyph{second:n1}\end{document}'),
        'builtin': (True, None, r'\OpenGlyphLoad{ebag}\OpenGlyphLoad{ebag}\begin{document}\ebag{s030-067}\ebag{s131-009}\ebag{s162-428}\end{document}'),
        'builtin-and-custom': (True, None, r'\OpenGlyphLoad{ebag}\OpenGlyphLoad{custom}\begin{document}\ebag{s030-067}\gwglyph{my:n2}\end{document}'),
        'builtin-sizes': (True, None, r'\OpenGlyphLoad{ebag}\begin{document}\ebag{s030-067} {\Huge\ebag{s030-067}}\footnote{\ebag{s030-067}}\end{document}'),
        'missing-local-font-no-fallback': (False, 'Cannot find', r'\OpenGlyphLoad{broken}\begin{document}x\end{document}'),
        'path-with-spaces': (True, None, r'\OpenGlyphLoad{custom bundle}\begin{document}\gwglyph{spaces:n1}\end{document}'),
    }
    with tempfile.TemporaryDirectory(prefix=args.engine+'-', dir=WORK/'build') as tmp:
        base=Path(tmp);paper=base/'unrelated paper';paper.mkdir()
        env=os.environ.copy()
        for key in ('TEXINPUTS','OPENTYPEFONTS','TEXMFHOME','TEXMFVAR','TEXMFCONFIG','TEXMFOUTPUT'):
            env.pop(key, None)
        env['PYTHONDONTWRITEBYTECODE']='1'
        layout='project-local'
        if args.engine=='tectonic':
            copy_runtime(paper)
        else:
            layout='personal-TEXMF'
            texmf=base/'texmf'
            texdir=texmf/'tex/latex/openglyph';texdir.mkdir(parents=True)
            fontdir=texmf/'fonts/opentype/public/openglyph';fontdir.mkdir(parents=True)
            for p in (PKG/'tex').iterdir():shutil.copyfile(p,texdir/p.name)
            shutil.copyfile(PKG/'fonts/OpenGlyphEbag.otf',fontdir/'OpenGlyphEbag.otf')
            env.update(TEXMFHOME=str(texmf),TEXMFVAR=str(base/'texmf-var'),TEXMFCONFIG=str(base/'texmf-config'))
            for filename,extra in [('openglyph.sty',[]),('openglyph-ebag.def',[]),('OpenGlyphEbag.otf',['--format=opentype fonts'])]:
                p,_=run('kpsewhich',[*extra,filename],paper,env)
                got=p.stdout.strip()
                results.append({'case':'lookup-'+filename,'layout':layout,'passed':p.returncode==0 and got.startswith(str(texmf)+'/'),'resolved':got})
        for name,namespace in [('custom','my'),('second','second'),('custom bundle','spaces')]:
            shutil.copytree(PKG/'doc/examples/myglyphs',paper/name)
            catalog=paper/name/'catalog.tex'
            if namespace!='my':catalog.write_text(catalog.read_text().replace('{my}','{'+namespace+'}').replace('{my:','{'+namespace+':'))
        # This missing local font must not silently pick the installed built-in.
        (paper/'broken').mkdir()
        (paper/'broken/catalog.tex').write_text(r'\OpenGlyphDeclareFont{broken}{OpenGlyphEbag.otf}')
        # Original local ebag/catalog.tex layout still has priority over TEXMF.
        shutil.copytree(PKG/'doc/examples/myglyphs',paper/'legacy')
        legacy=(paper/'legacy/catalog.tex').read_text().replace('{my}','{ebag}').replace('{my:','{ebag:')
        (paper/'legacy/catalog.tex').write_text(legacy)

        def compile_one(case,source,success=True,error=None,cwd=paper):
            tex=cwd/(case+'.tex');tex.write_text(source,encoding='utf-8')
            flags=['--keep-logs',tex.name] if args.engine=='tectonic' else ['-no-shell-escape','-interaction=nonstopmode','-halt-on-error',tex.name]
            p,seconds=run(args.engine,flags,cwd,env)
            output=p.stdout+p.stderr
            log=cwd/(case+'.log')
            if log.exists():output+='\n'+log.read_text(errors='replace')
            diagnostic=re.sub(r'\s+',' ',output)
            passed=(p.returncode==0)==success and (not error or error in diagnostic)
            if success and 'Missing character:' in output:passed=False
            if case in ('openglyph-doc','openglyph-example') and 'Overfull' in output:passed=False
            (REPORTS/(args.engine+'-'+case+'.log')).write_text(output,encoding='utf-8')
            results.append(dict(case=case,layout=layout,expected_success=success,exit_code=p.returncode,passed=passed,seconds=round(seconds,3),expected_error=error))
            print(json.dumps(results[-1]),flush=True)
            return passed

        for case,(success,error,body) in fixtures.items():
            compile_one(case,r'\documentclass{article}\usepackage{openglyph}\usepackage[unicode]{hyperref}'+body,success,error)
        (paper/'legacy').rename(paper/'ebag')
        compile_one('legacy-local-priority',r'\documentclass{article}\usepackage{openglyph}\OpenGlyphLoad{ebag}\begin{document}\ebag{n1}\OpenGlyphIfExistsTF{ebag:s030-067}{\errmessage{wrong catalogue}}{}\end{document}')

        if args.engine!='tectonic':
            # Explicit absolute custom bundle paths also work, including spaces.
            moved=base/'lookup area';moved.mkdir()
            shutil.copytree(PKG/'doc/examples/myglyphs',moved/'bysearch')
            compile_one('absolute-catalogue-font-relative',r'\documentclass{article}\usepackage{openglyph}\OpenGlyphLoad{'+(moved/'bysearch').as_posix()+r'}\begin{document}\gwglyph{my:n1}\end{document}')

        if args.manual:
            doc=base/'doc';shutil.copytree(PKG/'doc',doc)
            shutil.copytree(PKG/'source/examples',base/'source/examples')
            if args.engine=='tectonic':copy_runtime(doc)
            for stem in ('openglyph-doc','openglyph-example'):
                source=(doc/(stem+'.tex')).read_text()
                good=True
                for attempt in (1,2):
                    good=compile_one(stem,source,cwd=doc) and good
                if good and args.publish_pdf:
                    shutil.copyfile(doc/(stem+'.pdf'),PKG/'doc'/(stem+'.pdf'))
        version=subprocess.check_output([args.engine,'--version'],text=True,timeout=15).splitlines()[0]
        result={'engine':args.engine,'engine_version':version,'layout':layout,'shell_escape':False,'tests':results,
                'all_passed':all(x['passed'] for x in results)}
        (REPORTS/('tex-'+args.engine+'.json')).write_text(json.dumps(result,indent=2)+'\n')
    if not result['all_passed']:raise SystemExit(1)


if __name__=='__main__':main()
