#!/usr/bin/env python3
from os import listdir
from os.path import isdir, join
from tabulate import tabulate
from sys import argv

from rougail import RougailConfig
from rougail.convert import RougailConvert
from rougail.objspace import RootRougailObject
from risotto.utils import EXTRA_ANNOTATORS, ROUGAIL_NAMESPACE, ROUGAIL_NAMESPACE_DESCRIPTION
from risotto.image import load_application_service


rougailconfig = RougailConfig
rougailconfig['variable_namespace'] = ROUGAIL_NAMESPACE
rougailconfig['variable_namespace_description'] = ROUGAIL_NAMESPACE_DESCRIPTION


DEFAULT_TYPE = 'string'
ROUGAIL_VARIABLE_TYPE = 'https://forge.cloud.silique.fr/risotto/rougail/src/branch/main/doc/variable/README.md#le-type-de-la-variable'


def add_title_family(elts, dico):
    for idx, elt in enumerate(elts):
        description = elt.doc
        if not idx:
            description = description.capitalize()
        space = idx + 3
        title = '#' * space + f' {description} (*{elt.path}*)'
        if title not in dico:
            dico[title] = {'variables': [], 'help': '', 'type': ''}
            if hasattr(elt, 'information') and hasattr(elt.information, 'help'):
                dico[title]['help'] = elt.information.help
        if hasattr(elt, 'suffixes') and elt.suffixes:
            dico[title]['type'] = 'dynamic'
            dico[title]['suffixes'] = elt.suffixes.path
        if hasattr(elt, 'leadership') and elt.leadership:
            dico[title]['type'] = 'leadership'
    return title



def parse(applicationservice, elts, dico, providers_suppliers, hidden, objectspace):
    elt = elts[-1]
    first_variable = True
    if not hidden:
        hidden = hasattr(elt, 'properties') and ('hidden' in elt.properties or 'disabled' in elt.properties)
    is_leadership = hasattr(elt, 'leadership') and elt.leadership
    for children in vars(elt).values():
        if isinstance(children, dict):
            children = list(children.values())
        if not isinstance(children, list):
            continue
        for idx, child in enumerate(children):
            if isinstance(child, objectspace.property_) or \
                    not isinstance(child, RootRougailObject):
                continue
            if isinstance(child, objectspace.variable):
                if not hidden and (not hasattr(child, 'properties') or ('hidden' not in child.properties and not 'disabled' in child.properties)):
                    if first_variable:
                        title = add_title_family(elts, dico)
                        first_variable = False
                    var_title = child.doc
                    if hasattr(child, 'properties') and 'mandatory' in child.properties:
                        var_title = '**' + var_title + '**'
                    var_path = child.xmlfiles[-1].split('/', 2)[-1]
                    if child.doc != child.name:
                        var_title += f' (*[{child.name}]({var_path})*)'
                    else:
                        var_title = f'*[{var_title}]({var_path})*'
                    if ((idx == 0 or not is_leadership) and child.multi is True) or (idx != 0 and is_leadership and child.multi == 'submulti'):
                        var_title += ' [+]'
                    values = {'description': var_title,
                              }
                    if hasattr(child, 'information') and hasattr(child.information, 'help'):
                        values['help'] = child.information.help
                    if child.type != DEFAULT_TYPE:
                        values['type'] = child.type
                    if hasattr(child, 'default'):
                        default = child.default
                        if isinstance(default, objectspace.value):
                            default = '<calculated>'
                        if isinstance(default, list):
                            default = '<br />'.join(default)
                        values['values'] = default
                    if hasattr(child, 'choice'):
                        values['choices'] = '<br />'.join([choice.name for choice in child.choice])
                    if hasattr(child, 'provider'):
                        provider = child.provider
                        values['provider'] = provider
                        if ':' not in provider:
                            providers_suppliers['providers'].setdefault(provider, []).append(applicationservice)
                    if hasattr(child, 'supplier'):
                        supplier = child.supplier
                        values['supplier'] = supplier
                        if ':' not in supplier:
                            providers_suppliers['suppliers'].setdefault(supplier, []).append(applicationservice)
                    dico[title]['variables'].append(values)
                else:
                    if hasattr(child, 'provider'):
                        provider = child.provider
                        if ':' not in provider:
                            providers_suppliers['providers'].setdefault(provider, []).append(applicationservice)
                    if hasattr(child, 'supplier'):
                        supplier = child.supplier
                        if ':' not in supplier:
                            providers_suppliers['suppliers'].setdefault(supplier, []).append(applicationservice)
            else:
                parse(applicationservice, elts + [child], dico, providers_suppliers, hidden, objectspace)


