/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "advanced/iotcs_messaging.h"
#include "util/util_thread_private.h"
#include "iotcs/iotcs_private.h"
#include "util/util.h"
#include "util/util_resource_private.h"
#include "protocol/http/http_wrapper.h"
#include "policy/device_policy.h"

#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_MSG
#include "log/log_template.h"

#ifndef IOTCS_MAX_RESOURCE_NUMBER
#define IOTCS_MAX_RESOURCE_NUMBER 5
#endif

#define MSG_IS_WILDCARD(p) (p != NULL && p[0] == '*' && p[1] == '\0')
#define MSG_NORMALIZE_REQUEST_PARAM(p) ((!p || strlen(p) == 0) ? "*" : p)
#define MSG_STARTS_WITH(path, subpath, subpath_len) \
    (subpath_len < (int)strlen(path) && strncmp(path, subpath, subpath_len) == 0)

typedef struct {
    const char* endpoint_id;
    const char* path;
    iotcs_resource_handler handler;
    void *arg;
} msg_request_handler_registration;

static msg_request_handler_registration resource_list[IOTCS_MAX_RESOURCE_NUMBER];
static void default_handler(iotcs_request_message* request, void *arg, iotcs_message* response);
static msg_request_handler_registration default_registration = {.handler = default_handler, 0,};

static const iotcs_message_base default_base = {
    .priority = IOTCS_MESSAGE_PRIORITY_HIGH,
    .reliability = IOTCS_MESSAGE_RELIABILITY_BEST_EFFORT,
    .type = IOTCS_MESSAGE_RESPONSE
};

static void default_handler(iotcs_request_message* request, void *arg, iotcs_message* response) {
    (void) request; /* unused */
    (void) arg; /* unused */
    memset(response, 0, sizeof (iotcs_message));
    response->base = &default_base;
    response->u.response.status_code = PROTOCOL_RESPONSE_CODE_NOT_FOUND;
}

iotcs_result iotcs_request_dispatcher_init(void) {
    return cl_internal_request_dispatcher_init();
}

iotcs_result cl_internal_request_dispatcher_init(void) {
    UTIL_LOCK_LL();
    memset(resource_list, 0, sizeof (resource_list));
    UTIL_UNLOCK_LL();
    return IOTCS_RESULT_OK;
}

void iotcs_request_dispatcher_finalize(void) {
    cl_internal_request_dispatcher_finalize();
}

void cl_internal_request_dispatcher_finalize(void) {
    UTIL_LOCK_LL();
    memset(resource_list, 0, sizeof (resource_list));
    UTIL_UNLOCK_LL();
}

static msg_request_handler_registration* get_registered_handler(const char* endpoint_id, const char* path) {
    int i;
    const char* normalized_endpoint_id = MSG_NORMALIZE_REQUEST_PARAM(endpoint_id);
    const char* normalized_path = MSG_NORMALIZE_REQUEST_PARAM(path);

    if (MSG_IS_WILDCARD(normalized_path) && MSG_IS_WILDCARD(normalized_endpoint_id)) {
        return &default_registration;
    }
    for (i = 0; i < IOTCS_MAX_RESOURCE_NUMBER; i++) {
        msg_request_handler_registration* registration = &resource_list[i];
        if (registration->handler != NULL &&
                strcmp(normalized_endpoint_id, registration->endpoint_id) == 0 &&
                strcmp(normalized_path, registration->path) == 0) {
            return registration;
        }
    }
    return NULL;
}

/**
 * Return the most specific registered handler for the endpoint_id and path. 
 * If there is not a specific match for endpoint_id, the lookup will try to 
 * match ("*", path). Failing that, the lookup will return a handler for 
 * ("*", "*"), or the default registration if no handler for ("*","*") has been 
 * registered.
 * 
 * registration->path is treated as matched if the path argument starts with 
 * registration->path. If there are several suitable registered handlers, 
 * registration with the longest matched path will be returned.
 */
static msg_request_handler_registration* request_handler_lookup(const char* endpoint_id, const char* path) {
    int i;
    const char* normalized_endpoint_id = MSG_NORMALIZE_REQUEST_PARAM(endpoint_id);
    const char* normalized_path = MSG_NORMALIZE_REQUEST_PARAM(path);

    if (MSG_IS_WILDCARD(normalized_path) && MSG_IS_WILDCARD(normalized_endpoint_id)) {
        return &default_registration;
    }

    msg_request_handler_registration* result = &default_registration;
    int result_subpath_length = -1;
    int result_exact_endpoint_id = 0;
    for (i = 0; i < IOTCS_MAX_RESOURCE_NUMBER; i++) {
        msg_request_handler_registration* registration = &resource_list[i];
        if (registration->handler != NULL) {
            int exact_endpoint_id = strcmp(normalized_endpoint_id, registration->endpoint_id) == 0;
            if (exact_endpoint_id ||
                    (!result_exact_endpoint_id && MSG_IS_WILDCARD(registration->endpoint_id))) {
                int exact_path = strcmp(normalized_path, registration->path) == 0;
                if (exact_endpoint_id && exact_path) {
                    return registration;
                }
                // find registration with the longest matched path
                int registration_path_len = strlen(registration->path);
                if (registration_path_len >= result_subpath_length &&
                        (exact_path ||
                        MSG_IS_WILDCARD(registration->path) ||
                        MSG_STARTS_WITH(normalized_path, registration->path, registration_path_len))) {
                    result = registration;
                    // special zero value for wildcard registration path length is used
                    // for correct processing of single-character non wildcard paths
                    result_subpath_length = MSG_IS_WILDCARD(registration->path) ? 0 : registration_path_len;
                    result_exact_endpoint_id = exact_endpoint_id;
                }
            }
        }
    }
    return result;
}

