# Copyright (c) 2014, The MITRE Corporation. All rights reserved.
# See LICENSE.txt for complete terms.

import stix
import stix.utils
from stix.common import Identity, InformationSource, StructuredText, VocabString
import stix.extensions.identity as ext_identity
import stix.bindings.indicator as indicator_binding
from cybox.core import Observable, ObservableComposition
from cybox.common import Time


class IndicatorType(VocabString):
    _namespace = 'http://stix.mitre.org/default_vocabularies-1'
    _XSI_TYPE = 'stixVocabs:IndicatorTypeVocab-1.0'


class Indicator(stix.Entity):
    _binding = indicator_binding
    _namespace = 'http://stix.mitre.org/Indicator-2'
    _version = "2.1"

    def __init__(self, id_=None, title=None, description=None, indicator_types=None, producer=None, observables=None):
        self.id_ = id_ or stix.utils.create_id("indicator")
        self.version = self._version
        self.producer = producer
        self.observables = observables
        self.title = title
        self.description = description
        self.indicator_types = indicator_types
       
    @property
    def description(self):
        return self._description

    @description.setter
    def description(self, value):
        if value:
            if isinstance(value, StructuredText):
                self._description = value
            else:
                self._description = StructuredText(value=value)
        else:
            self._description = None

    @property
    def producer(self):
        return self._producer

    @producer.setter
    def producer(self, value):
        if value and not isinstance(value, InformationSource):
            raise ValueError('value must be instance of InformationSource')

        self._producer = value

    @property
    def observables(self):
        return self._observables

    @observables.setter
    def observables(self, valuelist):
        self._observables = [] # initialize the variable

        if valuelist:
            for value in valuelist:
                self.add_observable(value)

    @property
    def indicator_types(self):
        return self._indicator_types

    @indicator_types.setter
    def indicator_types(self, value):
        self._indicator_types = []
        if not value:
            return
        elif isinstance(value, list):
            for v in value:
                self.add_indicator_type(v)
        else:
            self.add_indicator_type(value)

    def add_indicator_type(self, value):
        if not value:
            return
        elif isinstance(value, IndicatorType):
            self.indicator_types.append(value)
        else:
            tmp_indicator_type = IndicatorType(value=value)
            self.indicator_types.append(tmp_indicator_type)

    def set_producer_identity(self, identity):
        '''
        Sets the name of the producer of this indicator.
        The identity param can be a string (name) or an Identity
        instance.
        '''
        if not self.producer:
            self.producer = InformationSource()

        if isinstance(identity, Identity):
            self.producer.identity = identity
        else:
            if not self.producer.identity:
                self.producer.identity = Identity()

            self.producer.identity.name = identity # assume it's a string

    def set_produced_time(self, produced_time):
        '''The produced date variable must be in ISO 8601 format'''
        if not self.producer.time:
            self.producer.time = Time()

        self.producer.time.produced_time = produced_time

    def get_produced_time(self):
        if self.producer and self.producer.time:
            return self.producer.time.produced_time
        else:
            return None    

    def set_received_time(self, received_time):
        '''Set the time when this indicator was received'''
        if not self.producer.time:
            self.producer.time = Time()

        self.producer.time.received_time = received_time

    def get_received_time(self):
        '''Return the time when this indicator was received'''
        if self.producer and self.producer.time:
            return self.producer.time.received_time
        else:
            return None


    def add_observable(self, observable):
        ''' Adds an observable to the Indicator. If the number of observables associated with this indicator 
            is greater than one, the indicator will nest all of its observables under a parent observable
            composition, with an logical operator of 'OR'. If this is not ideal, an separate indicator
            should be made for each observable'''

        if not isinstance(observable, Observable):
            raise ValueError('observable must be instance of Observable')

        self.observables.append(observable)

    def _merge_observables(self, observables, operator='AND'):
        observable_composition = ObservableComposition()
        observable_composition.operator = operator

        for observable_ in observables:
            observable_composition.add(observable_)

        root_observable = Observable()
        root_observable.observable_composition = observable_composition

        return root_observable

    def add_object(self, object_):
        ''' The object paramter is wrapped in an observable and attached to the indicator. The object must be a 
            cybox.core.DefinedObject instance'''

        observable = Observable(object_)
        self.add_observable(observable)

    def to_obj(self, return_obj=None):
        if not return_obj:
            return_obj = self._binding.IndicatorType()

        if self.id_:
            return_obj.set_id(self.id_)

        if self.version:
            return_obj.set_version(self._version)

        if self.description:
            return_obj.set_Description(self.description.to_obj())

        if self.indicator_types:
            for indicator_type in self.indicator_types:
                tmp_indicator_type = indicator_type.to_obj()
                return_obj.add_Type(tmp_indicator_type)

        return_obj.set_Title(self.title)

        if self.observables:
            if len(self.observables) > 1:
                root_observable = self._merge_observables(self.observables)
            else:
                root_observable = self.observables[0]

            return_obj.set_Observable(root_observable.to_obj())

        if self.producer:
            return_obj.set_Producer(self.producer.to_obj())

        return return_obj

    @classmethod
    def from_obj(cls, obj, return_obj=None):        
        if not obj:
            return None

        if not return_obj:
            return_obj = cls()

        return_obj.id_              = obj.get_id()
        return_obj.title            = obj.get_Title()
        return_obj.description      = StructuredText.from_obj(obj.get_Description())
        return_obj.producer         = InformationSource.from_obj(obj.get_Producer())

        if obj.get_version():
            return_obj.version = obj.get_version()

        if obj.get_Type():
            for indicator_type in obj.get_Type():
                return_obj.add_indicator_type(IndicatorType.from_obj(indicator_type)) 

        if obj.get_Observable():
            observable_obj = obj.get_Observable()
            observable = Observable.from_obj(observable_obj)
            return_obj.observables.append(observable)

        return return_obj

    def to_dict(self, return_dict=None):
        if not return_dict:
            return_dict = {}

        if self.id_:
            return_dict['id'] = self.id_

        if self.version:
            return_dict['version'] = self.version

        if self.observables:
            if len(self.observables) == 1:
                return_dict['observable'] = self.observables[0].to_dict()
            else:
                composite_observable = self._merge_observables(self.observables)
                return_dict['observable'] = composite_observable.to_dict()

        if self.producer:
            return_dict['producer'] = self.producer.to_dict()

        if self.title:
            return_dict['title'] = self.title

        if self.description:
            return_dict['description'] = self.description.to_dict()

        if self.indicator_types:
            return_dict['indicator_types'] = [x.to_dict() for x in self.indicator_types]

        return return_dict


    @classmethod
    def from_dict(cls, dict_repr, return_obj=None):
        if not dict_repr:
            return None

        if not return_obj:
            return_obj = cls()

        return_obj.id_      = dict_repr.get('id')
        return_obj.title    = dict_repr.get('title')
        return_obj.version  = dict_repr.get('version', cls._version)
        observable_dict     = dict_repr.get('observable')
        producer_dict       = dict_repr.get('producer')
        description_dict    = dict_repr.get('description')
        indicator_type_list = dict_repr.get('indicator_types')

        if observable_dict:
            return_obj.add_observable(Observable.from_dict(observable_dict))

        if producer_dict:
            return_obj.producer = InformationSource.from_dict(producer_dict)

        if description_dict:
            return_obj.description = StructuredText.from_dict(description_dict)

        if indicator_type_list:
            for indicator_type_dict in indicator_type_list:
                return_obj.add_indicator_type(IndicatorType.from_dict(indicator_type_dict))

        return return_obj




