import collections
from ..wrappers import GitWrapper
from ..lock import Lock
from ..wrappers import GitWrapper
from ..events import DeployEvent


class Project(collections.MutableMapping):

    """A dictionary that applies an arbitrary key-altering
       function before accessing the keys"""

    def __init__(self, *args, **kwargs):
        self.store = dict()
        self.update(dict(*args, **kwargs))  # use the free update to set keys

    def __getitem__(self, key):
        return self.store[self.__keytransform__(key)]

    def __setitem__(self, key, value):
        self.store[self.__keytransform__(key)] = value

    def __delitem__(self, key):
        del self.store[self.__keytransform__(key)]

    def __iter__(self):
        return iter(self.store)

    def __len__(self):
        return len(self.store)

    def __keytransform__(self, key):
        return key

    def get_name(self):
        return self['url'].split('/')[-1].split('.git')[0]

    def passes_payload_filter(self, payload, action):

        # At least one filter must match
        for filter in self['payload-filter']:

            # All options specified in the filter must match
            for filter_key, filter_value in filter.items():

                # Ignore filters with value None (let them pass)
                if filter_value == None:
                    continue

                # Interpret dots in filter name as path notations 
                node_value = payload
                for node_key in filter_key.split('.'):

                    # If the path is not valid the filter does not match
                    if not node_key in node_value:
                        action.log_info("Filter '%s' does not match since the path is invalid" % (filter_key))

                        # Filter does not match, do not process this repo config
                        return False

                    node_value = node_value[node_key]

                if filter_value == node_value:
                    continue

                # If the filter value is set to True. the filter
                # will pass regardless of the actual value
                if filter_value == True:
                    continue

                action.log_debug("Filter '%s' does not match ('%s' != '%s')" % (filter_key, filter_value, (str(node_value)[:75] + '..') if len(str(node_value)) > 75 else str(node_value)))

                # Filter does not match, do not process this repo config
                return False

        # Filter does match, proceed
        return True

    def passes_header_filter(self, request_headers):

        # At least one filter must match
        for key in self['header-filter']:

            # Verify that the request has the required header attribute
            if key.lower() not in request_headers:
                return False

            # "True" indicates that any header value is accepted
            if self['header-filter'][key] is True:
                continue

            # Verify that the request has the required header value
            if self['header-filter'][key] != request_headers[key.lower()]:
                return False

        # Filter does match, proceed
        return True

    def apply_filters(self, request_headers, request_body, action):
        """Verify that the suggested repositories has matching settings and
        issue git pull and/or deploy commands."""
        import os
        import time
        import json

        payload = json.loads(request_body)

        # Verify that all payload filters matches the request (if any payload filters are specified)
        if 'payload-filter' in self and not self.passes_payload_filter(payload, action):

            # Filter does not match, do not process this repo config
            return False

        # Verify that all header filters matches the request (if any header filters are specified)
        if 'header-filter' in self and not self.passes_header_filter(request_headers):

            # Filter does not match, do not process this repo config
            return False

        return True

    def execute_webhook(self, event_store):
        """Verify that the suggested repositories has matching settings and
        issue git pull and/or deploy commands."""
        import os
        import time
        import json

        event = DeployEvent(self)
        event_store.register_action(event)
        event.set_waiting(True)
        event.log_info("Running deploy commands")

        # In case there is no path configured for the repository, no pull will
        # be made.
        if 'path' not in self:
            res = GitWrapper.deploy(self)
            event.log_info("%s" % res)
            event.set_waiting(False)
            event.set_success(True)
            return

        # If the path does not exist, a warning will be raised and no pull or
        # deploy will be made.
        if not os.path.isdir(self['path']):
            event.log_error("The repository '%s' does not exist locally. Make sure it was pulled properly without errors by reviewing the log." % self['path'])
            event.set_waiting(False)
            event.set_success(False)
            return

        # If the path is not writable, a warning will be raised and no pull or
        # deploy will be made.
        if not os.access(self['path'], os.W_OK):
            event.log_error("The path '%s' is not writable. Make sure that GAD has write access to that path." % self['path'])
            event.set_waiting(False)
            event.set_success(False)
            return

        running_lock = Lock(os.path.join(self['path'], 'status_running'))
        waiting_lock = Lock(os.path.join(self['path'], 'status_waiting'))
        try:

            # Attempt to obtain the status_running lock
            while not running_lock.obtain():

                # If we're unable, try once to obtain the status_waiting lock
                if not waiting_lock.has_lock() and not waiting_lock.obtain():
                    event.log_error("Unable to obtain the status_running lock nor the status_waiting lock. Another process is already waiting, so we'll ignore the request.")

                    # If we're unable to obtain the waiting lock, ignore the request
                    break

                # Keep on attempting to obtain the status_running lock until we succeed
                time.sleep(5)

            n = 4
            res = None
            while n > 0:

                # Attempt to pull up a maximum of 4 times
                res = GitWrapper.pull(self)

                # Return code indicating success?
                if res == 0:
                    break

                n -= 1

            if 0 < n:
                res = GitWrapper.deploy(self)

        #except Exception as e:
        #    logger.error('Error during \'pull\' or \'deploy\' operation on path: %s' % self['path'])
        #    logger.error(e)
        #    raise e

        finally:

            # Release the lock if it's ours
            if running_lock.has_lock():
                running_lock.release()

            # Release the lock if it's ours
            if waiting_lock.has_lock():
                waiting_lock.release()

        event.log_info("Deploy commands were executed")
        event.set_waiting(False)
        event.set_success(True)

