#!/usr/bin/python

# -*- coding: utf-8 -*-

# For better print formatting
from __future__ import print_function

# Imports
from abc import abstractmethod
from enum import Enum


class Version:

    def __init__(self, data, version_id, timestamp):
        self._data = data
        self._id = version_id
        self._producer = None
        self._usages = []
        self._history = [[timestamp, "Registered Version" + self._id]]

    def get_id(self):
        return self._id

    def get_data(self):
        return self._data

    def get_rename(self):
        return "d" + self._data.get_id()+"v"+self._id

    def register_consumer(self, access, timestamp):
        self._usages.append(access)
        consumer = ""
        cause = access.get_cause()
        if cause is not None and cause.task is not None:
            consumer = "task " +cause.task.task_id
        else:
            consumer = "Main code"
        self._history.append([timestamp, "Registered Read access to " + self.get_rename() + " by " + consumer])

    def register_producer(self, access, timestamp):
        self._producer = access
        producer = ""
        cause = access.get_cause()
        if cause is not None and cause.task is not None:
            producer = "task " + cause.task.task_id
        else:
            producer = "Main code"
        self._history.append([timestamp, "Registered Write access to " + self.get_rename() + " by " + producer])

    def get_producer(self):
        if self._producer is not None:
            return self._producer.get_cause()
        else:
            return None

    def commit_read(self, access, timestamp):
        cause = access.get_cause()
        if cause is not None and cause.task is not None:
            producer = "task " + cause.task.task_id
        else:
            producer = "Main code"
        self._history.append([timestamp, "Completed read access to " + self.get_rename() + " by " + producer])

    def commit_write(self, access, timestamp):
        cause = access.get_cause()
        if cause is not None and cause.task is not None:
            producer = "task " + cause.task.task_id
        else:
            producer = "Main code"
        self._history.append([timestamp, "New value (" + self.get_rename() + ") generated by " + producer])

    def main_access_progress(self, state, timestamp):
        self._history.append([timestamp, "Main access " + state])

    def get_history(self):
        history = []
        history = history + self._history
        return sorted(history, key=lambda t: int(t[0]))

    def __str__(self):
        return self.get_rename()


class Data:
    def __init__(self, data_id, timestamp):
        self._id = data_id
        self._versions = []
        self.add_version(timestamp)
        self._history = []
        self._history.append([timestamp, "First data access"])

    def get_id(self):
        return self._id

    def get_current_version(self):
        return self._versions[-1]

    def get_all_versions(self):
        return self._versions

    def add_version(self, timestamp):
        version = Version(self, str(len(self._versions)+1), timestamp)
        self._versions.append(version)
        return version

    def get_last_writer(self):
        version = self._versions[-1]
        if version is not None:
            access = version.get_producer()
            return access
        return None

    def register_consumer(self, access, timestamp):
        if len(self._versions) == 0:
            v = self.add_version(timestamp)
        else:
            v = self._versions[-1]
        v.register_consumer(access, timestamp)
        return v

    def register_update(self, access, timestamp):
        v = self.add_version(timestamp)
        v.register_producer(access, timestamp)
        return v

    def get_history(self):
        history = []
        history = history + self._history
        for v in self.get_all_versions():
            history = history + v.get_history()
        return sorted(history, key=lambda t: int(t[0]))


class Access(object):

    def __init__(self, timestamp):
        self._cause = None

    @staticmethod
    def create_access(direction, timestamp):
        if direction.upper() == "IN" or direction.upper() == "R":
            return RAccess(timestamp)
        elif direction.upper() == "INOUT" or direction.upper() == "RW":
            return RWAccess(timestamp)
        else:  # direction.upper() == "OUT" or direction.upper() == "W":
            return WAccess(timestamp)

    def get_cause(self):
        return self._cause

    def set_cause(self, cause):
        self._cause = cause

    @abstractmethod
    def get_data(self):
        pass

    @abstractmethod
    def get_direction(self):
        pass

    @abstractmethod
    def get_dependence(self):
        pass

    @abstractmethod
    def committed(self, timestamp):
        pass


class RAccess(Access):
    def __init__(self, timestamp):
        super(RAccess, self).__init__(timestamp)
        self._read_version = None
        self._history = []

    def get_read_version(self, timestamp):
        return self._read_version

    def get_data(self):
        if self._read_version is not None:
            return self._read_version.get_data()
        else:
            return None

    def get_direction(self):
        return "IN"

    def get_dependence(self):
        if self._read_version is not None:
            producer = self._read_version.get_producer()
            if producer is not None:
                return producer.get_reason()
        return None

    def register_read(self, data, timestamp):
        self._read_version = data.register_consumer(self, timestamp)

    def committed(self, timestamp):
        if self._read_version is not None:
            self._read_version.commit_read(self, timestamp)

    def __str__(self):
        read_version = "?"
        if self._read_version is not None:
            read_version = self._read_version.get_rename()
        return read_version


