from coapthon import defines
from coapthon.messages.response import Response
from coapthon.resources.resource import Resource

__author__ = 'Giacomo Tanganelli'


class ResourceLayer(object):
    """
    Handles the Resources.
    """
    def __init__(self, parent):
        """
        Initialize a Resource Layer.

        :type parent: CoAP
        :param parent: the CoAP server
        """
        self._parent = parent

    def edit_resource(self, transaction, path):
        """
        Render a POST on an already created resource.

        :param path: the path of the resource
        :param transaction: the transaction
        :return: the transaction
        """
        resource_node = self._parent.root[path]
        transaction.resource = resource_node
        # If-Match
        if transaction.request.if_match:
            if None not in transaction.request.if_match and str(transaction.resource.etag) \
                    not in transaction.request.if_match:
                transaction.response.code = defines.Codes.PRECONDITION_FAILED.number
                return transaction

        method = getattr(resource_node, "render_POST", None)
        try:
            resource = method(request=transaction.request)
        except NotImplementedError:
            try:
                method = getattr(resource_node, "render_POST_advanced", None)
                ret = method(request=transaction.request, response=transaction.response)
                if isinstance(ret, tuple) and len(ret) == 2 and isinstance(ret[1], Response) \
                        and isinstance(ret[0], Resource):
                    # Advanced handler
                    resource, response = ret
                    resource.changed = True
                    resource.observe_count += 1
                    transaction.resource = resource
                    transaction.response = response
                    if transaction.response.code is None:
                        transaction.response.code = defines.Codes.CREATED.number
                    return transaction
                elif isinstance(ret, tuple) and len(ret) == 3 and isinstance(ret[1], Response) \
                        and isinstance(ret[0], Resource):
                    # Advanced handler separate
                    resource, response, callback = ret
                    ret = self._handle_separate_advanced(transaction, callback)
                    if not isinstance(ret, tuple) or \
                            not (isinstance(ret[0], Resource) and isinstance(ret[1], Response)):  # pragma: no cover
                        transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
                        return transaction
                    resource, response = ret
                    resource.changed = True
                    resource.observe_count += 1
                    transaction.resource = resource
                    transaction.response = response
                    if transaction.response.code is None:
                        transaction.response.code = defines.Codes.CREATED.number
                    return transaction
                else:
                    raise NotImplementedError
            except NotImplementedError:
                transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
                return transaction

        if isinstance(resource, Resource):
            pass
        elif isinstance(resource, tuple) and len(resource) == 2:
            resource, callback = resource
            resource = self._handle_separate(transaction, callback)
            if not isinstance(resource, Resource):  # pragma: no cover
                transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
                return transaction
        else:  # pragma: no cover
            # Handle error
            transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
            return transaction

        if resource.path is None:
            resource.path = path
        resource.observe_count = resource_node.observe_count

        if resource is resource_node:
            transaction.response.code = defines.Codes.CHANGED.number
        else:
            transaction.response.code = defines.Codes.CREATED.number
        resource.changed = True
        resource.observe_count += 1
        transaction.resource = resource

        assert(isinstance(resource, Resource))
        if resource.etag is not None:
            transaction.response.etag = resource.etag

        if transaction.response.code == defines.Codes.CREATED.number:
            # Only on CREATED according to RFC 7252 Chapter 5.8.2 POST
            transaction.response.location_path = resource.path

            if resource.location_query is not None and len(resource.location_query) > 0:
                transaction.response.location_query = resource.location_query

        transaction.response.payload = None

        self._parent.root[resource.path] = resource

        return transaction

    def add_resource(self, transaction, parent_resource, lp):
        """
        Render a POST on a new resource.

        :param transaction: the transaction
        :param parent_resource: the parent of the resource
        :param lp: the location_path attribute of the resource
        :return: the response
        """
        method = getattr(parent_resource, "render_POST", None)
        try:
            resource = method(request=transaction.request)
        except NotImplementedError:
            try:
                method = getattr(parent_resource, "render_POST_advanced", None)
                ret = method(request=transaction.request, response=transaction.response)
                if isinstance(ret, tuple) and len(ret) == 2 and isinstance(ret[1], Response) \
                        and isinstance(ret[0], Resource):
                    # Advanced handler
                    resource, response = ret
                    resource.path = lp
                    resource.changed = True
                    self._parent.root[resource.path] = resource
                    transaction.resource = resource
                    transaction.response = response
                    if transaction.response.code is None:
                        transaction.response.code = defines.Codes.CREATED.number
                    return transaction
                elif isinstance(ret, tuple) and len(ret) == 3 and isinstance(ret[1], Response) \
                        and isinstance(ret[0], Resource):
                    # Advanced handler separate
                    resource, response, callback = ret
                    ret = self._handle_separate_advanced(transaction, callback)
                    if not isinstance(ret, tuple) or \
                            not (isinstance(ret[0], Resource) and isinstance(ret[1], Response)):  # pragma: no cover
                        transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
                        return transaction
                    resource, response = ret
                    resource.path = lp
                    resource.changed = True
                    self._parent.root[resource.path] = resource
                    transaction.resource = resource
                    transaction.response = response
                    if transaction.response.code is None:
                        transaction.response.code = defines.Codes.CREATED.number
                    return transaction
                else:
                    raise NotImplementedError
            except NotImplementedError:
                transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
                return transaction
        if isinstance(resource, Resource):
            pass
        elif isinstance(resource, tuple) and len(resource) == 2:
            resource, callback = resource
            resource = self._handle_separate(transaction, callback)
            if not isinstance(resource, Resource):  # pragma: no cover
                transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
                return transaction
        else:  # pragma: no cover
            # Handle error
            transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
            return transaction

        resource.path = lp

        if resource.etag is not None:
            transaction.response.etag = resource.etag

        transaction.response.location_path = resource.path

        if resource.location_query is not None and len(resource.location_query) > 0:
            transaction.response.location_query = resource.location_query

        transaction.response.code = defines.Codes.CREATED.number
        transaction.response.payload = None

        assert (isinstance(resource, Resource))
        if resource.etag is not None:
            transaction.response.etag = resource.etag
        if resource.max_age is not None:
            transaction.response.max_age = resource.max_age

        resource.changed = True

        transaction.resource = resource

        self._parent.root[resource.path] = resource

        return transaction

    def create_resource(self, path, transaction):
        """
        Render a POST request.

        :param path: the path of the request
        :param transaction: the transaction
        :return: the response
        """
        t = self._parent.root.with_prefix(path)
        max_len = 0
        imax = None
        for i in t:
            if i == path:
                # Resource already present
                return self.edit_resource(transaction, path)
            elif len(i) > max_len:
                imax = i
                max_len = len(i)

        lp = path
        parent_resource = self._parent.root[imax]
        if parent_resource.allow_children:
            return self.add_resource(transaction, parent_resource, lp)
        else:
            transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
            return transaction

    def update_resource(self, transaction):
        """
        Render a PUT request.

        :param transaction: the transaction
        :return: the response
        """
        # If-Match
        if transaction.request.if_match:
            if None not in transaction.request.if_match and str(transaction.resource.etag) \
                    not in transaction.request.if_match:
                transaction.response.code = defines.Codes.PRECONDITION_FAILED.number
                return transaction
        # If-None-Match
        if transaction.request.if_none_match:
            transaction.response.code = defines.Codes.PRECONDITION_FAILED.number
            return transaction

        method = getattr(transaction.resource, "render_PUT", None)

        try:
            resource = method(request=transaction.request)
        except NotImplementedError:
            try:
                method = getattr(transaction.resource, "render_PUT_advanced", None)
                ret = method(request=transaction.request, response=transaction.response)
                if isinstance(ret, tuple) and len(ret) == 2 and isinstance(ret[1], Response) \
                        and isinstance(ret[0], Resource):
                    # Advanced handler
                    resource, response = ret
                    resource.changed = True
                    resource.observe_count += 1
                    transaction.resource = resource
                    transaction.response = response
                    if transaction.response.code is None:
                        transaction.response.code = defines.Codes.CHANGED.number
                    return transaction
                elif isinstance(ret, tuple) and len(ret) == 3 and isinstance(ret[1], Response) \
                        and isinstance(ret[0], Resource):
                    # Advanced handler separate
                    resource, response, callback = ret
                    ret = self._handle_separate_advanced(transaction, callback)
                    if not isinstance(ret, tuple) or \
                            not (isinstance(ret[0], Resource) and isinstance(ret[1], Response)):  # pragma: no cover
                        transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
                        return transaction
                    resource, response = ret
                    resource.changed = True
                    resource.observe_count += 1
                    transaction.resource = resource
                    transaction.response = response
                    if transaction.response.code is None:
                        transaction.response.code = defines.Codes.CHANGED.number
                    return transaction
                else:
                    raise NotImplementedError
            except NotImplementedError:
                transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
                return transaction

        if isinstance(resource, Resource):
            pass
        elif isinstance(resource, tuple) and len(resource) == 2:
            resource, callback = resource
            resource = self._handle_separate(transaction, callback)
            if not isinstance(resource, Resource):  # pragma: no cover
                transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
                return transaction
        else:  # pragma: no cover
            # Handle error
            transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
            return transaction

        if resource.etag is not None:
            transaction.response.etag = resource.etag

        transaction.response.code = defines.Codes.CHANGED.number

        transaction.response.payload = None

        assert (isinstance(resource, Resource))
        if resource.etag is not None:
            transaction.response.etag = resource.etag
        if resource.max_age is not None:
            transaction.response.max_age = resource.max_age

        resource.changed = True
        resource.observe_count += 1
        transaction.resource = resource

        return transaction

    def _handle_separate(self, transaction, callback):
        # Handle separate
        if not transaction.request.acknowledged:
            self._parent._send_ack(transaction)
            transaction.request.acknowledged = True
        resource = callback(request=transaction.request)
        return resource

    def _handle_separate_advanced(self, transaction, callback):
        # Handle separate
        if not transaction.request.acknowledged:
            self._parent._send_ack(transaction)
            transaction.request.acknowledged = True
        return callback(request=transaction.request, response=transaction.response)

    def delete_resource(self, transaction, path):
        """
        Render a DELETE request.

        :param transaction: the transaction
        :param path: the path
        :return: the response
        """

        resource = transaction.resource
        method = getattr(resource, 'render_DELETE', None)

        try:
            ret = method(request=transaction.request)
        except NotImplementedError:
            try:
                method = getattr(transaction.resource, "render_DELETE_advanced", None)
                ret = method(request=transaction.request, response=transaction.response)
                if isinstance(ret, tuple) and len(ret) == 2 and isinstance(ret[1], Response) \
                        and isinstance(ret[0], bool):
                    # Advanced handler
                    delete, response = ret
                    if delete:
                        del self._parent.root[path]
                    transaction.response = response
                    if transaction.response.code is None:
                        transaction.response.code = defines.Codes.DELETED.number
                    return transaction
                elif isinstance(ret, tuple) and len(ret) == 3 and isinstance(ret[1], Response) \
                        and isinstance(ret[0], Resource):
                    # Advanced handler separate
                    resource, response, callback = ret
                    ret = self._handle_separate_advanced(transaction, callback)
                    if not isinstance(ret, tuple) or \
                            not (isinstance(ret[0], bool) and isinstance(ret[1], Response)):  # pragma: no cover
                        transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
                        return transaction
                    delete, response = ret
                    if delete:
                        del self._parent.root[path]
                    transaction.response = response
                    if transaction.response.code is None:
                        transaction.response.code = defines.Codes.DELETED.number
                    return transaction
                else:
                    raise NotImplementedError
            except NotImplementedError:
                transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
                return transaction

        if isinstance(ret, bool):
            pass
        elif isinstance(ret, tuple) and len(ret) == 2:
            resource, callback = ret
            ret = self._handle_separate(transaction, callback)
            if not isinstance(ret, bool):  # pragma: no cover
                transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
                return transaction
        else:  # pragma: no cover
            # Handle error
            transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
            return transaction
        if ret:
            del self._parent.root[path]
            transaction.response.code = defines.Codes.DELETED.number
            transaction.response.payload = None
            transaction.resource.deleted = True
        else:  # pragma: no cover
            transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number

        return transaction

    def get_resource(self, transaction):
        """
        Render a GET request.

        :param transaction: the transaction
        :return: the transaction
        """
        method = getattr(transaction.resource, 'render_GET', None)

        transaction.resource.actual_content_type = None
        # Accept
        if transaction.request.accept is not None:
            transaction.resource.actual_content_type = transaction.request.accept

        # Render_GET
        try:
            resource = method(request=transaction.request)
        except NotImplementedError:
            try:
                method = getattr(transaction.resource, "render_GET_advanced", None)
                ret = method(request=transaction.request, response=transaction.response)
                if isinstance(ret, tuple) and len(ret) == 2 and isinstance(ret[1], Response) \
                        and isinstance(ret[0], Resource):
                    # Advanced handler
                    resource, response = ret
                    transaction.resource = resource
                    transaction.response = response
                    if transaction.response.code is None:
                        transaction.response.code = defines.Codes.CONTENT.number
                    return transaction
                elif isinstance(ret, tuple) and len(ret) == 3 and isinstance(ret[1], Response) \
                        and isinstance(ret[0], Resource):
                    # Advanced handler separate
                    resource, response, callback = ret
                    ret = self._handle_separate_advanced(transaction, callback)
                    if not isinstance(ret, tuple) or \
                            not (isinstance(ret[0], Resource) and isinstance(ret[1], Response)):  # pragma: no cover
                        transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
                        return transaction
                    resource, response = ret
                    transaction.resource = resource
                    transaction.response = response
                    if transaction.response.code is None:
                        transaction.response.code = defines.Codes.CONTENT.number
                    return transaction
                else:
                    raise NotImplementedError
            except NotImplementedError:
                transaction.response.code = defines.Codes.METHOD_NOT_ALLOWED.number
                return transaction

        if isinstance(resource, Resource):
            pass
        elif isinstance(resource, tuple) and len(resource) == 2:
            resource, callback = resource
            resource = self._handle_separate(transaction, callback)
            if not isinstance(resource, Resource):  # pragma: no cover
                transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
                return transaction
        else:  # pragma: no cover
            # Handle error
            transaction.response.code = defines.Codes.INTERNAL_SERVER_ERROR.number
            return transaction.response

        if resource.etag in transaction.request.etag:
            transaction.response.code = defines.Codes.VALID.number
        else:
            transaction.response.code = defines.Codes.CONTENT.number

        try:
            transaction.response.payload = resource.payload
            if resource.actual_content_type is not None \
                    and resource.actual_content_type != defines.Content_types["text/plain"]:
                transaction.response.content_type = resource.actual_content_type
        except KeyError:
            transaction.response.code = defines.Codes.NOT_ACCEPTABLE.number
            return transaction.response

        assert(isinstance(resource, Resource))
        if resource.etag is not None:
            transaction.response.etag = resource.etag
        if resource.max_age is not None:
            transaction.response.max_age = resource.max_age

        transaction.resource = resource

        return transaction

    def discover(self, transaction):
        """
        Render a GET request to the .well-know/core link.

        :param transaction: the transaction
        :return: the transaction
        """
        transaction.response.code = defines.Codes.CONTENT.number
        payload = ""
        for i in self._parent.root.dump():
            if i == "/":
                continue
            resource = self._parent.root[i]
            if resource.visible:
                ret = self.valid(transaction.request.uri_query, resource.attributes)
                if ret:
                    payload += self.corelinkformat(resource)

        transaction.response.payload = payload
        transaction.response.content_type = defines.Content_types["application/link-format"]
        return transaction

    @staticmethod
    def valid(query, attributes):
        query = query.split("&")
        for q in query:
            q = str(q)
            assert(isinstance(q, str))
            tmp = q.split("=")
            if len(tmp) > 1:
                k = tmp[0]
                v = tmp[1]
                if k in attributes:
                    if v == attributes[k]:
                        continue
                    else:
                        return False
                else:
                    return False
        return True

    @staticmethod
    def corelinkformat(resource):
        """
        Return a formatted string representation of the corelinkformat in the tree.

        :return: the string
        """
        msg = "<" + resource.path + ">;"
        assert(isinstance(resource, Resource))
        keys = sorted(list(resource.attributes.keys()))
        for k in keys:
            method = getattr(resource, defines.corelinkformat[k], None)
            if method is not None and method != "":
                v = method
                msg = msg[:-1] + ";" + str(v) + ","
            else:
                v = resource.attributes[k]
                if v is not None:
                    msg = msg[:-1] + ";" + k + "=" + v + ","
        return msg
