# -*- coding: utf-8 -*-
# Copyright (c) 2020 Salvador E. Tropea
# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
from .registrable import RegFilter, Registrable, RegOutput
from .misc import IFILT_MECHANICAL, IFILT_VAR_RENAME, IFILT_ROT_FOOTPRINT
from .error import KiPlotConfigurationError
from .bom.columnlist import ColumnList
from .macros import macros, document  # noqa: F401
from . import log

logger = log.get_logger(__name__)
DEFAULT_EXCLUDE = [{'column': ColumnList.COL_REFERENCE, 'regex': '^TP[0-9]*'},
                   {'column': ColumnList.COL_REFERENCE, 'regex': '^FID'},
                   {'column': ColumnList.COL_PART, 'regex': 'mount.*hole'},
                   {'column': ColumnList.COL_PART, 'regex': 'solder.*bridge'},
                   {'column': ColumnList.COL_PART, 'regex': 'solder.*jump'},
                   {'column': ColumnList.COL_PART, 'regex': 'test.*point'},
                   {'column': ColumnList.COL_FP, 'regex': 'test.*point'},
                   {'column': ColumnList.COL_FP, 'regex': 'mount.*hole'},
                   {'column': ColumnList.COL_FP, 'regex': 'fiducial'},
                   ]


class DummyFilter(Registrable):
    """ A filter that allows all """
    def __init__(self):
        super().__init__()
        self.name = 'Dummy'
        self.type = 'dummy'
        self.comment = 'A filter that does nothing'

    def filter(self, comp):
        return True


class MultiFilter(Registrable):
    """ A filter containing a list of filters.
        They are applied in sequence. """
    def __init__(self, filters):
        super().__init__()
        self.name = ','.join([f.name for f in filters])
        self.type = ','.join([f.type for f in filters])
        self.comment = 'Multi-filter'
        self.filters = filters

    def filter(self, comp):
        for f in self.filters:
            if not f.filter(comp):
                return False
        return True


class NotFilter(Registrable):
    """ A filter that returns the inverted result """
    def __init__(self, filter):
        super().__init__()
        self.name = 'Not '+filter.name
        self.type = '!'+filter.type
        self.comment = filter.comment
        self._filter = filter

    def filter(self, comp):
        return not self._filter.filter(comp)


def apply_pre_transform(comps, filter):
    if filter:
        logger.debug('Applying transform filter `{}`'.format(filter.name))
        for c in comps:
            filter.filter(c)


def apply_exclude_filter(comps, filter):
    if filter:
        logger.debug('Applying filter `{}` to exclude'.format(filter.name))
        for c in comps:
            if c.included:
                c.included = filter.filter(c)


def reset_filters(comps):
    logger.debug('Filters reset')
    for c in comps:
        c.included = True
        c.fitted = True
        c.fixed = False
        c.back_up_fields()


def apply_fitted_filter(comps, filter):
    if filter:
        logger.debug('Applying filter `{}` to fitted'.format(filter.name))
        for c in comps:
            if c.fitted:
                c.fitted = filter.filter(c)


def apply_fixed_filter(comps, filter):
    if filter:
        logger.debug('Applying filter `{}` to fixed'.format(filter.name))
        for c in comps:
            if not c.fixed:
                c.fixed = filter.filter(c)


class BaseFilter(RegFilter):
    def __init__(self):
        super().__init__()
        self._unkown_is_error = True
        self._internal = False
        with document:
            self.name = ''
            """ Used to identify this particular filter definition """
            self.type = ''
            """ Type of filter """
            self.comment = ''
            """ A comment for documentation purposes """

    def config(self):
        super().config()
        if self.name[0] == '_' and not self._internal:
            raise KiPlotConfigurationError('Filter names starting with `_` are reserved ({})'.format(self.name))

    @staticmethod
    def _create_mechanical(name):
        o_tree = {'name': name}
        o_tree['type'] = 'generic'
        o_tree['comment'] = 'Internal default mechanical filter'
        o_tree['exclude_all_hash_ref'] = True
        o_tree['exclude_any'] = DEFAULT_EXCLUDE
        o_tree['exclude_virtual'] = True
        logger.debug('Creating internal filter: '+str(o_tree))
        return o_tree

    @staticmethod
    def _create_var_rename(name):
        o_tree = {'name': name}
        o_tree['type'] = 'var_rename'
        o_tree['comment'] = 'Internal default variant field renamer filter'
        logger.debug('Creating internal filter: '+str(o_tree))
        return o_tree

    @staticmethod
    def _create_rot_footprint(name):
        o_tree = {'name': name}
        o_tree['type'] = 'rot_footprint'
        o_tree['comment'] = 'Internal default footprint rotator'
        logger.debug('Creating internal filter: '+str(o_tree))
        return o_tree

    @staticmethod
    def _create_kibom_dnx(name):
        type = name[7:10]
        if len(name) > 11:
            subtype = name[11:]
        else:
            subtype = 'config'
        o_tree = {'name': name}
        o_tree['type'] = 'generic'
        o_tree['comment'] = 'Internal KiBoM '+type.upper()+' filter ('+subtype+')'
        o_tree['config_field'] = subtype
        o_tree['exclude_value'] = True
        o_tree['exclude_config'] = True
        o_tree['keys'] = type+'_list'
        if type[-1] == 'c':
            o_tree['invert'] = True
        logger.debug('Creating internal filter: '+str(o_tree))
        return o_tree

    @staticmethod
    def _create_internal_filter(name):
        if name == IFILT_MECHANICAL:
            tree = BaseFilter._create_mechanical(name)
        elif name.startswith('_kibom_dn') and len(name) >= 10:
            tree = BaseFilter._create_kibom_dnx(name)
        elif name == IFILT_VAR_RENAME:
            tree = BaseFilter._create_var_rename(name)
        elif name == IFILT_ROT_FOOTPRINT:
            tree = BaseFilter._create_rot_footprint(name)
        else:
            return None
        filter = RegFilter.get_class_for(tree['type'])()
        filter._internal = True
        filter.set_tree(tree)
        filter.config()
        RegOutput.add_filter(filter)
        return filter

    @staticmethod
    def solve_filter(names, target_name, default=None):
        """ Name can be:
            - A class, meaning we have to use a default.
            - A string, the name of a filter.
            - A list of strings, the name of 1 or more filters.
            If any of the names matches def_key we call creator asking to create the filter.
            If def_real is not None we pass this name to creator. """
        if isinstance(names, type):
            # Nothing specified, use the default
            if default is None:
                return None
            names = [default]
        elif isinstance(names, str):
            # User provided, but only one, make a list
            if names == '_none':
                return None
            names = [names]
        # Here we should have a list of strings
        filters = []
        for name in names:
            if not name:
                continue
            if isinstance(name, Registrable):
                # A filter already converted
                filters.append(name)
                continue
            if name[0] == '!':
                invert = True
                name = name[1:]
                # '!' => always False
                if not name:
                    filters.append(NotFilter(DummyFilter()))
                    continue
            else:
                invert = False
            # Is already defined?
            if RegOutput.is_filter(name):
                filter = RegOutput.get_filter(name)
            else:  # Nope, can be created?
                filter = BaseFilter._create_internal_filter(name)
                if filter is None:
                    raise KiPlotConfigurationError("Unknown filter `{}` used for `{}`".format(name, target_name))
            if invert:
                filters.append(NotFilter(filter))
            else:
                filters.append(filter)
        # Finished collecting filters
        if not filters:
            return DummyFilter()
        if len(filters) == 1:
            return filters[0]
        return MultiFilter(filters)