def build_dependencies_tree(applicationservice, applicationservice_data, applicationservices_data, applicationservices_data_ext, space):
    depends = []
    if applicationservice_data['depends']:
        if applicationservice in applicationservices_data:
            app_data = applicationservices_data[applicationservice]
        else:
            for url, apps_data in applicationservices_data_ext.items():
                if applicationservice in apps_data:
                    app_data = apps_data[applicationservice]
                    break
            else:
                raise Exception(f'cannot find applicationservice "{applicationservice}"')
        for idx, depend in enumerate(app_data['depends']):
            if depend in applicationservices_data:
                url = '..'
                ext = False
            else:
                for url, apps_data in applicationservices_data_ext.items():
                    if depend in apps_data:
                        break
                else:
                    raise Exception(f'cannot find applicationservice "{applicationservice}"')
                ext = True
            subdepends = build_dependencies_tree(depend, applicationservice_data, applicationservices_data, applicationservices_data_ext, space + 2)
            if not idx or subdepends:
                title = '\n'
            else:
                title = ''
            depend_desc = depend
            if ext:
                depend_desc += ' (in external dataset)'
            title = ' ' * space + f'- [{depend_desc}]({url}/{depend}/README.md)'
            depends.append(title)
            depends.extend(subdepends)
    return depends


def load_data(url, directory, applicationservices_data, global_data={}):
    root_path = join(directory, 'seed')
    applicationservices = listdir(root_path)
    tmps = {}
    for applicationservice in applicationservices:
        as_dir = join(root_path, applicationservice)
        if not isdir(as_dir):
            continue
        applicationservice_data = load_application_service(as_dir)
        if not applicationservice_data.get('documentation', True):
            continue
        applicationservices_data[applicationservice] = {'description': applicationservice_data['description'],
                                                        'website': applicationservice_data.get('website'),
                                                        'as_dir': as_dir,
                                                        'depends': [],
                                                        'used_by': [],
                                                        }
        if applicationservice in tmps:
            for app in tmps.pop(applicationservice):
                used_by = f'[{app}](../{app}/README.md)'
                applicationservices_data[applicationservice]['used_by'].append(used_by)
        if 'depends' in applicationservice_data:
            for depend in applicationservice_data['depends']:
                applicationservices_data[applicationservice]['depends'].append(depend)
                if depend in applicationservices_data:
                    used_by = f'[{applicationservice}](../{applicationservice}/README.md)'
                    applicationservices_data[depend]['used_by'].append(used_by)
                else:
                    tmps.setdefault(depend, []).append(applicationservice)
    if tmps and global_data:
        for depend, applications in tmps.items():
            for app in applications:
                used_by = f'[{app} (in external dataset)]({url}/{app}/README.md)'
                global_data[depend]['used_by'].append(used_by)


