#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Base builder class for Rebulk
"""
from abc import ABCMeta, abstractmethod
from contextlib import contextmanager
from copy import deepcopy
from logging import getLogger

from .loose import set_defaults
from .pattern import RePattern, StringPattern, FunctionalPattern

log = getLogger(__name__).log


@contextmanager
def overrides(kwargs):
    """
    Implements override kwarg to restore initial kwarg arguments from overrides list after set_defaults calls.
    :param kwargs:
    :return:
    """
    override_keys = kwargs.pop('overrides', None)
    backup = {}
    if override_keys:
        for override_key in override_keys:
            backup[override_key] = kwargs[override_key]

    yield backup

    kwargs.update(backup)


class Builder(metaclass=ABCMeta):
    """
    Base builder class for patterns
    """

    def __init__(self):
        self._defaults = {}
        self._regex_defaults = {}
        self._string_defaults = {}
        self._functional_defaults = {}
        self._chain_defaults = {}

    def reset(self):
        """
        Reset all defaults.

        :return:
        """
        self.__init__()  # pylint: disable=unnecessary-dunder-call

    def defaults(self, **kwargs):
        """
        Define default keyword arguments for all patterns
        :param kwargs:
        :type kwargs:
        :return:
        :rtype:
        """
        set_defaults(kwargs, self._defaults, override=True)
        return self

    def regex_defaults(self, **kwargs):
        """
        Define default keyword arguments for functional patterns.
        :param kwargs:
        :type kwargs:
        :return:
        :rtype:
        """
        set_defaults(kwargs, self._regex_defaults, override=True)
        return self

    def string_defaults(self, **kwargs):
        """
        Define default keyword arguments for string patterns.
        :param kwargs:
        :type kwargs:
        :return:
        :rtype:
        """
        set_defaults(kwargs, self._string_defaults, override=True)
        return self

    def functional_defaults(self, **kwargs):
        """
        Define default keyword arguments for functional patterns.
        :param kwargs:
        :type kwargs:
        :return:
        :rtype:
        """
        set_defaults(kwargs, self._functional_defaults, override=True)
        return self

    def chain_defaults(self, **kwargs):
        """
        Define default keyword arguments for patterns chain.
        :param kwargs:
        :type kwargs:
        :return:
        :rtype:
        """
        set_defaults(kwargs, self._chain_defaults, override=True)
        return self

    def build_re(self, *pattern, **kwargs):
        """
        Builds a new regular expression pattern

        :param pattern:
        :type pattern:
        :param kwargs:
        :type kwargs:
        :return:
        :rtype:
        """
        with overrides(kwargs):
            set_defaults(self._regex_defaults, kwargs)
            set_defaults(self._defaults, kwargs)

        return RePattern(*pattern, **kwargs)

    def build_string(self, *pattern, **kwargs):
        """
        Builds a new string pattern

        :param pattern:
        :type pattern:
        :param kwargs:
        :type kwargs:
        :return:
        :rtype:
        """
        with overrides(kwargs):
            set_defaults(self._string_defaults, kwargs)
            set_defaults(self._defaults, kwargs)

        return StringPattern(*pattern, **kwargs)

    def build_functional(self, *pattern, **kwargs):
        """
        Builds a new functional pattern

        :param pattern:
        :type pattern:
        :param kwargs:
        :type kwargs:
        :return:
        :rtype:
        """
        with overrides(kwargs):
            set_defaults(self._functional_defaults, kwargs)
            set_defaults(self._defaults, kwargs)

        return FunctionalPattern(*pattern, **kwargs)

    def build_chain(self, **kwargs):
        """
        Builds a new patterns chain

        :param pattern:
        :type pattern:
        :param kwargs:
        :type kwargs:
        :return:
        :rtype:
        """
        from .chain import Chain  # pylint:disable=import-outside-toplevel,cyclic-import

        with overrides(kwargs):
            set_defaults(self._chain_defaults, kwargs)
            set_defaults(self._defaults, kwargs)

        chain = Chain(self, **kwargs)
        chain._defaults = deepcopy(self._defaults)  # pylint: disable=protected-access
        chain._regex_defaults = deepcopy(self._regex_defaults)  # pylint: disable=protected-access
        chain._functional_defaults = deepcopy(self._functional_defaults)  # pylint: disable=protected-access
        chain._string_defaults = deepcopy(self._string_defaults)  # pylint: disable=protected-access
        chain._chain_defaults = deepcopy(self._chain_defaults)  # pylint: disable=protected-access

        return chain

    @abstractmethod
    def pattern(self, *pattern):
        """
        Register a list of Pattern instance
        :param pattern:
        :return:
        """

    def regex(self, *pattern, **kwargs):
        """
        Add re pattern

        :param pattern:
        :type pattern:
        :return: self
        :rtype: Rebulk
        """
        return self.pattern(self.build_re(*pattern, **kwargs))

    def string(self, *pattern, **kwargs):
        """
        Add string pattern

        :param pattern:
        :type pattern:
        :return: self
        :rtype: Rebulk
        """
        return self.pattern(self.build_string(*pattern, **kwargs))

    def functional(self, *pattern, **kwargs):
        """
        Add functional pattern

        :param pattern:
        :type pattern:
        :return: self
        :rtype: Rebulk
        """
        functional = self.build_functional(*pattern, **kwargs)
        return self.pattern(functional)

    def chain(self, **kwargs):
        """
        Add patterns chain, using configuration of this rebulk

        :param pattern:
        :type pattern:
        :param kwargs:
        :type kwargs:
        :return:
        :rtype:
        """
        chain = self.build_chain(**kwargs)
        self.pattern(chain)
        return chain
