#-*- coding: utf-8

import odml.base as base
import odml.format as format
import odml.mapping as mapping
import odml.value as odml_value
import odml
from odml.tools.doc_inherit import inherit_docstring, allow_inherit_docstring


class Property(base._baseobj):
    pass


@allow_inherit_docstring
class BaseProperty(base.baseobject, mapping.mapableProperty, Property):
    """An odML Property"""
    _format = format.Property

    def __init__(self, name, value, definition=None, dependency=None, dependency_value=None, mapping=None):
        """
        Create a new Property with one single or multiple values. If something is passed as value that
        is not a Value object, the method will try to infer the values dtype from the type of the
        parameter.

        Example for a property with a single value
        >>> Property("property1", odml.Value(2)) #or
        >>> Property("property1", 2)

        Example for a property with multiple values
        >>> Property("property2", [odml.Value(data=1), odml.Value(data=2)]) #or
        >>> Property("property2", [1, 2])

        :param name: The mane of the property
        :param value: Either a Value or some type a Value can be created from or a list of values.
        :param definition: The definition of the property.
        :param dependency: Another property this property depends on.
        :param dependency_value: Dependency on a certain value.
        :param mapping: Mapping information of the property.
        """
        #TODO doc description for arguments
        #TODO validate arguments
        self._name = name
        self._section = None
        self._reset_values()

        self.definition = definition
        self.dependency = dependency
        self.dependency_value = dependency_value
        self._mapping = mapping

        if isinstance(value, list):
            for v in value:
                if not isinstance(v, odml_value.Value):
                    v = odml.Value(v)
                self.append(v)
        elif value is not None:
            self.append(value)

        # getter and setter methods are omnitted for now, but they can easily
        # be introduced later using python-properties

    #odML "native" properties
    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_value):
        self._name = new_value

    def __repr__(self):
        return "<Property %s>" % self._name


    # API (public)
    #
    #  properties
    @property
    def parent(self):
        """the section containing this property"""
        return self._section

    @property
    def values(self):
        """returns the list of values for this property"""
        return self._values

    @values.setter
    def values(self, new_values):
        # TODO for consistency this actually needs to manually remove each existing value
        self._reset_values()
        for i in new_values:
            self.append(i)

    @property
    def value(self):
        """
        returns the value of this property (or list if multiple values are present)

        use :py:meth:`odml.property.BaseProperty.values` to always return the list
        """
        if len(self._values) == 1:
            return self._values[0]

        #create a copy of the list, so mutations in there won’t affect us:
        return self._values[:]

    @value.setter
    def value(self, new_value):
        self._reset_values()
        self.append(new_value)

    def append(self, value):
        """
        adds a value to the list of values

        If *value* is not an odml.Value instance, such an instance will be created
        given the addition function arguments (see :ref:`__init__` for their description).

        If *value* is not an odml.Value instance and *unit*, *dtype* or *uncertainty* are
        missing, the values will be copied from the last value in this properties
        value-list if *copy_attributes* is True. If there is no value present to be
        copied from, an IndexError will be raised.
        """
        if not isinstance(value, odml_value.Value):
            value = odml.Value(value)
        self._values.append(value)
        value._property = self

    def remove(self, value):
        """
        Remove a value from this property and unset its parent.

        Raises a TypeError if this would cause the property not to hold any value at all.
        This can be circumvented by using the *_values* property.
        """
        if len(self._values) == 1:
            raise TypeError("Cannot remove %s from %s. A property must always have at least one value." % (repr(value), repr(self)))
        self._values.remove(value)
        value._property = None

    @inherit_docstring
    def reorder(self, new_index):
        return self._reorder(self.parent.properties, new_index)

    def __len__(self):
        return len(self._values)

    def __iter__(self):
        return self._values.__iter__()

    def get_path(self):
        """return the absolute path to this object"""
        return self.parent.get_path() + ":" + self.name

    def clone(self, children=True):
        """
        clone this object recursively allowing to copy it independently
        to another document
        """
        obj = super(BaseProperty, self).clone(children)
        obj._section = None

        obj._reset_values()
        if children:
            for v in self._values:
                obj.append(v.clone())

        return obj

    def _reset_values(self):
        """
        reinitialize the list of values with an empty list
        """
        self._values = base.SafeList()

    def merge(self, property):
        """stub that doesn't do anything for this class"""
        pass

    def unmerge(self, property):
        """stub that doesn't do anything for this class"""
        pass

    def get_merged_equivalent(self):
        """
        return the merged object (i.e. if the section is linked to another one,
        return the corresponding property of the linked section) or None
        """
        if self._section._merged is None: return None
        return self._section._merged.contains(self)

    @inherit_docstring
    def get_terminology_equivalent(self):
        if self._section is None: return None
        sec = self._section.get_terminology_equivalent()
        if sec is None: return None
        try:
            return sec.properties[self.name]
        except KeyError:
            return None