def write_data(applicationservices_data, applicationservices_data_ext):
    dico = {}
    providers_suppliers = {'providers': {}, 'suppliers': {}}
    for applicationservice, applicationservice_data in applicationservices_data.items():
        as_dir = applicationservice_data['as_dir']
        dirname = join(as_dir, 'dictionaries')
        if isdir(dirname):
            rougailconfig['dictionaries_dir'] = [dirname]
        else:
            rougailconfig['dictionaries_dir'] = []
        dirname_extras = join(as_dir, 'extras')
        extra_dictionaries = {}
        if isdir(dirname_extras):
            for extra in listdir(dirname_extras):
                extra_dir = join(dirname_extras, extra)
                if isdir(extra_dir):
                    extra_dictionaries.setdefault(extra, []).append(extra_dir)
        if not isdir(dirname) and not extra_dictionaries:
            continue
        rougailconfig['extra_dictionaries'] = extra_dictionaries
        converted = RougailConvert(rougailconfig, just_doc=True)
        converted.load_dictionaries()
        converted.annotate()
        objectspace = converted.rougailobjspace
        if hasattr(objectspace.space, 'variables'):
            dico[applicationservice] = {}
            for name, elt in objectspace.space.variables.items():
                parse(applicationservice, [elt], dico[applicationservice], providers_suppliers, False, objectspace)
    for applicationservice, applicationservice_data in applicationservices_data.items():
        as_dir = applicationservice_data['as_dir']
        with open(join(as_dir, 'README.md'), 'w') as as_fh:
            as_fh.write(f'---\ngitea: none\ninclude_toc: true\n---\n\n')
            as_fh.write(f'# {applicationservice}\n\n')
            as_fh.write(f'## Description\n\n')
            description = applicationservice_data['description'] + '.\n'
            if applicationservice_data['website']:
                description += f'\n[For more informations]({applicationservice_data["website"]})\n'
            as_fh.write(description)
            if applicationservice_data['depends']:
                as_fh.write(f'\n## Dependances\n\n')
                for depend in build_dependencies_tree(applicationservice, applicationservice_data, applicationservices_data, applicationservices_data_ext, 0):
                    as_fh.write(f'{depend}\n')
            if applicationservice in dico and dico[applicationservice]:
                as_fh.write('\n## Variables\n\n')
                for title, data in dico[applicationservice].items():
                    as_fh.write(f'{title}\n')
                    if data['type'] == 'leadership':
                        as_fh.write('\nThis a family is a leadership.\n')
                    if data['type'] == 'dynamic':
                        as_fh.write(f'\nThis a dynamic family generated from the variable "{data["suffixes"]}".\n')
                    if data['help']:
                        as_fh.write(f'\n{data["help"]}\n')
                    keys = []
                    if data['variables']:
                        variables = data['variables']
                        for variable in variables:
                            for key in variable:
                                if key not in keys:
                                    keys.append(key)
                        values = []
                        for variable in variables:
                            value = []
                            for key in keys:
                                if key in variable:
                                    val = variable[key]
                                elif key == 'type':
                                    val = DEFAULT_TYPE
                                else:
                                    val = ''
                                if key == 'type':
                                    val = f'[{val}]({ROUGAIL_VARIABLE_TYPE})'
                                value.append(val)
                            values.append(value)
                        as_fh.write('\n')
                        as_fh.write(tabulate(values, headers=[key.capitalize() for key in keys], tablefmt="github"))
                        as_fh.write('\n')
                    as_fh.write('\n')
                # FIXME if not applicationservice_data['used_by']:
                # FIXME     as_fh.write('\n## Variables with dependencies\n\n')
                as_fh.write('\n- [+]: variable is multiple\n- **bold**: variable is mandatory\n')
            if applicationservice_data['used_by']:
                as_fh.write('\n## Used by\n\n')
                if len(applicationservice_data['used_by']) == 1:
                    link = applicationservice_data['used_by'][0]
                    as_fh.write(f'{link}\n')
                else:
                    for link in applicationservice_data['used_by']:
                        as_fh.write(f'- {link}\n')
            linked = []
            for provider, provider_as in providers_suppliers['providers'].items():
                if not applicationservice in provider_as:
                    continue
                for supplier in providers_suppliers['suppliers'][provider]:
                    if supplier in linked:
                        continue
                    linked.append(supplier)
            linked.sort()
            if linked:
                if len(linked) == 1:
                    as_fh.write('\n## Supplier\n\n')
                    as_fh.write(f'[{linked[0]}](../{linked[0]}/README.md)\n')
                else:
                    as_fh.write('\n## Suppliers\n\n')
                    for supplier in linked:
                        as_fh.write(f'- [{supplier}](../{supplier}/README.md)\n')
            linked = []
            for supplier, supplier_as in providers_suppliers['suppliers'].items():
                if not applicationservice in supplier_as:
                    continue
                for provider in providers_suppliers['providers'][supplier]:
                    if provider in linked:
                        continue
                    linked.append(provider)
            linked.sort()
            if linked:
                if len(linked) == 1:
                    as_fh.write('\n## Provider\n\n')
                    as_fh.write(f'[{linked[0]}](../{linked[0]}/README.md)\n')
                else:
                    as_fh.write('\n## Providers\n\n')
                    for provider in linked:
                        as_fh.write(f'- [{provider}](../{provider}/README.md)\n')
            as_fh.write(f'\n[All applications services for this dataset.](../README.md)\n')

    with open('seed/README.md', 'w') as as_fh:
        as_fh.write('# Application services\n\n')
        applicationservices = {}
        for applicationservice in applicationservices_data:
            applicationservices.setdefault(applicationservice.split('-')[0], []).append(applicationservice)
        applicationservice_categories = list(applicationservices.keys())
        applicationservice_categories.sort()
        for category in applicationservice_categories:
            applicationservices_ = applicationservices[category]
            if len(applicationservices_) == 1:
                applicationservice = applicationservices_[0]
                applicationservice_data = applicationservices_data[applicationservice]
                as_fh.write(f'- [{applicationservice}]({applicationservice}/README.md): {applicationservice_data["description"]}\n')
            else:
                as_fh.write(f'- {category}:\n')
                applicationservices_.sort()
                for applicationservice in applicationservices_:
                    applicationservice_data = applicationservices_data[applicationservice]
                    as_fh.write(f'  - [{applicationservice}]({applicationservice}/README.md): {applicationservice_data["description"]}\n')
        providers = list(providers_suppliers['providers'].keys())
        providers.sort()
        if providers:
            as_fh.write('\n# Providers and suppliers\n\n')
        for provider in providers:
            as_fh.write(f'- {provider}:\n')
            if providers_suppliers['providers'][provider]:
                if len(providers_suppliers['providers'][provider]) == 1:
                    applicationservice = providers_suppliers['providers'][provider][0]
                    as_fh.write(f'  - Provider: [{applicationservice}]({applicationservice}/README.md)\n')
                else:
                    as_fh.write(f'  - Providers:\n')
                    for applicationservice in providers_suppliers['providers'][provider]:
                        as_fh.write(f'    - [{applicationservice}]({applicationservice}/README.md)\n')
            if providers_suppliers['suppliers']:
                if len(providers_suppliers['suppliers'][provider]) == 1:
                    applicationservice = providers_suppliers['suppliers'][provider][0]
                    as_fh.write(f'  - Supplier: [{applicationservice}]({applicationservice}/README.md)\n')
                else:
                    as_fh.write(f'  - Suppliers:\n')
                    for applicationservice in providers_suppliers['suppliers'][provider]:
                        as_fh.write(f'    - [{applicationservice}]({applicationservice}/README.md)\n')


def main():
    applicationservices_data = {}
    load_data('..', '', applicationservices_data)
    applicationservices_data_ext = {}
    for arg in argv[1:]:
        if '=' not in arg:
            raise Exception(f'cannot parse argument "{arg}", should be dataset_path=url')
        path, url = arg.split('=', 1)
        if url in applicationservices_data_ext:
            raise Exception(f'duplicate url "{url}" in arguments')
        applicationservices_data_ext[url] = {}
        load_data(url, path, applicationservices_data_ext[url], applicationservices_data)
    write_data(applicationservices_data, applicationservices_data_ext)


if __name__ == '__main__':
    main()