"""
Silique (https://www.silique.fr)
Copyright (C) 2022-2025
            
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

from typing import Optional
from ruamel.yaml import YAML

from tiramisu.error import PropertiesOptionError, ConfigError

from .config import OutPuts
from .i18n import _


class RougailOutputDisplay:

    def __init__(
        self,
        config: "Config",
        metaconfig=None,
        *,
        rougailconfig: "RougailConfig" = None,
        user_data_errors: Optional[list] = None,
        user_data_warnings: Optional[list] = None,
        config_owner_is_path: bool = False,
        layer_datas=None,
        **kwargs,
    ) -> None:
        if rougailconfig is None:
            from rougail import RougailConfig
            rougailconfig = RougailConfig
        self.rougailconfig = rougailconfig
        self.config = config
        self.metaconfig = metaconfig
        self.layer_datas = layer_datas
        self.config_owner_is_path = config_owner_is_path
        self.errors = []
        self.warnings = []
        if user_data_errors is None:
            user_data_errors = []
        self.user_data_errors = user_data_errors
        if user_data_warnings is None:
            user_data_warnings = []
        self.user_data_warnings = user_data_warnings
        self.out = []
        self.root = None

    def run(self) -> str:
        if self.root is None:
            self.root = self.get_root()
        self.is_mandatory = self.rougailconfig["display.mandatory"]
        self.show_secrets = self.rougailconfig["display.show_secrets"]
        return self.output.run()

    def print(self) -> None:
        return self.output.print()

    def get_root(self) -> None:
        yaml = YAML()
        yaml.indent(mapping=2, sequence=4, offset=2)
        output_format = self.rougailconfig["display.output_format"]
        self.output = OutPuts().get()[output_format](
            _("Variables:"),
            None,
            self,
            yaml,
            self.out,
            no_icon=True,
        )
        return self.output

    def exporter(self) -> bool:
        if self.is_mandatory:
            ori_properties = self.config.property.exportation()
            self.config.property.read_write()
            if not self.user_data_errors and not self.errors:
                self.mandatories()
            self.config.property.importation(ori_properties)
        warnings = self.user_data_warnings + self.warnings
        if warnings:
            self.display_warnings(warnings)

        errors = self.user_data_errors + self.errors
        if errors:
            self.output.display_errors(errors)
            return False
        self.parse_options(
            self.config,
            self.root,
        )
        if self.output.tree and self.output.tree.children:
            self.output.header()
            self.end()
        return True

    def mandatories(self) -> None:
        try:
            mandatories = self.config.value.mandatory()
        except (ConfigError, PropertiesOptionError) as err:
            self.errors.append(_("Error in config: {0}").format(err))
        except ValueError as err:
            self.errors.append(str(err))
        else:
            self.check_mandatories(mandatories)

    def check_mandatories(self, mandatories: list) -> None:
        options_with_error = []
        options = []
        for option in mandatories:
            try:
                option.value.get()
            except PropertiesOptionError:
                options_with_error.append(option)
            else:
                options.append(option)
        if options:
            self.errors.append(
                {_("The following variables are mandatory but have no value:"): self.options_to_dict(options)}
            )
        elif options_with_error:
            self.errors.append(
                {
                    _(
                        "The following variables are inaccessible but are empty and mandatory:"
                    ): self.options_to_dict(options_with_error),
                }
            )

    def options_to_dict(self, mandatories: list):
        options = {}
        for option in mandatories:
            parents = [option]
            parent = option
            while True:
                parent = parent.parent()
                if parent.path() is None:
                    break
                parents.insert(0, parent)
            current_options = options
            for child in parents:
                if child.isoptiondescription():
                    current_options = current_options.setdefault(child.description(), {})
                else:
                    current_options.setdefault(None, []).append(child.description())
        return options

    def parse_options(
        self,
        conf,
        parent,
    ):
        for child in conf:
            if child.isoptiondescription():
                if child.isleadership():
                    func = self.parse_leadership
                else:
                    func = self.parse_options
                family = parent.add_family(child)
                func(
                    child,
                    family,
                )
            else:
                parent.add_variable(child)

    def parse_leadership(
        self,
        leadership,
        parent,
    ) -> None:
        leader, *followers = list(leadership)
        leader_values = leader.value.get()
        followers_values = {idx: [] for idx in range(len(leader_values))}
        for follower in followers:
            followers_values[follower.index()].append(follower)
        for idx in followers_values:
            leader_obj = parent.add_family(leader)
            leader_obj.add_variable(
                leader,
                value=leader_values[idx],
                leader_index=idx,
            )
            for follower in followers_values[idx]:
                leader_obj.add_variable(follower)

    def end(self):
        self.out.append(self.output.tree)
