
import json
from .context import ToJsonContext


class ATT:
    search_attr_literal = '#l'
    search_attr_int = '#i'
    group_attr = '#g'
    group_attr_literal = '#h'
    prop = '#p'
    fsprop = '#m'
    factor = '#f'
    special_key = '#k'


class SaasAttribute(object):
    __slots__ = ('name', 'type', 'value')

    def __init__(self, name, type, value):
        self.name = name
        self.type = type
        self.value = value

    def __str__(self):
        return json.dumps(self.to_json(ToJsonContext()), indent=4)

    def to_json(self, context):
        return {
            'value': context.serialize_text(self.value),
            'type': self.type
        }


class AttributesOwner(object):
    attributes = {}

    def add_attribute(self, attribute, attr_type=None, attr_value=None):
        """
        Add attribute to document; if attribute(s) with such name already exists, just appends new one
            Examples:
                doc.add_attribute('some_attr_name', ATT.some_type, some_attr_value)
                    or
                doc.add_attribute(SaasAttribute('some_attr_name', ATT.some_type, some_attr_value))
        :param attribute: attribute name or object
        :type attribute: str or SaasAttribute
        :param attr_type: one of ATT members
        :type attr_type: str
        """
        if isinstance(attribute, SaasAttribute):
            self.add_saas_attribute(attribute)
        else:
            if attr_type is None or attr_value is None:
                raise Exception('attribute value or type not defined')
            self.add_saas_attribute(SaasAttribute(attribute, attr_type, attr_value))

    def add_factor(self, name, value):
        self.add_attribute(SaasAttribute(name, ATT.factor, value))

    def add_property(self, name, value):
        self.add_attribute(SaasAttribute(name, ATT.prop, value))

    def set_attribute(self, attribute, attr_type=None, attr_value=None):
        """
        Set attribute for document; if attribute(s) with such name already exists, removes them
            Examples:
                doc.set_attribute('some_attr_name', ATT.some_type, some_attr_value)
                    or
                doc.set_attribute(SaasAttribute('some_attr_name', ATT.some_type, some_attr_value))
                    or
                doc.set_attribute([SaasAttribute('some_attr_name', ATT.some_type1, value1),
                                   SaasAttribute('some_attr_name', ATT.some_type2, value2)])
        :param attribute: attribute name or object or list of SaasAttribute objects with the same name
        :type attribute: str or SaasAttribute or list
        :param attr_type: one of ATT members
        :type attr_type: str
        """
        if isinstance(attribute, SaasAttribute) or isinstance(attribute, list):
            self.set_saas_attribute(attribute)
        else:
            if attr_type is None or attr_value is None:
                raise Exception('attribute value or type not defined')
            self.set_saas_attribute(SaasAttribute(attribute, attr_type, attr_value))

    def add_saas_attribute(self, attribute):
        if not isinstance(attribute, SaasAttribute):
            raise Exception('attribute must be instance of SaasAttribute here')
        if attribute.name in self.attributes:
            if attribute not in self.attributes[attribute.name]:
                self.attributes[attribute.name].append(attribute)
        else:
            self.set_saas_attribute(attribute)

    def set_saas_attribute(self, attribute):
        if isinstance(attribute, SaasAttribute):
            self.attributes[attribute.name] = [attribute]
        elif isinstance(attribute, list):
            if not attribute:
                raise Exception('attribute is empty')
            attr_names = list(set([attr.name for attr in attribute]))
            if len(attr_names) != 1:
                raise Exception('names in attribute must be the same')
            attr_name = attr_names[0]
            self.attributes[attr_name] = attribute
        else:
            raise Exception('incorrect attribute, %s' % attribute)

    def attributes_to_json(self, context):
        json_repr = {}
        for attr_name, attrs in self.attributes.items():
            if attrs:
                json_repr[attr_name] = [attr.to_json(context) for attr in attrs]
        return json_repr
