"""Redefine Tiramisu object

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-2024

distribued with GPL-2 or later license

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""
try:
    from tiramisu5 import DynOptionDescription, calc_value
except ModuleNotFoundError:
    from tiramisu import DynOptionDescription, calc_value
from importlib.machinery import SourceFileLoader as _SourceFileLoader
from importlib.util import spec_from_loader as _spec_from_loader, module_from_spec as _module_from_spec
from jinja2 import StrictUndefined, DictLoader
from jinja2.sandbox import SandboxedEnvironment
from rougail.object_model import CONVERT_OPTION
from rougail.error import display_xmlfiles
from tiramisu.error import ValueWarning, ConfigError
from .utils import normalize_family


global func
dict_env = {}
ENV = SandboxedEnvironment(loader=DictLoader(dict_env), undefined=StrictUndefined)
func = ENV.filters
ENV.compile_templates('jinja_caches', zip=None)


def load_functions(path):
    global _SourceFileLoader, _spec_from_loader, _module_from_spec, func
    loader = _SourceFileLoader('func', path)
    spec = _spec_from_loader(loader.name, loader)
    func_ = _module_from_spec(spec)
    loader.exec_module(func_)
    for function in dir(func_):
        if function.startswith('_'):
            continue
        func[function] = getattr(func_, function)


def rougail_calc_value(*args, __default_value=None, **kwargs):
    values = calc_value(*args, **kwargs)
    if __default_value is not None and values in [None, []]:
        return __default_value
    return values


def jinja_to_function(__internal_variable, __internal_attribute, __internal_jinja, __internal_type, __internal_multi, __internal_files, __default_value=None, **kwargs):
    global ENV, CONVERT_OPTION
    kw = {}
    for key, value in kwargs.items():
        if '.' in key:
            c_kw = kw
            path, var = key.rsplit('.', 1)
            for subkey in path.split('.'):
                c_kw = c_kw.setdefault(subkey, {})
            c_kw[var] = value
        else:
            if key in kw:
                raise ConfigError(f'internal error, multi key for "{key}" in jinja_to_function')
            kw[key] = value
    try:
        values = ENV.get_template(__internal_jinja).render(kw, **func).strip()
    except Exception as err:
        raise ConfigError(f'cannot calculating "{__internal_attribute}" attribute for variable "{__internal_variable}" in {display_xmlfiles(__internal_files)}: {err}') from err
    convert = CONVERT_OPTION[__internal_type].get('func', str)
    if __internal_multi:
        values = [convert(val) for val in values.split('\n') if val != ""]
        if not values and __default_value is not None:
            return __default_value
        return values
    try:
        values = convert(values)
    except Exception as err:
        raise ConfigError(f'cannot converting "{__internal_attribute}" attribute for variable "{__internal_variable}" in {display_xmlfiles(__internal_files)}: {err}') from err
    values = values if values != '' and values != 'None' else None
    if values is None and __default_value is not None:
        return __default_value
    return values


def variable_to_property(prop, value, when, inverse):
    if inverse:
        is_match = value != when
    else:
        is_match = value == when
    return prop if is_match else None


def jinja_to_property(prop, when, inverse, **kwargs):
    value = func['jinja_to_function'](**kwargs)
    return func['variable_to_property'](prop, value is not None, when, inverse)


def jinja_to_property_help(prop, **kwargs):
    value = func['jinja_to_function'](**kwargs)
    return (prop, f'\"{prop}\" ({value})')


def valid_with_jinja(warnings_only=False, **kwargs):
    global ValueWarning
    value = func['jinja_to_function'](**kwargs)
    if value:
       if warnings_only:
           raise ValueWarning(value)
       else:
           raise ValueError(value)


func['calc_value'] = rougail_calc_value
func['jinja_to_function'] = jinja_to_function
func['jinja_to_property'] = jinja_to_property
func['jinja_to_property_help'] = jinja_to_property_help
func['variable_to_property'] = variable_to_property
func['valid_with_jinja'] = valid_with_jinja


class ConvertDynOptionDescription(DynOptionDescription):
    """Suffix could be an integer, we should convert it in str
    Suffix could also contain invalid character, so we should "normalize" it
    """

    def convert_suffix_to_path(self, suffix):
        if suffix is None:
            return suffix
        if not isinstance(suffix, str):
            suffix = str(suffix)
        return normalize_family(suffix)

    def impl_getname(
        self,
        suffix=None,
    ) -> str:
        """get name"""
        name = super().impl_getname(None)
        if suffix is None:
            return name
        path_suffix = self.convert_suffix_to_path(suffix)
        if "{{ suffix }}" in name:
            return name.replace("{{ suffix }}", path_suffix)
        return name + path_suffix

    def impl_get_display_name(
        self,
        subconfig,
        ) -> str:
        display = super().impl_get_display_name(subconfig)
        if "{{ suffix }}" in display:
            return display.replace("{{ suffix }}", self.convert_suffix_to_path(self.get_suffixes(subconfig)[-1]))
        return display
