"""Routines for the export of scripts to various form

"""

import itertools
from . import Script, __version__


def get_pipeline_options(scripts:[Script]) -> dict:
    """Return the mapping option->False to be used by standalone_export_pipeline.

    One may want to set some options to True, to get them in pipeline standalone export.

    """
    return {n: False for _, __, n in option_names_from_options(scripts)}


def standalone_export_pipeline(scripts:[Script], options:dict={}, default_context:str='', verbosity:int=0) -> [str]:
    """Yield Python code strings, implementing a standalone program reproducing given pipeline.

    options -- {option name: bool} indicating whether or not the option
               must be exposed as program option.
    default_context -- the default initial context.
    verbosity -- verbosity of the standalone program itself.

    """
    option_names = {(idx, name): final_name for idx, (_, name, final_name) in enumerate(option_names_from_options(scripts), 1)}
    options_used = tuple(
        (idx, final_name) for idx, final_name in option_names.items()
        if options[final_name]
    )
    yield '"Standalone script generated by biseau ' + str(__version__) + '"\n'
    yield 'import argparse'
    yield 'import clitogui'
    yield 'import clyngor'
    yield ''
    if options:
        yield '@clitogui'
        yield 'def cli():'
        yield '    parser = argparse.ArgumentParser()'
        for (idx, name), final_name in options_used:
            option = scripts[idx-1].options
            option = scripts[idx].options[name]
            print(option)
            yield f'    parser.addArgument({final_name})'
        yield '    ...'
        yield '    return parser'
        yield ''
    yield 'def run_on(context:str, ' + ', '.join(name for _, name in options_used) + '):'
    for script in scripts:
        if script.language == 'python':
            yield '     models = clyngor.solve(inline=context).by_predicate'
        elif script.language == 'asp':
            yield '     context += """\n' + script.source_code + '\n"""'
        else:
            raise ValueError(f"unhandled export of language '{script.language}'")
    yield 'if __name__ == "__main__":'
    yield '    args = cli()'
    yield '    for dot in run_on(**args):'
    yield '        print(dot)'


def option_names_from_options(scripts:[Script]) -> [(str, str, str)]:
    """Yield (script name, option name, unambiguous option name)
    so that unambiguous option name is deterministic and unique
    accross all options of given scripts.

    """
    used_names = set()  # set of all available options
    conflicting_names = set()  # names that will need the script name
    script_names = tuple(script.name for script in scripts)
    script_name_doublons = {n: itertools.count(1) for n in script_names if script_names.count(n) > 1}
    # define names of scripts (append their index when multiple scripts of same name)
    script_names = tuple(
        (script.name + ' ' + str(next(script_name_doublons[script.name])))
        if script.name in script_name_doublons else script.name
        for script in scripts
    )
    assert len(script_names) == len(scripts)
    # detect the name conflicts among scripts options
    for script in scripts:
        for name, type, default, description in script.options:
            if name in used_names:
                conflicting_names.add(name)
            used_names.add(name)
    # for each script, ensure the use of proper option name
    for script_name, script in zip(script_names, scripts):
        for name, type, default, description in script.options:
            final_name = name
            if name in conflicting_names:
                final_name = f'{script_name}: {name}'
            yield script_name, name, final_name
