"""Annotate properties

Created by:
EOLE (http://eole.orion.education.fr)
Copyright (C) 2005-2018

Forked by:
Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021

Silique (https://www.silique.fr)
Copyright (C) 2022-2026

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 Union, List
from tiramisu.setting import PROPERTIES_MAKE_SENSE
from rougail.i18n import _
from rougail.error import DictConsistencyError
from rougail.annotator.variable import Walk
from rougail.convert.object_model import Calculation
from rougail.utils import NAME_REGEXP


PROPERTIES = (
    "hidden",
    "frozen",
    "force_default_on_freeze",
    "force_store_value",
    "disabled",
    "mandatory",
)


class Annotator(Walk):
    """Annotate properties"""

    level = 90

    def __init__(self, objectspace, *args) -> None:
        self.objectspace = objectspace
        self.frozen = {}
        if self.objectspace.paths:
            self.convert_family()
            self.convert_variable()

    def convert_family(self) -> None:
        """convert families"""
        for family in self.get_families():
            self._convert_property(family)
            # collect for force_default_on_freeze
            if family.hidden:
                self.set_variable_frozen(
                    family.path,
                    family.hidden,
                )

    def set_variable_frozen(
        self,
        family_path: str,
        hidden: Union[bool, Calculation],
    ) -> None:
        for variable_path in self.objectspace.parents[family_path]:
            if variable_path in self.objectspace.families:
                # it's a family
                self.set_variable_frozen(
                    variable_path,
                    hidden,
                )
            else:
                # it's a variable
                variable = self.objectspace.paths[variable_path]
                # if frozen is already true or hidden for variable is true => always frozen
                if (
                    self.frozen.get(variable.path) is True
                    or variable.hidden is True
                    or hidden is True
                ):
                    self.frozen[variable.path] = True
                elif variable.path in self.frozen:
                    self.frozen[variable.path].append(hidden)
                else:
                    self.frozen[variable.path] = [hidden]

    def convert_variable(self) -> None:
        """convert variables"""
        for variable in self.get_variables():
            if variable.path.startswith("services."):
                continue
            if variable.type == "symlink":
                continue
            self._convert_variable_property(variable)

    def _convert_variable_property(
        self,
        variable: dict,
    ) -> None:
        """convert properties"""
        path = variable.path
        self._convert_property(variable)
        if variable.hidden:
            if variable.hidden is True:
                self.frozen[path] = True
            elif self.frozen.get(path) is not True:
                self.frozen.setdefault(path, []).append(variable.hidden)
        if path in self.frozen:
            frozen = self.frozen[path]
            if frozen is True:
                value = True
            else:
                value = []
                for calculation in frozen:
                    calculation_copy = calculation.model_copy()
                    calculation_copy.attribute_name = "frozen"
                    calculation_copy.ori_path = calculation_copy.path
                    calculation_copy.path = path
                    value.append(calculation_copy)
                if len(value) == 1:
                    value = value[0]
            self.objectspace.properties.add(path, "frozen", value)
            if not variable.auto_save:
                # if auto_save, save calculated value
                self.objectspace.properties.add(path, "force_default_on_freeze", True)
        if not variable.empty and self.objectspace.multis.get(variable.path, False):
            # a multi could not have "None" has value
            # to permit it, just add empty="false"
            self.objectspace.properties.add(path, "notempty", True)
        if variable.unique:
            self.objectspace.properties.add(path, "unique", True)
        if variable.unique is False:
            self.objectspace.properties.add(path, "notunique", True)
        if variable.auto_save:
            self.objectspace.properties.add(path, "force_store_value", True)
        if variable.tags:
            for tag in variable.tags:
                self.check_tag(tag, variable.xmlfiles)
                self.objectspace.properties.add(variable.path, tag, True)
            self.objectspace.informations.add(
                variable.path, "tags", tuple(variable.tags)
            )

    def check_tag(
        self,
        tag: str,
        xmlfiles: List[str],
    ):
        match = NAME_REGEXP.search(tag)
        if not match:
            msg = _(
                'invalid tag name "{0}" should only contains lowercase ascii character, number or _'
            ).format(tag)
            raise DictConsistencyError(msg, 82, xmlfiles)
        if tag in self.objectspace.modes_level:
            msg = _(
                'invalid tag name "{0}" should not be a name of an existing mode'
            ).format(tag)
            raise DictConsistencyError(msg, 82, xmlfiles)
        if tag in PROPERTIES_MAKE_SENSE:
            msg = _(
                'invalid tag name "{0}" should not be the name of an available properties'
            ).format(tag)
            raise DictConsistencyError(msg, 82, xmlfiles)

    def _convert_property(
        self,
        obj: dict,
    ) -> None:
        for prop in PROPERTIES:
            if not hasattr(obj, prop):
                continue
            value = getattr(obj, prop)
            if not value:
                continue
            self.objectspace.properties.add(obj.path, prop, value)
        if obj.mode:
            self.objectspace.properties.add(obj.path, obj.mode, True)
