# -*- coding: utf-8 -*-
# Copyright (C) 2012-2019 Team tiramisu (see AUTHORS for all contributors)
#
# 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/>.
# ____________________________________________________________
"user defined exceptions"
import weakref
from .i18n import _


def display_list(lst, separator='and', add_quote=False):
    if separator == 'and':
        separator = _('and')
    elif separator == 'or':
        separator = _('or')
    if isinstance(lst, tuple) or isinstance(lst, frozenset):
        lst = list(lst)
    if len(lst) == 1:
        ret = lst[0]
        if not isinstance(ret, str):
            ret = str(ret)
        if add_quote and not ret.startswith('"'):
            ret = '"{}"'.format(ret)
        return ret
    lst_ = []
    for l in lst:
        if not isinstance(l, str):
            l = str(l)
        lst_.append(_(l))
    lst__ = []
    for l in lst_:
        if add_quote and not l.startswith('"'):
            l = '"{}"'.format(l)
        lst__.append(l)
    lst__.sort()
    last = lst__[-1]
    return ', '.join(lst__[:-1]) + _(' {} ').format(separator) + '{}'.format(last)


# Exceptions for an Option
class PropertiesOptionError(AttributeError):
    "attempt to access to an option with a property that is not allowed"
    def __init__(self,
                 option_bag,
                 proptype,
                 settings,
                 opt_type=None,
                 name=None,
                 orig_opt=None):
        if opt_type:
            self._opt_type = opt_type
            self._name = name
            self._orig_opt = orig_opt
        else:
            if option_bag.option.impl_is_optiondescription():
                self._opt_type = 'optiondescription'
            else:
                self._opt_type = 'option'
            self._name = option_bag.option.impl_get_display_name()
            self._orig_opt = None
        self._option_bag = option_bag
        self.proptype = proptype
        self._settings = settings
        self.msg = None
        super().__init__(None)

    def set_orig_opt(self, opt):
        self._orig_opt = opt

    def __str__(self):
        # this part is a bit slow, so only execute when display
        if self.msg is not None:
            return self.msg
        if self._settings is None:
            return 'error'
        properties = list(self._settings.calc_raises_properties(self._option_bag,
                                                                apply_requires=False))
        for property_ in self._settings.get_calculated_properties(self._option_bag):
            prop = property_.help(self._option_bag)
            if prop is not None:
                properties.append(prop)

        if not properties:
            # if proptype == ['mandatory']
            properties = self.proptype
        only_one = len(properties) == 1
        properties_msg = display_list(properties, add_quote=True)
        if only_one:
            prop_msg = _('property')
        else:
            prop_msg = _('properties')
        if properties == ['frozen']:
            if self._orig_opt:
                msg = 'cannot modify the {0} "{1}" because "{2}" has {3} {4}'
            else:
                msg = 'cannot modify the {0} "{1}" because has {2} {3}'
        else:
            if self._orig_opt:
                msg = 'cannot access to {0} "{1}" because "{2}" has {3} {4}'
            else:
                msg = 'cannot access to {0} "{1}" because has {2} {3}'
        if self._orig_opt:
            self.msg = _(msg).format(self._opt_type,
                                     self._orig_opt.impl_get_display_name(),
                                     self._name,
                                     prop_msg,
                                     properties_msg)
        else:
            self.msg = _(msg).format(self._opt_type,
                                     self._name,
                                     prop_msg,
                                     properties_msg)
        del self._opt_type, self._name
        del self._settings, self._orig_opt
        return self.msg


#____________________________________________________________
# Exceptions for a Config
class ConfigError(Exception):
    """attempt to change an option's owner without a value
    or in case of `_cfgimpl_descr` is None
    or if a calculation cannot be carried out"""
    def __init__(self,
                 exp,
                 ori_err=None):
        super().__init__(exp)
        self.ori_err = ori_err


class ConflictError(Exception):
    "duplicate options are present in a single config"
    pass


#____________________________________________________________
# miscellaneous exceptions
class LeadershipError(Exception):
    "problem with a leadership's value length"
    pass


class ConstError(TypeError):
    "no uniq value in _NameSpace"
    pass


class _CommonError:
    def __init__(self,
                 val,
                 display_type,
                 opt,
                 err_msg,
                 index):
        self.val = val
        self.display_type = display_type
        self.opt = weakref.ref(opt)
        self.name = opt.impl_get_display_name()
        self.err_msg = err_msg
        self.index = index
        super().__init__(self.err_msg)

    def __str__(self):
        try:
            msg = self.prefix
        except AttributeError:
            self.prefix = self.tmpl.format(self.val,
                                           self.display_type,
                                           self.name)
            msg = self.prefix
        if self.err_msg:
            if msg:
                msg += ', {}'.format(self.err_msg)
            else:
                msg = self.err_msg
        if not msg:
            msg = _('invalid value')
        return msg


class ValueWarning(_CommonError, UserWarning):
    tmpl = _('attention, "{0}" could be an invalid {1} for "{2}"')

    def __init__(self, *args, **kwargs):
        if len(args) == 1 and not kwargs:
            self.msg = args[0]
            pass
        else:
            super().__init__(*args, **kwargs)
            self.msg = None

    def __str__(self):
        if self.msg is None:
            return super().__str__()
        return self.msg


class ValueOptionError(_CommonError, ValueError):
    tmpl = _('"{0}" is an invalid {1} for "{2}"')


class ValueErrorWarning(ValueWarning):
    tmpl = _('"{0}" is an invalid {1} for "{2}"')


class APIError(Exception):
    pass