"""
    historyBlock.py
    - Maintains history of each rows
    - Insert: inserts and update history
    Author: Divakar Lakhera
"""
import copy
import datetime
import logging


class historyBlock:
    def __init__(self):
        self.data = {0: {"isBegin": True}}
        self.top = 0
        self.current = {"isBegin": True}
        self.adjustment_factor = 0
        self.creation_time = self.get_time_internal()
        self.last_commit_time = ""
        self.is_locked = False

    def get_time_internal(self) -> str:
        timex = datetime.datetime.now()
        return timex.strftime("%Y-%m-%d %H:%M:%S")

    def get_diff_map(self, data) -> dict:
        keys = set(data.keys())
        keys_current = set(self.current.keys())
        changes = {"+": [], "-": [], "*": [], "p": []}
        #  + add  - remove
        new_keys = keys.difference(keys_current)
        removed_keys = keys_current.difference(keys)
        new_updates = []
        # keys that need update
        for key in keys:
            if key not in new_keys and key not in removed_keys and data[key] != self.current[key]:
                new_updates.append(key)
        for key in new_keys:
            changes["+"].append({key: data[key]})

        for key in removed_keys:
            changes["-"].append(key)
            changes["p"].append({key: self.current[key]})

        for key in new_updates:
            changes["*"].append({key: data[key]})
            changes["p"].append({key: self.current[key]})

        return changes

    def apply_diff(self, diff):
        # Remove
        for key in diff["-"]:
            self.current.pop(key)
        # Add new [{} ,{}]
        for element in diff["+"]:
            self.current[list(element.keys())[0]] = list(element.values())[0]
        # Update
        for element in diff["*"]:
            self.current[list(element.keys())[0]] = list(element.values())[0]

    def revert_diff(self, diff):
        # Remove will be add
        # Add will be remove
        # Revert back to previous values
        logging.info("dd :{}".format(diff))
        for entry in diff["+"]:
            self.current.pop(list(entry.keys())[0])
        for entry in diff["p"]:
            key = list(entry.keys())[0]
            value = list(entry.values())[0]
            self.current[key] = value
        return

    def insert(self, data) -> str:
        # Does ShallowCopy increase speed ?
        error = ""
        has_error = False
        try:
            self.is_locked = True
            # wait ?
            diffs = self.get_diff_map(data)
            self.data[self.top + 1] = copy.deepcopy(diffs)
            self.apply_diff(diffs)
            del diffs
            self.top += 1
            self.last_commit_time = self.get_time_internal()
            self.is_locked = False
        except Exception as e:
            logging.critical("Insert Failed: {}".format(e))
            error = "{}".format(e)
            has_error = True
        finally:
            if has_error:
                return error
            return "OK"

    def rollback(self, times):
        error = ""
        has_error = False
        removed = 0
        try:
            self.is_locked = True
            current_top = self.top
            new_top = max(0, (self.top - times))
            for i in range(current_top, new_top, -1):
                self.revert_diff(self.data[i])
                removed += 1

        except Exception as e:
            logging.critical("Rollback Failed: {}".format(e))
            error = "{}".format(e)
            has_error = True
        finally:
            self.top -= removed
            if has_error:
                return error
            return "OK"

    def print_good(self):
        print("<{}> ".format(self.creation_time), end="")
        for key in self.current.keys():
            print("{}:{}".format(key, self.current[key]), end=" | ")
        print()
