# coding=utf-8
from __future__ import unicode_literals, print_function, absolute_import

import threading
import uuid

from collections import deque

import weakref


class EventLoop(object):
    def push(self, event):
        raise NotImplementedError

    def fetch(self):
        raise NotImplementedError

    def run_once(self):
        event = self.fetch()
        if not event:
            return False

        event_source = event.event_source
        for e in event_source.handler_set(event=event):
            self.push(e)

        return True

    def run_to_empty(self):
        while self.run_once():
            pass


class SimpleEventLoop(EventLoop):
    def __init__(self):
        self._deque = deque()

    def push(self, event):
        assert isinstance(event, Event)
        self._deque.append(event)

    def fetch(self):
        if len(self._deque) == 0:
            return None
        return self._deque.popleft()


class DistinctEventLoop(EventLoop):
    def __init__(self):
        self._deque = deque()
        self._set = set()

    def push(self, event):
        assert isinstance(event, Event)
        if event not in self._set:
            self._deque.append(event)
            self._set.add(event)

    def fetch(self):
        if len(self._deque) == 0:
            return None
        event = self._deque.popleft()
        self._set.remove(event)
        return event


class Event(object):

    __slots__ = 'event_source', '__trace_id'

    def __init__(self, event_source):
        assert isinstance(event_source, EventSource)
        self.event_source = event_source
        self.__trace_id = None

    def __eq__(self, other):
        return False

    def __unicode__(self):
        return '{}(event_source={})'.format(self.__class__.__name__, self.event_source)

    def short_description(self):
        return '{}(event_source={})'.format(self.__class__.__name__, id(self.event_source))

    @property
    def trace_id(self):
        if self.__trace_id is None:
            self.__trace_id = uuid.uuid4().hex
        return self.__trace_id

    @trace_id.setter
    def trace_id(self, value):
        self.__trace_id = value

    def to_json(self):
        raise NotImplementedError


class DataSourceChangeEvent(Event):
    __slots__ = 'pk', 'is_create', 'is_delete'

    def __init__(self, data_source, pk, is_create, is_delete):
        assert isinstance(data_source, DataSource)
        super(DataSourceChangeEvent, self).__init__(event_source=data_source)
        self.pk = pk
        self.is_create = is_create
        self.is_delete = is_delete

    def __hash__(self):
        return hash(self.pk)

    def __eq__(self, other):
        if not isinstance(other, DataSourceChangeEvent):
            return False
        if (self.event_source, self.pk, self.is_create, self.is_delete) != (
                other.event_source, other.pk, other.is_create, other.is_delete):
            return False
        return True

    def __unicode__(self):
        return '{}(event_source={}, pk={}, ...)'.format(
            self.__class__.__name__,
            self.event_source,
            self.pk,
        )

    def short_description(self):
        return '{}(event_source={}, pk={}, ...)'.format(
            self.__class__.__name__,
            self.event_source.id,
            self.pk,
        )

    def to_json(self):
        return {
            'type': self.__class__.__name__,
            'trace_id': self.trace_id,
            'pk': self.pk,
            'is_create': self.is_create,
            'is_delete': self.is_delete,
            'event_source_id': self.event_source.id,
        }


class DataRelationChangeEvent(Event):
    __slots__ = 'primary_pk', 'is_create', 'is_delete'

    def __init__(self, data_relation, primary_pk, is_create, is_delete):
        assert isinstance(data_relation, DataRelation)
        super(DataRelationChangeEvent, self).__init__(event_source=data_relation)
        self.primary_pk = primary_pk
        self.is_create = is_create
        self.is_delete = is_delete

    def __hash__(self):
        return hash(self.primary_pk)

    def __eq__(self, other):
        if not isinstance(other, DataRelationChangeEvent):
            return False
        if (self.event_source, self.primary_pk, self.is_create, self.is_delete) != (
                other.event_source, other.primary_pk, other.is_create, other.is_delete):
            return False
        return True

    def __unicode__(self):
        return '{}(event_source={}, primary_pk={}, ...)'.format(
            self.__class__.__name__,
            self.event_source,
            self.primary_pk,
        )

    def short_description(self):
        return '{}(event_source={}, primary_pk={}, ...)'.format(
            self.__class__.__name__,
            self.event_source.id,
            self.primary_pk,
        )

    def to_json(self):
        return {
            'type': self.__class__.__name__,
            'trace_id': self.trace_id,
            'primary_pk': self.primary_pk,
            'is_create': self.is_create,
            'is_delete': self.is_delete,
            'event_source_id': self.event_source.id,
        }


class EventSourceSet(object):
    def __init__(self):
        self._source_set = weakref.WeakSet()

    def _add(self, other):
        assert isinstance(other, EventSource)
        self._source_set.add(other)
        return self

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

    def __contains__(self, item):
        return item in self._source_set

    def __iter__(self):
        for s in self._source_set:
            yield s


