# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from logging import NullHandler, getLogger
from six import PY3, binary_type, string_types, text_type

from .core import Reportable

logger = getLogger(__name__)
logger.addHandler(NullHandler())

_visible_chars_table = dict.fromkeys(range(32))


def _is_unknown(value):
    return isinstance(value, text_type) and (not value or value.lower() == 'unknown')


class Property(Reportable):
    """Property class."""

    def __init__(self, name, default=None, private=False, description=None, delimiter=' / ', **kwargs):
        """Init method."""
        super(Property, self).__init__(name, description, **kwargs)
        self.default = default
        self.private = private
        # Used to detect duplicated values. e.g.: en / en or High@L4.0 / High@L4.0 or Progressive / Progressive
        self.delimiter = delimiter

    def extract_value(self, track, context):
        """Extract the property value from a given track."""
        names = self.name.split('.')
        value = track.get(names[0], {}).get(names[1]) if len(names) == 2 else track.get(self.name)
        if value is None:
            if self.default is None:
                return

            value = self.default

        if isinstance(value, string_types):
            if isinstance(value, binary_type):
                value = text_type(value)
            else:
                value = value.translate(_visible_chars_table).strip()
                if _is_unknown(value):
                    return
            value = self._deduplicate(value)

        result = self.handle(value, context)
        if result is not None and not _is_unknown(result):
            return result

    @classmethod
    def _deduplicate(cls, value):
        values = value.split(' / ')
        if len(values) == 2 and values[0] == values[1]:
            return values[0]
        return value

    def handle(self, value, context):
        """Return the value without any modification."""
        return value


class Configurable(Property):
    """Configurable property where values are in a config mapping."""

    def __init__(self, config, *args, **kwargs):
        """Init method."""
        super(Configurable, self).__init__(*args, **kwargs)
        self.mapping = getattr(config, self.__class__.__name__)

    @classmethod
    def _extract_key(cls, value):
        return text_type(value).upper()

    @classmethod
    def _extract_fallback_key(cls, value, key):
        pass

    def _lookup(self, key, context):
        result = self.mapping.get(key)
        if result is not None:
            result = getattr(result, context.get('profile') or 'default')
            return result if result != '__ignored__' else False

    def handle(self, value, context):
        """Return Variable or Constant."""
        key = self._extract_key(value)
        if key is False:
            return

        result = self._lookup(key, context)
        if result is False:
            return

        while not result and key:
            key = self._extract_fallback_key(value, key)
            result = self._lookup(key, context)
            if result is False:
                return

        if not result:
            self.report(value, context)

        return result


class MultiValue(Property):
    """Property with multiple values."""

    def __init__(self, prop=None, delimiter='/', single=False, handler=None, name=None, **kwargs):
        """Init method."""
        super(MultiValue, self).__init__(prop.name if prop else name, **kwargs)
        self.prop = prop
        self.delimiter = delimiter
        self.single = single
        self.handler = handler

    def handle(self, value, context):
        """Handle properties with multiple values."""
        values = (self._split(value[0], self.delimiter)
                  if len(value) == 1 else value) if isinstance(value, list) else self._split(value, self.delimiter)
        call = self.handler or self.prop.handle
        if len(values) > 1 and not self.single:
            return [call(item, context) if not _is_unknown(item) else None for item in values]

        return call(values[0], context)

    @classmethod
    def _split(cls, value, delimiter='/'):
        if value is None:
            return

        v = text_type(value)
        result = map(text_type.strip, v.split(delimiter))
        return list(result) if PY3 else result