void cl_internal_response_message_free_unsafe(iotcs_message *response) {
    if (response->base->type == IOTCS_MESSAGE_RESPONSE) {
        cl_internal_request_message_free_unsafe(response->u.response.request);
        response->u.response.request = NULL;
    }
}

void iotcs_request_dispatcher_dispatch(iotcs_request_message* request_message, iotcs_message* response_message) {
    cl_internal_request_dispatcher_dispatch(request_message, response_message);
}

void cl_internal_request_dispatcher_dispatch(iotcs_request_message* request_message, iotcs_message* response_message) {

    if (request_message == NULL || response_message == NULL) {
        return;
    }
    UTIL_LOCK_LL();
#ifdef IOTCS_MESSAGE_DISPATCHER
#ifdef IOTCS_IMPLICIT_EDGE_COMPUTING
	if (strncmp(request_message->url, "deviceModels/urn:oracle:iot:dcd:capability:device_policy/policyChanged",
                strlen("deviceModels/urn:oracle:iot:dcd:capability:device_policy/policyChanged")) == 0) {
		dm_load_local_policy_unsafe(NULL, request_message, IOTCS_TRUE);
		memset(response_message, 0, sizeof (iotcs_message));
		response_message->base = &default_base;
		response_message->u.response.status_code = PROTOCOL_RESPONSE_CODE_OK;
	} else {
#endif
#endif
		msg_request_handler_registration* registration = request_handler_lookup(request_message->destination, request_message->url);
		/* Init the response with 404 code, default base and empty body */
		default_handler(request_message, NULL, response_message);

		/* default_handler is already invoked - no need to call it again */
		if (registration->handler != default_handler) {
			(*registration->handler)(request_message, registration->arg, response_message);
		}

		/* Link response with request */
		response_message->u.response.request = request_message;
#ifdef IOTCS_MESSAGE_DISPATCHER
#ifdef IOTCS_IMPLICIT_EDGE_COMPUTING
	}
#endif
#endif
    UTIL_UNLOCK_LL();
}

static msg_request_handler_registration* get_free_registration() {
    int i;
    for (i = 0; i < IOTCS_MAX_RESOURCE_NUMBER; i++) {
        if (resource_list[i].handler == NULL) {
            return &resource_list[i];
        }
    }
    return NULL;
}

iotcs_result iotcs_register_request_handler(const char* endpoint_id, const char* path, iotcs_resource_handler handler, void* arg) {
    msg_request_handler_registration* registration = NULL;

    UTIL_LOCK_LL();
    if (!handler) {
        UTIL_UNLOCK_LL();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    const char* normalized_endpoint_id = MSG_NORMALIZE_REQUEST_PARAM(endpoint_id);
    const char* normalized_path = MSG_NORMALIZE_REQUEST_PARAM(path);
    registration = get_registered_handler(normalized_endpoint_id, normalized_path);
    if (registration == NULL) {
        registration = get_free_registration();
        if (registration == NULL) {
            UTIL_UNLOCK_LL();
            return IOTCS_RESULT_OUT_OF_MEMORY;
        }
        registration->endpoint_id = normalized_endpoint_id;
        registration->path = normalized_path;
    }

    registration->handler = handler;
    registration->arg = arg;
    UTIL_UNLOCK_LL();
    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_unregister_request_handler_all(iotcs_resource_handler handler) {
    int i;
    UTIL_LOCK_LL();

    if (!handler) {
        UTIL_UNLOCK_LL();
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if (handler == default_registration.handler) {
        default_registration.handler = default_handler;
    }

    for (i = 0; i < IOTCS_MAX_RESOURCE_NUMBER; i++) {
        if (resource_list[i].handler == handler) {
            resource_list[i].handler = NULL;
        }
    }

    UTIL_UNLOCK_LL();
    return IOTCS_RESULT_OK;
}

iotcs_result iotcs_unregister_request_handler(const char* endpoint_id, const char* path) {
    UTIL_LOCK_LL();
    msg_request_handler_registration* registration = get_registered_handler(endpoint_id, path);
    if (registration == &default_registration) {
        default_registration.handler = default_handler;
    } else if (registration != NULL) {
        registration->handler = NULL;
    }
    UTIL_UNLOCK_LL();
    return IOTCS_RESULT_OK;
}

iotcs_resource_handler iotcs_get_request_handler(const char* endpoint_id, const char* path) {
    msg_request_handler_registration* registration;

    UTIL_LOCK_LL();
    registration = request_handler_lookup(endpoint_id, path);
    UTIL_UNLOCK_LL();
    return registration->handler;
}