class EventHandlerSet(object):
    def __init__(self, event_source):
        self._event_source = event_source
        self._handler_set = weakref.WeakSet()

    def __iadd__(self, other):
        assert isinstance(other, EventHandler)
        self._handler_set.add(other)
        other.event_source_set._add(self._event_source)
        return self

    def __call__(self, event):
        for h in self._handler_set:
            generated_events = h.process_event(event=event)
            if not generated_events:
                continue
            for ge in generated_events:
                yield ge

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

    def __contains__(self, item):
        return item in self._handler_set

    def __iter__(self):
        for h in self._handler_set:
            yield h


class EventSource(object):
    _lock = threading.Lock()
    _static_construction_counter = 0

    @classmethod
    def __get_and_increase_construction_counter(cls):
        with EventSource._lock:
            value = EventSource._static_construction_counter
            EventSource._static_construction_counter += 1
            return value

    def __init__(self):
        self.__event_source__handler_set = EventHandlerSet(event_source=self)
        self.__event_source__bound_objects = set()
        self.__id = self.__get_and_increase_construction_counter()

    @property
    def id(self):
        return self.__id

    @property
    def handler_set(self):
        return self.__event_source__handler_set

    @handler_set.setter
    def handler_set(self, value):
        assert value is self.__event_source__handler_set

    @property
    def bound_objects(self):
        return self.__event_source__bound_objects

    def _to_extra_json(self):
        raise NotImplementedError

    def to_json(self):
        d = {
            'id': self.id,
            'type': self.__class__.__name__,
        }
        d.update(**self._to_extra_json())
        return d


class EventHandler(object):
    __event_handler__source_set = None

    @property
    def event_source_set(self):
        if self.__event_handler__source_set is None:
            self.__event_handler__source_set = EventSourceSet()
        return self.__event_handler__source_set

    @event_source_set.setter
    def event_source_set(self, value):
        assert value is self.__event_handler__source_set

    def process_event(self, event):
        raise NotImplementedError


class DataSink(object):
    def __setitem__(self, key, value):
        raise NotImplementedError

    def __delitem__(self, key):
        raise NotImplementedError


class EmptyDataSink(DataSink):
    def __delitem__(self, key):
        pass

    def __setitem__(self, key, value):
        pass


class ConsoleDumpDataSink(DataSink):
    def __init__(self, name):
        self.name = name

    def __setitem__(self, key, value):
        print('{}.{}[{}] = ...'.format(self.__class__.__name__, self.name, key, value))

    def __delitem__(self, key):
        print('{}.{}[{}] deleted'.format(self.__class__.__name__, self.name, key))


class DataSource(EventSource):
    def __getitem__(self, key):
        raise NotImplementedError


class DataRelation(EventSource):
    __slots__ = 'primary_data_source', 'secondary_data_source'

    def __init__(self, primary_data_source, secondary_data_source):
        super(DataRelation, self).__init__()
        assert isinstance(primary_data_source, DataSource)
        assert isinstance(secondary_data_source, DataSource)

        self.primary_data_source = primary_data_source
        self.secondary_data_source = secondary_data_source

    def get_related_pk_list(self, primary_pk):
        raise NotImplementedError

    def get_reversed_related_pk_list(self, secondary_pk):
        raise NotImplementedError

    def _to_extra_json(self):
        raise NotImplementedError


class ReversedDataRelation(DataRelation):
    def __init__(self, rev):
        super(ReversedDataRelation, self).__init__(
            primary_data_source=rev.secondary_data_source,
            secondary_data_source=rev.primary_data_source,
        )
        assert isinstance(rev, DataRelation)
        self._rev = rev

    def get_related_pk_list(self, primary_pk):
        return self._rev.get_reversed_related_pk_list(secondary_pk=primary_pk)

    def get_reversed_related_pk_list(self, secondary_pk):
        return self._rev.get_related_pk_list(primary_pk=secondary_pk)

    def __repr__(self):
        return '{}({})'.format(
            self.__class__.__name__,
            self._rev,
        )

    def _to_extra_json(self):
        return {
            'reversed': self._rev.to_json(),
        }


class CombinedDataSource(DataSource, EventHandler):
    def __getitem__(self, key):
        raise NotImplementedError

    def process_event(self, event):
        raise NotImplementedError


class InheritedDataSource(DataSource):
    def get_parent(self):
        raise NotImplementedError


def _get_data_source_ancestor(data_source):
    while isinstance(data_source, InheritedDataSource):
        data_source = data_source.get_parent()
    assert isinstance(data_source, DataSource)
    return data_source


def _check_data_source_same_ancestor(ds1, ds2):
    as1 = _get_data_source_ancestor(ds1)
    as2 = _get_data_source_ancestor(ds2)
    if as1 is not as2:
        raise Exception(
            'Unmatched Data Source: {}({}), {}({})'.format(
                repr(ds1), repr(as1), repr(ds2), repr(as2),
            )
        )


