# Copyright (C) 2017 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
#
# See the License for the specific language governing permissions and
# limitations under the License.

import re


class VarGraph(object):
    # This is based on the JobGraph from Zuul.

    def __init__(self, vars):
        self.vars = {}
        self._varnames = set()
        self._dependencies = {}  # dependent_var_name -> set(parent_var_names)
        for k, v in vars.items():
            self._varnames.add(k)
        for k, v in vars.items():
            self._addVar(k, str(v))

    bash_var_re = re.compile(r'\$\{?(\w+)')
    def getDependencies(self, value):
        return self.bash_var_re.findall(value)

    def _addVar(self, key, value):
        if key in self.vars:
            raise Exception("Variable {} already added".format(key))
        self.vars[key] = value
        # Append the dependency information
        self._dependencies.setdefault(key, set())
        try:
            for dependency in self.getDependencies(value):
                if dependency == key:
                    # A variable is allowed to reference itself; no
                    # dependency link needed in that case.
                    continue
                if dependency not in self._varnames:
                    # It's not necessary to create a link for an
                    # external variable.
                    continue
                # Make sure a circular dependency is never created
                ancestor_vars = self._getParentVarNamesRecursively(
                    dependency, soft=True)
                ancestor_vars.add(dependency)
                if any((key == anc_var) for anc_var in ancestor_vars):
                    raise Exception("Dependency cycle detected in var {}".
                                    format(key))
                self._dependencies[key].add(dependency)
        except Exception:
            del self.vars[key]
            del self._dependencies[key]
            raise

    def getVars(self):
        ret = []
        keys = sorted(self.vars.keys())
        seen = set()
        for key in keys:
            dependencies = self.getDependentVarsRecursively(key)
            for var in dependencies + [key]:
                if var not in seen:
                    ret.append((var, self.vars[var]))
                    seen.add(var)
        return ret

    def getDependentVarsRecursively(self, parent_var):
        dependent_vars = []

        current_dependent_vars = self._dependencies[parent_var]
        for current_var in current_dependent_vars:
            if current_var not in dependent_vars:
                dependent_vars.append(current_var)
            for dep in self.getDependentVarsRecursively(current_var):
                if dep not in dependent_vars:
                    dependent_vars.append(dep)
        return dependent_vars

    def _getParentVarNamesRecursively(self, dependent_var, soft=False):
        all_parent_vars = set()
        vars_to_iterate = set([dependent_var])
        while len(vars_to_iterate) > 0:
            current_var = vars_to_iterate.pop()
            current_parent_vars = self._dependencies.get(current_var)
            if current_parent_vars is None:
                if soft:
                    current_parent_vars = set()
                else:
                    raise Exception("Dependent var {} not found: ".format(
                                    dependent_var))
            new_parent_vars = current_parent_vars - all_parent_vars
            vars_to_iterate |= new_parent_vars
            all_parent_vars |= new_parent_vars
        return all_parent_vars


class LocalConf(object):

    def __init__(self, localrc, localconf, services, plugins):
        self.localrc = []
        self.meta_sections = {}
        if plugins:
            self.handle_plugins(plugins)
        if services:
            self.handle_services(services)
        if localrc:
            self.handle_localrc(localrc)
        if localconf:
            self.handle_localconf(localconf)

    def handle_plugins(self, plugins):
        for k, v in plugins.items():
            if v:
                self.localrc.append('enable_plugin {} {}'.format(k, v))

    def handle_services(self, services):
        for k, v in services.items():
            if v is False:
                self.localrc.append('disable_service {}'.format(k))
            elif v is True:
                self.localrc.append('enable_service {}'.format(k))

    def handle_localrc(self, localrc):
        vg = VarGraph(localrc)
        for k, v in vg.getVars():
            self.localrc.append('{}={}'.format(k, v))

    def handle_localconf(self, localconf):
        for phase, phase_data in localconf.items():
            for fn, fn_data in phase_data.items():
                ms_name = '[[{}|{}]]'.format(phase, fn)
                ms_data = []
                for section, section_data in fn_data.items():
                    ms_data.append('[{}]'.format(section))
                    for k, v in section_data.items():
                        ms_data.append('{} = {}'.format(k, v))
                    ms_data.append('')
                self.meta_sections[ms_name] = ms_data

    def write(self, path):
        with open(path, 'w') as f:
            f.write('[[local|localrc]]\n')
            f.write('\n'.join(self.localrc))
            f.write('\n\n')
            for section, lines in self.meta_sections.items():
                f.write('{}\n'.format(section))
                f.write('\n'.join(lines))


def main():
    module = AnsibleModule(
        argument_spec=dict(
            plugins=dict(type='dict'),
            services=dict(type='dict'),
            localrc=dict(type='dict'),
            local_conf=dict(type='dict'),
            path=dict(type='str'),
        )
    )

    p = module.params
    lc = LocalConf(p.get('localrc'),
                   p.get('local_conf'),
                   p.get('services'),
                   p.get('plugins'))
    lc.write(p['path'])

    module.exit_json()


from ansible.module_utils.basic import *  # noqa
from ansible.module_utils.basic import AnsibleModule

if __name__ == '__main__':
    main()