#! /usr/bin/env python
# -*- coding: utf-8 -*-

import os
import re
import subprocess
import sys

try:
    import regex as re
    has_regex = True
    param1 = r'''
(?<rec1>    # capturing group rec1
 (?:        # non-capturing group
  [^<>]++   # anything but angle brackets one or more times without backtracking
  |         # or
  <(?&rec1)>  # recursive substitute of group rec1
 )*
)'''
    param2 = r'''
(?<rec2>    # capturing group rec2
 (?:        # non-capturing group
  [^<>]++   # anything but angle brackets one or more times without backtracking
  |         # or
  <(?&rec2)>  # recursive substitute of group rec2
 )*
)'''
except ImportError:
    has_regex = False

from vcsn_config import config
from vcsn.demangle import demangle

me = sys.argv[0]

import argparse

class AppendStringAction(argparse.Action):
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        if nargs is not None:
            raise ValueError("nargs not allowed")
        super(AppendStringAction, self).__init__(option_strings, dest, **kwargs)
    def __call__(self, parser, namespace, values, option_string=None):
        value = getattr(namespace, self.dest) + " " + values
        setattr(namespace, self.dest, value)

parser = argparse.ArgumentParser(description = 'Compile.')
parser.add_argument('input', help='source file to compile')
parser.add_argument('-shared', help='create a shared lib instead of an executable',
                    action='store_true')
parser.add_argument('-v', '--verbose', help='Be verbose',
                    action='count', default=config['verbose'])
parser.add_argument('--ccache', help='Compiler prefix',
                    default=config['ccache'])
parser.add_argument('--cppflags', help='Preprocessor flags',
                    default=config['cppflags'])
parser.add_argument('--cxx', help='Compiler',
                    default=config['cxx'])
parser.add_argument('--cxxflags', help='Compiler flags',
                    default=config['cxxflags'])
parser.add_argument('--ldflags', help='Linker flags',
                    default=config['ldflags'])
parser.add_argument('--extra-ldflags', help='Additional linker flags',
                    action=AppendStringAction, dest='ldflags')
parser.add_argument('-c', '--color', dest='color', action='store',
                    default="auto",
                    choices=['auto', 'always', 'never'],
                    help='Whether to use colors in the output')

# Parse the arguments, and complete the 'args' object with the value
# from the configuration (cxx, cxxflags, etc.).
args = parser.parse_args()
for k in config:
    if not k in args:
        setattr(args, k, config[k])
if 2 <= args.verbose:
    print("args:", file=sys.stderr)
    for k in sorted(args.__dict__):
        print("  {}: {}".format(k, getattr(args, k)), file=sys.stderr)

# Strip extension.
args.base = args.input[:-3]
# Beware of concurrency issues: insert pid in the name to avoid problem.
args.pid = str(os.getpid())
args.tmp = args.base + '.' + args.pid

def fmt(s, **kwargs):
    '''Substitute the value in args.'''
    # Can be nicer once we require Python 3.5.
    d = args.__dict__.copy()
    d.update(kwargs)
    return s.format(**d)

def sugar(s):
    '''Perform some transformations that aim at displaying a cuter version
    of a Vcsn type string.'''
    # Long specs are split into subdirs.  Remove the subdirs.
    s = s.replace('/', '')
    s = re.sub(r'(char|string)_letter', r'\1', s)
    s = re.sub(r'(\w+)_automaton', r'\1', s)
    if has_regex:
        s = re.sub(r'context<{param1},\ +{param2}>'.format(param1=param1,
                                                           param2=param2),
                   r'\1 → \2',
                   s,
                   flags=re.VERBOSE)
    else:
        s = re.sub(r'^context<(.*)>$', r'\1', s)
    return s

def notify():
    '''Notify the user that a compilation was finished.'''
    sound = 'Glass' # or 'Basso'
    # what = algos|contexts, specs = argument specifications.
    what, specs = re.match(r'.*/plugins/([^/]+)/(.*)', args.base).group(1, 2)
    if what == 'algos':
        title, message = specs.split('/', 1)
        message = sugar(message)
    else:
        title = 'context'
        message = sugar(specs)
    cmd = '(terminal-notifier -title "{title}" -message "{message}" -appIcon "{datadir}/figs/vcsn.png") 2>/dev/null'
    os.system(fmt(cmd, title = title, message = message))

def run(cmd):
    cmd = fmt(cmd)
    if args.verbose:
        print(me + ": run:", cmd, file=sys.stderr)
    try:
        p = subprocess.Popen(cmd, shell=True,
                             stdin=subprocess.DEVNULL,
                             stderr=subprocess.PIPE)
        out, err = p.communicate()
        retcode = p.wait()
        if out:
            out = out.decode('utf-8')
            print(out)
        if retcode:
            if retcode < 0:
                print(me + ": child was terminated by signal", -retcode, file=sys.stderr)
            else:
                print(me + ": child returned", retcode, file=sys.stderr)
            err = err.decode('utf-8')
            err = demangle(err, color=args.color)
            print(err, file=sys.stderr)
            sys.exit(retcode)
    except OSError as e:
        print(me + ": execution failed:", e, file=sys.stderr)
        sys.exit(1)

if args.shared:
    run("LC_ALL=C {ccache} {cxx} {cppflags} {cxxflags} -fPIC -c -o '{tmp}.o' '{base}.cc'")
    run("LC_ALL=C {cxx} {cxxflags} {ldflags} -fPIC -lvcsn -shared -o '{tmp}.so' '{tmp}.o'")
    os.rename(fmt('{tmp}.so'), fmt('{base}.so'))
    # Upon success, remove the .o file, it is large (10x compared to
    # the *.so on erebus using clang) and not required.  However the
    # debug symbols are in there, so when debugging, leave them!
    if "VCSN_DEBUG" not in os.environ:
        os.remove(fmt('{tmp}.o'))
    notify()
else:
    # Exploit ccache: use separate compilation.
    run("LC_ALL=C {ccache} {cxx} {cppflags} {cxxflags} -c -o '{tmp}.o' '{base}.cc'")
    run("LC_ALL=C {cxx} {cxxflags} {ldflags} -lvcsn -o '{tmp}' '{tmp}.o'")
    os.rename(fmt('{tmp}'), fmt('{base}'))
    if "VCSN_DEBUG" not in os.environ:
        os.remove(fmt('{tmp}.o'))