class RelatedDataSource(CombinedDataSource, InheritedDataSource):
    def __init__(self, primary_data_source, secondary_data_source, data_relation):
        super(RelatedDataSource, self).__init__()

        assert isinstance(primary_data_source, DataSource)
        assert isinstance(secondary_data_source, DataSource)
        assert isinstance(data_relation, DataRelation)

        _check_data_source_same_ancestor(primary_data_source, data_relation.primary_data_source)
        _check_data_source_same_ancestor(secondary_data_source, data_relation.secondary_data_source)

        self.primary_data_source = primary_data_source
        self.secondary_data_source = secondary_data_source
        self.data_relation = data_relation

        self.secondary_data_source.handler_set += self
        self.data_relation.handler_set += self

    def get_parent(self):
        return self.primary_data_source

    def __getitem__(self, key):
        raise NotImplementedError

    def process_event(self, event):
        assert isinstance(event, Event)
        if event.event_source is self.secondary_data_source:
            assert isinstance(event, DataSourceChangeEvent)
            if event.is_create or event.is_delete:
                return
            for pk in self.data_relation.get_reversed_related_pk_list(secondary_pk=event.pk):
                yield DataSourceChangeEvent(
                    data_source=self,
                    pk=pk,
                    is_create=False,
                    is_delete=False,
                )
        elif event.event_source is self.data_relation:
            assert isinstance(event, DataRelationChangeEvent)
            yield DataSourceChangeEvent(
                data_source=self,
                pk=event.primary_pk,
                is_create=False,
                is_delete=False,
            )
        else:
            raise Exception('UNREACHABLE!!!')

    def __repr__(self):
        return '{}(primary={}, secondary={}, relation={})'.format(
            self.__class__.__name__,
            self.primary_data_source,
            self.secondary_data_source,
            self.data_relation,
        )

    def _to_extra_json(self):
        return {
            'primary': self.primary_data_source.to_json(),
            'secondary': self.secondary_data_source.to_json(),
            'relation': self.data_relation.to_json(),
        }


class GroupedDataSource(CombinedDataSource, InheritedDataSource):
    def __init__(self, *args, **kwargs):
        super(GroupedDataSource, self).__init__()

        all_ds = list(args) + kwargs.values()
        assert len(all_ds) > 0

        for ds in all_ds:
            assert isinstance(ds, DataSource)

        self._ancestor_data_source = _get_data_source_ancestor(all_ds[0])
        for ds in all_ds:
            _check_data_source_same_ancestor(self._ancestor_data_source, ds)

        self._args = args
        self._kwargs = kwargs

        for ds in all_ds:
            ds.handler_set += self

    def get_parent(self):
        return self._ancestor_data_source

    def __getitem__(self, key):
        raise NotImplementedError

    def process_event(self, event):
        assert isinstance(event, DataSourceChangeEvent)
        yield DataSourceChangeEvent(
            data_source=self,
            pk=event.pk,
            is_create=event.is_create,
            is_delete=event.is_delete,
        )

    def __repr__(self):
        return '{}{}'.format(
            self.__class__.__name__,
            (self._args, self._kwargs),
        )

    def _to_extra_json(self):
        return {
            'args': [
                arg.to_json()
                for arg
                in self._args
                ],
            'kwargs': {
                key: arg.to_json()
                for key, arg
                in self._kwargs.items()
                },
        }


class JoinedDataSource(GroupedDataSource):
    def __init__(self, primary_data_source, secondary_data_source, data_relation):
        related_data_source = RelatedDataSource(
            primary_data_source=primary_data_source,
            secondary_data_source=secondary_data_source,
            data_relation=data_relation,
        )
        super(JoinedDataSource, self).__init__(
            primary_data_source,
            related_data_source,
        )
        self._related_data_source = related_data_source

    def __repr__(self):
        return '{}(primary={}, secondary={}, relation={})'.format(
            self.__class__.__name__,
            self._related_data_source.primary_data_source,
            self._related_data_source.secondary_data_source,
            self._related_data_source.data_relation,
        )

    def _to_extra_json(self):
        return {
            'related': self._related_data_source.to_json(),
        }


class DataConnection(EventHandler):
    def __init__(self, data_source, data_sink):
        assert isinstance(data_source, DataSource)
        assert isinstance(data_sink, DataSink)
        self.data_source = data_source
        self.data_sink = data_sink

        self.data_source.handler_set += self

    def process_event(self, event):
        assert isinstance(event, DataSourceChangeEvent)
        key = event.pk

        try:
            value = self.data_source[key]
        except KeyError:
            del self.data_sink[key]
        else:
            self.data_sink[key] = value


class EmptyDataSource(InheritedDataSource, EventHandler):
    def __init__(self, data_source):
        super(EmptyDataSource, self).__init__()
        assert isinstance(data_source, DataSource)
        self._data_source = data_source

    def get_parent(self):
        return self._data_source

    def __getitem__(self, item):
        raise NotImplementedError

    def process_event(self, event):
        pass

    def __repr__(self):
        return '{}({})'.format(
            self.__class__.__name__,
            self._data_source,
        )

    def _to_extra_json(self):
        return {
            'arg': self._data_source.to_json(),
        }


class _EmptyDataSourceCreator(object):
    def __mod__(self, other):
        return EmptyDataSource(other)

    def __rmod__(self, other):
        return EmptyDataSource(other)


EMPTY_DATA_SOURCE = _EmptyDataSourceCreator()
