from uuid import uuid4
from .constants import RP_COMMON
from deepdiff import DeepDiff, Delta
import logging
import struct
try:
    import _pickle as pickle
except ImportError:
    import pickle

def object_classes(type_id):
    """
    """
    return _TYPE_MAP.get(type_id)


class ReplicationObject(object):
    __slots__ = [
        '_sha'
    ]

    def _serialize(self):
        """ Convert the object into chunks of bytes
        """
        raise NotImplementedError()

    def _deserialize(self, chunks):
        """ Load chunks of to object
        """
        raise NotImplementedError()

    @staticmethod
    def from_raw_chunks(chunks):
        """ Reconstruct a replication object from chunks
        """
        type_num = pickle.loads(chunks[0])
        obj = object_classes(type_num)()
        obj._deserialize(chunks)

        return obj

    def as_raw_chunks(self):
        return self._serialize()


class Commit(ReplicationObject):
    __slots__ = [
        'delta'     # deepdiff Delta
    ]

    type_num = 1

    def __init__(self):
        self.delta = None

    def _serialize(self):
        chunks = []
        chunks.append(self.type_num)
        chunks.append(self.delta.dumps())
        return chunks

    def _deserialize(self, chunks):
        self.delta = Delta(chunks[0])




class Node(ReplicationObject):
    __slots__ = [
        'uuid',             # uuid used as key      (string)
        'data',             # dcc data ref          (DCC type)
        'instance',         # raw data              (json)
        'dependencies',     # dependencies array    (string)
        'owner',            # Data owner            (string)
        'buffer',           # Serialized local buffer (bytes)
        'state',            # Node state            (int)
        'sender',           # Node sender origin (client uuid)
        'delta'
        ]

    type_num = 2

    def __init__(
            self,
            owner=None,
            instance=None,
            uuid=None,
            data=None,
            bytes=None,
            sender=None,
            dependencies=[], 
            state=-1):

        self.uuid = uuid if uuid else str(uuid4())
        self.owner = owner
        self.buffer = None
        self.state = state
        self.data = {}
        self.instance = instance
        self.data = data
        self.delta = bytes
        self.dependencies = dependencies
        self.sender = sender

    def _serialize(self):
        chunks = []
        chunks.append(pickle.dumps(self.type_num, protocol=4))
        chunks.append(self.uuid.encode())
        chunks.append(self.owner.encode())
        chunks.append(pickle.dumps(self.dependencies, protocol=4))
        chunks.append(pickle.dumps(self.data, protocol=4))

        return chunks

    def _deserialize(self, chunks):
        self.uuid = chunks[1].decode()
        self.owner = chunks[2].decode()
        self.dependencies = pickle.loads(chunks[3])
        self.data = pickle.loads(chunks[4])

    def patch(self, patch):
        self.data = self.data + patch

    def add_dependency(self, dependency):
        if not self.dependencies:
            self.dependencies = []
        if dependency not in self.dependencies:
            self.dependencies.append(dependency)

    def __repr__(self):
        return f" {self.uuid} - owner: {self.owner} - deps: {self.dependencies}"


class Command(ReplicationObject):
    type_num = 3

    def __init__(
            self,
            owner=None,
            data=None):
        self.owner = owner
        self.data = data
        self.str_type = type(self).__name__

    def _serialize(self):
        chunks = []
        chunks.append(pickle.dumps(self.type_num, protocol=4))
        chunks.append(self.owner.encode())
        chunks.append(pickle.dumps(self.data, protocol=4))

        return chunks

    def _deserialize(self, chunks):
        self.owner = chunks[1].decode()
        self.data = pickle.loads(chunks[2])

    def execute(self, graph):
        raise NotImplementedError()


class Delete(Command):
    type_num = 4

    def execute(self, graph):
        assert(self.data)

        if graph and self.data in graph.keys():
            # Clean all reference to this node
            for key, value in graph.items():
                if value.dependencies and self.data in value.dependencies:
                    value.dependencies.remove(self.data)
            # Remove the node itself
            del graph[self.data]


class Right(Command):
    type_num = 5

    def execute(self, graph):
        assert(self.data)

        if graph and self.data['uuid'] in graph.keys():
            graph[self.data['uuid']].owner = self.data['owner']


class Config(Command):
    type_num = 6
    pass


class Snapshot(Command):
    type_num = 7
    pass


class ServerSnapshot(Command):
    type_num = 8
    pass


class Auth(Command):
    type_num = 9
    pass


class Disconnect(Command):
    type_num = 10
    pass


class Kick(Command):
    type_num = 11
    pass


class UpdateClientsState(Command):
    type_num = 12
    pass


class UpdateUserMetadata(Command):
    type_num = 13
    pass

OBJECT_CLASSES = (
    Commit,
    Node,
    Command,
    UpdateUserMetadata,
    UpdateClientsState,
    Kick,
    Disconnect,
    Auth,
    ServerSnapshot,
    Snapshot,
    Config,
    Right,
    Delete
)

_TYPE_MAP = {}

for cls in OBJECT_CLASSES:
    _TYPE_MAP[cls.type_num] = cls