class RWAccess(Access):
    def __init__(self, timestamp):
        super(RWAccess, self).__init__(timestamp)
        self._read_version = None
        self._written_version = None

    def get_read_version(self, timestamp):
        return self._read_version

    def get_written_version(self, timestamp):
        return self._written_version

    def set_written_version(self, version, timestamp):
        self._written_version = version

    def get_data(self):
        if self._read_version is not None:
            return self._read_version.get_data()
        elif self._written_version is not None:
            return self._written_version.get_data()
        else:
            return None

    def get_direction(self):
        return "INOUT"

    def get_dependence(self):
        if self._read_version is not None:
            producer = self._read_version.get_producer()
            if producer is not None:
                return producer.get_reason()
        return None

    def register_read(self, data, timestamp):
        self._read_version = data.register_consumer(self, timestamp)

    def register_write(self, data, timestamp):
        self._written_version = data.register_update(self, timestamp)

    def committed(self, timestamp):
        if self._read_version is not None:
            self._read_version.commit_read(self, timestamp)
        if self._written_version is not None:
            self._written_version.commit_write(self, timestamp)

    def __str__(self):
        read_version = "?"
        if self._read_version is not None:
            read_version = self._read_version.get_rename()
        written_version = "?"
        if self._written_version is not None:
            written_version = self._written_version.get_rename()
        return read_version + " -> " + written_version


class WAccess(Access):
    def __init__(self, timestamp):
        super(WAccess, self).__init__(timestamp)
        self._written_version = None

    def get_written_version(self, timestamp):
        return self._written_version

    def set_written_version(self, version, timestamp):
        self._written_version = version

    def get_data(self):
        if self._written_version is not None:
            return self._written_version.get_data()
        else:
            None

    def get_direction(self):
        return "OUT"

    def get_dependence(self):
        return None

    def register_write(self, data, timestamp):
        self._written_version = data.register_update(self, timestamp)

    def committed(self, timestamp):
        if self._written_version is not None:
            self._written_version.commit_write(self, timestamp)

    def __str__(self):
        written_version = "->?"
        if self._written_version is not None:
            written_version = "->"+self._written_version.get_rename()
        return written_version


class DataRegister:

    def __init__(self):
        self.data = {}  # value = Data
        self.last_registered_access = None

    def register_access(self, direction, timestamp):
        self.last_registered_access = Access.create_access(direction, timestamp)

    def register_data(self, data_id, timestamp):
        data = self.data.get(data_id)
        if data is None:
            data = Data(data_id, timestamp)
            self.data[data_id] = data
        return data

    def get_datum(self, data_id):
        return self.data.get(data_id)

    def get_data(self):
        return self.data.values()

    def __str__(self):
        string = ""
        for key, value in self.data.items():
            string = string + "\n * " + (str(key) + " -> "+str(value))

        return string



class MainDataAccessStatus(Enum):
    REQUESTED = 0
    EXISTENCE_AWARE = 1
    OBTAINED = 2


class MainDataAccess:
    def __init__(self, access, data, timestamp):
        self.access = access
        self.access.register_read(data, timestamp)
        self.state = MainDataAccessStatus.REQUESTED
        self.access.get_read_version(timestamp).main_access_progress("requested", timestamp)

    def exists(self, timestamp):
        self.state = MainDataAccessStatus.EXISTENCE_AWARE
        self.access.get_read_version(timestamp).main_access_progress("is aware of existence", timestamp)

    def obtained(self, timestamp):
        self.state = MainDataAccessStatus.OBTAINED
        self.access.get_read_version(timestamp).main_access_progress("has the value on the node", timestamp)

    def __str__(self):
        return  str(self.access) + " in state " + str(self.state)


class MainDataAccessRegister:

    def __init__(self):
        self.ongoing_accesses = {} # dataID = MainDataAccess
        self.accesses_count = 0
        self.completed_accesses_count = 0

    def register_access(self, access, datum, timestamp):
        main_access_description = MainDataAccess(access, datum, timestamp)
        self.ongoing_accesses[datum.get_id()] = main_access_description
        self.accesses_count = self.accesses_count + 1 

    def data_exists(self, data_id, timestamp):
        current_data_access = self.ongoing_accesses[data_id]
        if current_data_access is None:
            print("Available data value for unregistered access for data " + data_id)
        else:
            current_data_access.exists(timestamp)

    def data_obtained(self, data_id, timestamp):
        current_data_access = self.ongoing_accesses[data_id]
        if current_data_access is None:
            print("Available data value for unregistered access for data " + data_id)
        else:
            current_data_access.obtained(timestamp)
            del self.ongoing_accesses[data_id]
            self.completed_accesses_count = self.completed_accesses_count + 1

    def get_all_accesses_count(self):
        return self.accesses_count;

    def get_completed_accesses_count(self):
        return self.completed_accesses_count;

    def get_pending_accesses(self):
        return self.ongoing_accesses.values()
