from threading import RLock

from smqtk.exceptions import NoClassificationError
from smqtk.representation.classification_element import ClassificationElement


__author__ = "paul.tunison@kitware.com"


class MemoryClassificationElement (ClassificationElement):
    """
    In-memory representation of classification results. This is represented with
    a python dictionary.
    """

    @classmethod
    def is_usable(cls):
        # No external dependencies
        return True

    def __init__(self, type_name, uuid):
        """
        Initialize a new in-memory classification element.

        :param type_name: Name of the type of classifier this classification was
            generated by.
        :type type_name: str

        :param uuid: Unique ID reference of the classification
        :type uuid: collections.Hashable

        """
        super(MemoryClassificationElement, self).__init__(type_name, uuid)

        # dictionary of classification labels and values
        #: :type: None | dict[collections.Hashable, float]
        self._c = None
        # Cannot be pickled. New lock initialized upon pickle/unpickle
        self._c_lock = RLock()

    def __getstate__(self):
        state = {
            'type': self.type_name,
            'uuid': self.uuid
        }
        with self._c_lock:
            state['c'] = self._c
        return state

    def __setstate__(self, state):
        self.type_name = state['type']
        self.uuid = state['uuid']
        self._c_lock = RLock()
        with self._c_lock:
            #: :type: None | dict[collections.Hashable, float]
            self._c = state['c']

    def get_config(self):
        """
        Return a JSON-compliant dictionary that could be passed to this class's
        ``from_config`` method to produce an instance with identical
        configuration.

        :return: JSON type compliant configuration dictionary.
        :rtype: dict

        """
        return {}

    def has_classifications(self):
        """
        :return: If this element has classification information set.
        :rtype: bool
        """
        with self._c_lock:
            return bool(self._c)

    def get_classification(self):
        """
        Get classification result map, returning a label-to-confidence dict.

        We do no place any guarantees on label value types as they may be
        represented in various forms (integers, strings, etc.).

        Confidence values are in the [0,1] range.

        :raises NoClassificationError: No classification labels/confidences yet
            set.

        :return: Label-to-confidence dictionary.
        :rtype: dict[collections.Hashable, float]

        """
        with self._c_lock:
            if self._c:
                return self._c
            else:
                raise NoClassificationError("No classification labels/values")

    def set_classification(self, m=None, **kwds):
        """
        Set the whole classification map for this element. This will strictly
        overwrite the entire label-confidence mapping (vs. updating it)

        Label/confidence values may either be provided via keyword arguments or
        by providing a dictionary mapping labels to confidence values.

        :param m: New labels-to-confidence mapping to set.
        :type m: dict[collections.Hashable, float]

        :raises ValueError: The given label-confidence map was empty.

        """
        m = super(MemoryClassificationElement, self)\
            .set_classification(m, **kwds)
        with self._c_lock:
            self._c = m
