"""STIX 2 Common Data Types and Properties"""

try:
    from collections import OrderedDict
except ImportError:
    from ordereddict import OrderedDict

from .base import _STIXBase
from .properties import (BooleanProperty, HashesProperty, IDProperty,
                         ListProperty, Property, ReferenceProperty,
                         SelectorProperty, StringProperty, TimestampProperty,
                         TypeProperty)
from .utils import NOW, get_dict


class ExternalReference(_STIXBase):
    _properties = OrderedDict()
    _properties.update([
        ('source_name', StringProperty(required=True)),
        ('description', StringProperty()),
        ('url', StringProperty()),
        ('hashes', HashesProperty()),
        ('external_id', StringProperty()),
    ])

    def _check_object_constraints(self):
        super(ExternalReference, self)._check_object_constraints()
        self._check_at_least_one_property(["description", "external_id", "url"])


class KillChainPhase(_STIXBase):
    _properties = OrderedDict()
    _properties.update([
        ('kill_chain_name', StringProperty(required=True)),
        ('phase_name', StringProperty(required=True)),
    ])


class GranularMarking(_STIXBase):
    _properties = OrderedDict()
    _properties.update([
        ('marking_ref', ReferenceProperty(required=True, type="marking-definition")),
        ('selectors', ListProperty(SelectorProperty, required=True)),
    ])


class TLPMarking(_STIXBase):
    # TODO: don't allow the creation of any other TLPMarkings than the ones below
    _type = 'tlp'
    _properties = OrderedDict()
    _properties.update([
        ('tlp', Property(required=True))
    ])


class StatementMarking(_STIXBase):
    _type = 'statement'
    _properties = OrderedDict()
    _properties.update([
        ('statement', StringProperty(required=True))
    ])

    def __init__(self, statement=None, **kwargs):
        # Allow statement as positional args.
        if statement and not kwargs.get('statement'):
            kwargs['statement'] = statement

        super(StatementMarking, self).__init__(**kwargs)


class MarkingProperty(Property):
    """Represent the marking objects in the `definition` property of
    marking-definition objects.
    """

    def clean(self, value):
        if type(value) in OBJ_MAP_MARKING.values():
            return value
        else:
            raise ValueError("must be a Statement, TLP Marking or a registered marking.")


class MarkingDefinition(_STIXBase):
    _type = 'marking-definition'
    _properties = OrderedDict()
    _properties.update([
        ('type', TypeProperty(_type)),
        ('id', IDProperty(_type)),
        ('created_by_ref', ReferenceProperty(type="identity")),
        ('created', TimestampProperty(default=lambda: NOW)),
        ('external_references', ListProperty(ExternalReference)),
        ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))),
        ('granular_markings', ListProperty(GranularMarking)),
        ('definition_type', StringProperty(required=True)),
        ('definition', MarkingProperty(required=True)),
    ])

    def __init__(self, **kwargs):
        if set(('definition_type', 'definition')).issubset(kwargs.keys()):
            # Create correct marking type object
            try:
                marking_type = OBJ_MAP_MARKING[kwargs['definition_type']]
            except KeyError:
                raise ValueError("definition_type must be a valid marking type")

            if not isinstance(kwargs['definition'], marking_type):
                defn = get_dict(kwargs['definition'])
                kwargs['definition'] = marking_type(**defn)

        super(MarkingDefinition, self).__init__(**kwargs)


def register_marking(new_marking):
    """Register a custom STIX Marking Definition type.
    """
    OBJ_MAP_MARKING[new_marking._type] = new_marking


OBJ_MAP_MARKING = {
    'tlp': TLPMarking,
    'statement': StatementMarking,
}

TLP_WHITE = MarkingDefinition(
    id="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
    created="2017-01-20T00:00:00.000Z",
    definition_type="tlp",
    definition=TLPMarking(tlp="white")
)

TLP_GREEN = MarkingDefinition(
    id="marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da",
    created="2017-01-20T00:00:00.000Z",
    definition_type="tlp",
    definition=TLPMarking(tlp="green")
)

TLP_AMBER = MarkingDefinition(
    id="marking-definition--f88d31f6-486f-44da-b317-01333bde0b82",
    created="2017-01-20T00:00:00.000Z",
    definition_type="tlp",
    definition=TLPMarking(tlp="amber")
)

TLP_RED = MarkingDefinition(
    id="marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed",
    created="2017-01-20T00:00:00.000Z",
    definition_type="tlp",
    definition=TLPMarking(tlp="red")
)

COMMON_PROPERTIES = OrderedDict()

COMMON_PROPERTIES.update([
    # 'type' and 'id' should be defined on each individual type
    ('created_by_ref', ReferenceProperty(type="identity")),
    ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
    ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
    ('revoked', BooleanProperty()),
    ('labels', ListProperty(StringProperty)),
    ('external_references', ListProperty(ExternalReference)),
    ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))),
    ('granular_markings', ListProperty(GranularMarking)),
])