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

from enum import Enum
from collections import deque
from threading import Lock
from spoc import config as spoc_config
from spoc.flock import locked

class ActionItemType(Enum):
    IMAGE_DOWNLOAD = 1
    IMAGE_UNPACK = 2
    IMAGE_DELETE = 3
    APP_DOWNLOAD = 4
    APP_UNPACK = 5
    APP_INSTALL = 6
    APP_UPDATE = 7
    APP_UNINSTALL = 8
    APP_START = 9
    APP_STOP = 10

class ActionItem:
    def __init__(self, action_type, key, action):
        self.type = action_type
        self.key = key
        self.action = action
        self.units_total = 0
        self.units_done = 0

    def run(self):
        self.action(self)

class ActionAppQueue:
    def __init__(self, action):
        self.action = action
        self.queue = []
        self.exception = None
        self.index = 0

    def download_image(self, image):
        self.queue.append(ActionItem(ActionItemType.IMAGE_DOWNLOAD, image.name, image.download))
        self.queue.append(ActionItem(ActionItemType.IMAGE_UNPACK, image.name, image.unpack_downloaded))

    def delete_image(self, image):
        self.queue.append(ActionItem(ActionItemType.IMAGE_DELETE, image.name, image.delete))

    def install_app(self, app):
        self.queue.append(ActionItem(ActionItemType.APP_DOWNLOAD, app.name, app.download))
        self.queue.append(ActionItem(ActionItemType.APP_UNPACK, app.name, app.unpack_downloaded))
        self.queue.append(ActionItem(ActionItemType.APP_INSTALL, app.name, app.install))

    def update_app(self, app):
        self.queue.append(ActionItem(ActionItemType.APP_DOWNLOAD, app.name, app.download))
        self.queue.append(ActionItem(ActionItemType.APP_UNPACK, app.name, app.unpack_downloaded))
        self.queue.append(ActionItem(ActionItemType.APP_UPDATE, app.name, app.update))

    def uninstall_app(self, app):
        self.queue.append(ActionItem(ActionItemType.APP_UNINSTALL, app.name, app.uninstall))

    def start_app(self, app):
        self.queue.append(ActionItem(ActionItemType.APP_START, app.name, app.start))

    def stop_app(self, app):
        self.queue.append(ActionItem(ActionItemType.APP_STOP, app.name, app.stop))

    def process(self):
        for item in self.queue:
            self.index += 1
            item.run()

class ActionQueue:
    def __init__(self):
        self.actions = {}
        self.queue = deque()
        self.lock = Lock()
        self.is_running = False

    def get_actions(self):
        # Return copy of actions, so they can be read and traversed without state changes
        with self.lock:
            return self.actions.copy()

    def enqueue_action(self, app_name, action):
        # Enqueue action
        with self.lock:
            if app_name in self.actions:
                # If the app already has a pending action, reject any other actions
                return
            # Create empty queue to be populated with actions just before execution
            self.actions[app_name] = ActionAppQueue(action)
            self.queue.append(app_name)

    def process(self):
        # Main method for deferred queue processing called by WSGI close handler
        with self.lock:
            # If the queue is being processesd by another thread, allow this thread to be terminated
            if self.is_running:
                return
        self.process_actions()

    @locked(spoc_config.LOCK_FILE)
    def process_actions(self):
        while True:
            with self.lock:
                # Try to get an item from queue
                app_name = None
                if self.queue:
                    app_name = self.queue.popleft()
                # If there are no more queued items, unset the processing flag and allow the thread to be terminated
                if not app_name:
                    self.is_running = False
                    return
                # If there is an item to be processed, set processing flags and exit the lock
                self.is_running = True
                app_queue = self.actions[app_name]
            try:
                # Call the method passed in app_queue.action to populate the queue of the actual actions to be taken in relation to the current local repository state
                app_queue.action(app_name, app_queue)
                # Process the freshly populated queue of actions related to the particular app
                app_queue.process()
                # If the actions finished without errors, restore nominal state by deleting the item from action list
                self.clear_action(app_name)
            except (SystemExit, KeyboardInterrupt):
                raise
            except BaseException as e:
                # If the action failed, store the exception and leave it in the list for manual clearance
                with self.lock:
                    app_queue.exception = e

    def clear_action(self, app_name):
        # Restore nominal state by deleting the item from action list
        with self.lock:
            try:
                del self.actions[app_name]
            except KeyError:
                pass
