#!/usr/bin/env python3
import argparse
import json
import os
import re
from collections import defaultdict

import pathspec
from dataclasses import dataclass, field
from typing import Dict, Set

ROOT = os.path.abspath(os.path.join(os.path.split(__file__)[0], '..'))
ARC_ROOT = os.popen('arc root').read().strip()
ARC_ROOT = ARC_ROOT or 'intranet/forms'

macro = re.compile(r'(?P<indent>\s*)\$\$(?P<macro>[_A-Z]+) (?P<args>\{.*\})')


@dataclass
class Node:
    files: Set[str] = field(default_factory=set)
    subfolders: Set[str] = field(default_factory=set)


def _walk(path: str, tree: Dict[str, Node], absolute: bool, ident: str):
    if path not in tree:
        return
    node = tree[path]

    for subfolder in sorted(node.subfolders):
        yield from _walk(os.path.join(path, subfolder), tree, absolute, ident)

    for file in sorted(node.files):
        rel_path = os.path.join(path, file)
        if absolute:
            rel_path = os.path.join(path, rel_path).replace(ARC_ROOT, '')[1:]
        yield rel_path


def warn(working_folder, template_file):
    msgs = [
        '!!! This file is autogenerated !!!',
        '',
        f"Template: {template_file.replace(ARC_ROOT, '')}",
        '',
        'Do not edit it manually, edit template instead',
        'Use `make ya` to rebuild',
    ]

    max_len = max([len(m) for m in msgs])
    border = '#' * (max_len + 8)
    template = '#   {0: <%s}   #' % max_len

    yield border
    for m in msgs:
        yield template.format(m)
    yield border


def glob_match(working_folder, template_file, pattern, absolute=False, ident=4):
    path = working_folder
    ident = ' ' * ident
    path = path + '/'
    spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, pattern)
    tree = defaultdict(Node)
    found = False

    for f in spec.match_tree(path):
        found = True
        path, fn = os.path.split(f)
        path_parts = path.split('/')
        tree[path].files.add(fn)

        parent = ''
        if path != '':
            for part in path_parts:
                next_node = os.path.join(parent, part)
                tree[parent].subfolders.add(part)
                parent = next_node

    if found:
        yield from _walk('', tree, absolute, ident)
    else:
        yield f'# no files matched {pattern}'


def module_match(working_folder, template_file, rel_path='', match='', replace='', ident=4):
    root_path = working_folder
    path = os.path.join(root_path, rel_path)
    RE = re.compile(match)
    ident = ' ' * ident
    dir_sets = set()

    for subpath, _, _ in os.walk(path):
        module = subpath.replace(root_path, '')[1:].replace('/', '.')
        if RE.match(module):
            dir_sets.add(RE.sub(replace, module))
    dir_sets = sorted(dir_sets)

    for dir in dir_sets:
        yield dir


macroses = {'GLOB': glob_match, 'PY_MODULE': module_match, 'WARN': warn}


def process_template(src, tgt):
    src = os.path.abspath(src)
    tgt = os.path.abspath(tgt)
    working_folder, _ = os.path.split(tgt)
    context = {'working_folder': working_folder, 'template_file': src}
    with open(tgt, 'w') as wb:
        with open(src, 'r') as rb:
            for line in rb:
                macro_match = macro.match(line)
                if macro_match:
                    macro_name = macro_match.groupdict()['macro']
                    macro_indent = macro_match.groupdict()['indent']
                    macro_args = {}
                    macro_args.update(context)
                    macro_args.update(json.loads(macro_match.groupdict()['args']))

                    if macro_name in macroses:
                        for q in macroses[macro_name](**macro_args):
                            wb.write(macro_indent + q + '\n')
                else:
                    wb.write(line)

    # dirs = [f.path.replace(path, '') for f in os.scandir(path) if f.is_dir()]


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('input_file', help='input filename', type=str)
    parser.add_argument('output_file', help='output filename', type=str)
    args = parser.parse_args()
    process_template(args.input_file, args.output_file)


if __name__ == '__main__':
    main()
