# -*- coding: utf-8 -*-
"""
@author: Jan Bot
@licence: The MIT License (MIT)
@Copyright (c) 2016, Jan Bot
"""

# Python imports
import socket
import time

from . import jobid


class TokenModifier:
    """(semi)Abstract class for token modifiers
    """

    def __init__(self, timeout=86400):
        self.timeout = timeout

    def lock(self, *args, **kwargs):
        """Set the token to locked state"""
        raise NotImplementedError("Lock function not implemented.")

    def unlock(self, *args, **kwargs):
        """Set the token to unlocked state"""
        raise NotImplementedError("Unlock functin not implemented.")

    def close(self, *args, **kwargs):
        """Set the token to closed state"""
        raise NotImplementedError("Close function not implemented.")

    def unclose(self, *args, **kwargs):
        """Set the token to not closed state"""
        raise NotImplementedError("Unclose function not implemented.")

    def add_output(self, *args, **kwargs):
        """Add output to the token"""
        raise NotImplementedError("Add_output function not implemented.")

    def scrub(self, *args, **kwargs):
        """Scrub the token"""
        raise NotImplementedError("Scrub function not implemented.")

    def set_error(self, *args, **kwargs):
        """Set the token to error state"""
        raise NotImplementedError("set_error function not implemented.")


class BasicTokenModifier(TokenModifier):

    """Class to manage token modifications with.
    """

    def __init__(self):
        pass

    def lock(self, token):
        """Function which modifies the token such that it is locked.
        @param key: the key generated by the couchdb view.
        @param token: the token content.
        @return: modified token.
        """

        lock_content = {
            'hostname': socket.gethostname(),
            'lock': int(time.time()),
        }

        # try to include job id if present
        jobid.add_job_id(token)

        token.update(lock_content)
        return token

    def unlock(self, token):
        """Reset the token to its unlocked state.
        @param key: the key generated by the couchdb view.
        @param token: the token content.
        @return: modified token.
        """
        lock_content = {
            'hostname': socket.gethostname(),
            'lock': 0
        }
        jobid.remove_job_id(token)

        token.update(lock_content)
        return token

    def close(self, token):
        """Function which modifies the token such that it is closed for ever
        to the view that has supplied it.
        @param key: the key generated by the couchdb view.
        @param token: the token content.
        @return: modified token.
        """
        done_content = {
            'done': int(time.time())
        }
        token.update(done_content)
        return token

    def unclose(self, token):
        """Reset the token to be fetched again.
        @param key: the key generated by the CouchDB view.
        @param token: the token content.
        @return: modified token.
        """
        done_content = {
            'done': 0
        }
        token.update(done_content)
        jobid.remove_job_id(token)
        return token

    def add_output(self, token, output):
        """Add the output of the RunActor to the token.
        @param key: the key generated by the CouchDB view.
        @param token: the token content.
        @param output: The output to be included in the token.
        @return: modified token.
        """
        token.update(output)
        return token

    def scrub(self, token):
        """Token scrubber: makes sure a token can be handed out again.
        @param token: the token that needs to be scrubbed.
        @return the scrubbed token. Should be uploaded to the server to finish
         the process.
        """
        if 'scrub_count' not in token:
            token['scrub_count'] = 0
        token['scrub_count'] += 1
        token = self.unlock(token)
        return token

    def set_error(self, token):
        token['lock'] = 99
        token['done'] = 99
        return token


class NestedTokenModifier(TokenModifier):
    """Nested token modifier class
    """

    def __init__(self, timeout=86400):
        self.timeout = timeout
        self.tokenmodifier = BasicTokenModifier()

    def _get_token_from_list(self, ref, record):
        token = record
        for k in ref[1:]:
            token = token[k]
        return token

    def _get_token_from_value(self, ref, record):
        return record[ref]

    def _get_token(self, ref, record):
        if isinstance(ref, list):
            return self._get_token_from_list(ref, record)
        return self._get_token_from_value(ref, record)

    def get_token(self, ref, record):
        """Get a nested token with a reference"""
        return self._get_token(ref, record)

    def _update_record(self, ref, record, token):
        r = record
        if isinstance(ref, list):
            for k in ref[1:-1]:
                r = r[k]
            r[ref[-1]] = token
        else:
            record[ref] = token
        return record

    def lock(self, ref, record):
        token = self._get_token(ref, record)
        token = self.tokenmodifier.lock(token)
        return self._update_record(ref, record, token)

    def unlock(self, ref, record):
        token = self._get_token(ref, record)
        token = self.tokenmodifier.unlock(token)
        return self._update_record(ref, record, token)

    def close(self, ref, record):
        token = self._get_token(ref, record)
        token = self.tokenmodifier.close(token)
        return self._update_record(ref, record, token)

    def unclose(self, ref, record):
        token = self._get_token(ref, record)
        token = self.tokenmodifier.unclose(token)
        return self._update_record(ref, record, token)

    def add_output(self, ref, record, output):
        token = self._get_token(ref, record)
        token = self.tokenmodifier.add_output(token, output)
        return self._update_record(ref, record, token)

    def scrub(self, ref, record):
        token = self._get_token(ref, record)
        token = self.tokenmodifier.scrub(token)
        return self._update_record(ref, record, token)

    def set_error(self, ref, record):
        token = self._get_token(ref, record)
        token = self.tokenmodifier.set_error(token)
        return self._update_record(ref, record, token)
