#! /usr/bin/env python

###############################################
# Symbolically link various TeX files locally #
# and pre-build .tfm files from .mf sources.  #
#                                             #
# In fact, this script creates only a Ninja   #
# build file to perform the operations.       #
#                                             #
# Author: Scott Pakin <scott-clsl@pakin.org>  #
###############################################

import glob
import os
import re
import subprocess
import sys
import textwrap
from pathlib import Path


def kpsewhich(fname):
    'Find a filename in the TeX tree.'
    proc = subprocess.run(['kpsewhich', fname], capture_output=True,
                          check=True, encoding='utf-8')
    return proc.stdout.strip()


def header_string():
    'Return a "generated file" header string.'
    full_name = os.path.abspath(sys.argv[0])
    gen_line = 'This is a generated file.  DO NOT EDIT.'
    edit_line = f'Edit {full_name} instead.'
    max_chars = max(len(gen_line), len(edit_line))
    hash_line = '#' * (max_chars + 4)
    return '\n'.join([
        hash_line,
        '# %-*.*s #' % (max_chars, max_chars, gen_line),
        '# %-*.*s #' % (max_chars, max_chars, edit_line),
        hash_line,
        '',
    ])


# Define some special-case PK sizes.  These were found empirically by
# examining the PK files left over after the first LaTeX run.
base_to_dpi = {
    'astrosym': 1054,
    'dancers': 93,
    'dice3d': 375,
    'fselch10': 1219,
    'hands': 250,
    'nkarta': 800,
}

# Generate a Ninja file that performs the desired operations.
with open('nonlatex.ninja', 'w') as w:
    # Write a "generated file" warning.
    w.write(header_string())

    # Construct a map from local filename (specified on the command line)
    # to absolute filename.  As a side effect, delete the local filename.
    local2abs = {}
    for fname in sorted(sys.argv[1:], key=str.lower):
        try:
            os.remove(fname)
        except FileNotFoundError:
            pass
        full_fname = kpsewhich(fname)
        if full_fname != '':
            local2abs[Path(fname)] = Path(full_fname)

    # Symbolically link all absolute filenames locally.
    w.write('''
rule symlink
  command = ln -s -f $in $out
  description = Symlinking $in locally

# Symbolically link various files into the current directory.
''')
    for dest, src in local2abs.items():
        w.write(f'build {dest} : symlink {src}\n')

    # Precompile TFM and PK fonts from Metafont sources.
    w.write('''
# The optional DPI variable additionally scales the font relative to
# the base DPI of 1200.
rule mf-to-tfm-gf
  command = $
    base="$$(basename $in .mf)" ; $
    mf "\\nonstopmode; \\mode:=ljfzzz; input $$base" ; $
    if [ "$DPI" ] ; then $
      mf "\\nonstopmode; \\mode:=ljfzzz; mag:=$DPI/1200; input $$base" ; $
    fi ; $
    rm "$${base}.log"
  description = Generating $out from $in

rule gf-to-pk
  command = $
    gftopk $in $out
  description = Generating $out from $in

# This is a special-case rule for avoiding "paths don't intersect" errors
# in bskms10.
rule mf-to-pk-1080
  command = $
    rm -f $out ; $
    base=$$(basename $in .mf) ; $
    mf-nowin --progname=mf --output-directory=$$(pwd) "\\mode:=lmaster; mag:=1.08; nonstopmode; input $$base" ; $
    gftopk $${base}.1080gf ; $
    rm $${base}.1080gf $${base}.log
  description = Generating $out from $in

# Precompile Metafont fonts.
''')
    outputs = [str(dest) for dest in local2abs]
    for fname in local2abs:
        if fname.suffix != '.mf':
            continue
        base = fname.stem
        if base == 'bskms10':
            # bskms10 issues fatal Metafont errors ("The paths don't
            # intersect") when building with "--bdpi=1200 --dpi=1080",
            # which is invoked implicitly when building the CLSL.  We
            # therefore manually generate bskms10.1080pk from a
            # "--bdpi=1000" version.
            w.write(f'build {base}.1080pk : mf-to-pk-1080 {fname}\n')
            outputs.append(f'{base}.1080pk')
        else:
            try:
                # Handle special cases in which a multiple DPIs are used.
                dpi = base_to_dpi[base]
                w.write(f'build {base}.tfm | {base}.1200gf {base}.{dpi}gf : mf-to-tfm-gf {fname}\n')
                w.write(f'  DPI = {dpi}\n')
                w.write(f'build {base}.1200pk : gf-to-pk {base}.1200gf\n')
                w.write(f'build {base}.{dpi}pk : gf-to-pk {base}.{dpi}gf\n')
                outputs.extend([
                    f'{base}.tfm',
                    f'{base}.1200gf',
                    f'{base}.1200pk',
                    f'{base}.{dpi}gf',
                    f'{base}.{dpi}pk',
                ])
            except KeyError:
                # Handle the common case of 1200 DPI only.
                w.write(f'build {base}.tfm | {base}.1200gf : mf-to-tfm-gf {fname}\n')
                w.write(f'build {base}.1200pk : gf-to-pk {base}.1200gf\n')
                outputs.extend([
                    f'{base}.tfm',
                    f'{base}.1200gf',
                    f'{base}.1200pk',
                ])
    w.write('\n')

    # Define a phony rule that depends on all outputs.
    w.write('# Define a phony rule that depends on all of the preceding outputs.\n')
    w.write('build NONLATEX : phony $\n')
    outputs.sort(key=str.casefold)
    lines = textwrap.wrap(' '.join(outputs),
                          break_long_words=False, break_on_hyphens=False,
                          initial_indent='  ', subsequent_indent='  ')
    w.write('%s\n' % ' $\n'.join(lines))
