import glob
import os
import sys

STAGE0_MAP = {
    'it0': '.py',
    'it1': '.py',
    'it2': '.c',
    'it3': '.ll',
}

class Rule:
    def __init__(self, target: str, prerequisites: list[str], recipe: list[str]) -> None:
        self.target = target
        self.prerequisites = prerequisites
        self.recipe = recipe

    def make_lines(self):
        return [
            self.target + ': ' + ' '.join(self.prerequisites),
        ] + [
            '\t' + line
            for line in self.recipe
        ] + ['']

def get_file_list():
    return sorted(glob.glob('test_*/*.lang0'))

def make_rules(file_path, lang0it):
    result: list[Rule] = []

    stdin = file_path.replace('.lang0', '.stdin')
    exp_cmperr = file_path.replace('.lang0', '.exp.cmperr')
    exp_stderr = file_path.replace('.lang0', '.exp.stderr')
    exp_stdout = file_path.replace('.lang0', '.exp.stdout')

    act_cmperr = 'build/' + file_path.replace('.lang0', f'.{lang0it}.cmperr')

    act_stderr = 'build/' + file_path.replace('.lang0', f'.{lang0it}.stderr')
    act_stdout = 'build/' + file_path.replace('.lang0', f'.{lang0it}.stdout')

    act_result = 'build/' + file_path.replace('.lang0', f'.{lang0it}.result')
    program = 'build/' + file_path.replace('.lang0', f'.{lang0it}')

    program_stage0 = program + STAGE0_MAP[lang0it]

    if os.path.isfile(exp_cmperr):
        # No need to try to run the program
        exp_list = [exp_cmperr]
        act_list = [act_cmperr]
    elif os.path.isfile(exp_stdout) or os.path.isfile(exp_stderr):
        result.append(Rule(
            act_stdout,
            [program] + ([stdin] if os.path.isfile(stdin) else []),
            [
                '-' + (f'cat {stdin} | ' if os.path.isfile(stdin) else 'echo x | ')
                    + f'{program} 1> {act_stdout} 2> {act_stderr}',
            ]
        ))

        exp_list = [exp_stdout, exp_stderr]
        act_list = [act_stdout, act_stderr]
    else:
        assert False, f'Missing expectations for {file_path}'

    result.append(Rule(
        act_result,
        ([program_stage0] if os.path.isfile(exp_cmperr) else [act_stdout]) + [
            x
            for x in exp_list
            if os.path.isfile(x)
        ],
        [
            'rm -f $@',
        ] + [
            f'-diff {exp_file} {act_file} >> $@'
            for exp_file, act_file in zip(exp_list, act_list, strict=True)
            if os.path.isfile(exp_file)
        ] + [
            f'-diff /dev/null {act_file} >> $@'
            for exp_file, act_file in zip(exp_list, act_list, strict=True)
            if not os.path.isfile(exp_file)
        ]
    ))

    return result

def main(program, write_to, *lang0it):
    rule_list_list = [
        make_rules(file_path, it)
        for file_path in get_file_list()
        for it in lang0it
    ]

    result_targets = [
        rule.target
        for rule_list in rule_list_list
        for rule in rule_list
        if rule.target.endswith('.result')
    ]

    rule_list_list.append([Rule(
        'verify-results',
        result_targets,
        [
            '@echo Finding failed test results...',
            '@find $^ -not -empty -print -exec false {} +',
            '@echo All tests passed.'
        ]
    )])

    data = '\n'.join(
        line
        for rule_list in rule_list_list
        for rule in rule_list
        for line in rule.make_lines()
    )

    with open(write_to, 'wt', encoding='ASCII') as fil:
        fil.write(data)

if __name__ == '__main__':
    main(*sys.argv)
