/*
 * 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 <sys/types.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <time.h>
#include <openssl/err.h>
#include <winsock2.h>

#include "iotcs.h"
#include "iotcs_port_ssl.h"
#include "iotcs_port_mqtt.h"
#include "util/util.h"

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

#define MQTT_READ_MIN_TIMEOUT_MS 10000 

static int iotcs_posix_mqtt_read(Network* n, unsigned char* buffer, int len, int timeout_ms);
static int iotcs_posix_mqtt_write(Network* n, unsigned char* buffer, int len, int timeout_ms);

static Network g_network = {-1, iotcs_posix_mqtt_read, iotcs_posix_mqtt_write, 0};

char expired(Timer* timer) {
    struct timeval now, res;
    gettimeofday(&now, NULL);
    timersub(timer, &now, &res);
    return timercmp(timer, &now, <); //res.tv_sec < 0 || (res.tv_sec == 0 && res.tv_usec <= 0);
}

void countdown_ms(Timer* timer, unsigned int timeout) {
    struct timeval now;
    gettimeofday(&now, NULL);
    struct timeval interval = {timeout / 1000, (timeout % 1000) * 1000};
    timeradd(&now, &interval, timer);
}

void countdown(Timer* timer, unsigned int timeout) {
    struct timeval now;
    gettimeofday(&now, NULL);
    struct timeval interval = {timeout, 0};
    timeradd(&now, &interval, timer);
}

int left_ms(Timer* timer) {
    struct timeval now, res;
    gettimeofday(&now, NULL);
    timersub(timer, &now, &res);
    //LOG_DBG("left %d ms", (res.tv_sec < 0) ? 0 : res.tv_sec * 1000 + res.tv_usec / 1000);
    return (res.tv_sec < 0) ? 0 : res.tv_sec * 1000 + res.tv_usec / 1000;
}

void InitTimer(Timer* timer) {
    *timer = (struct timeval){0, 0};
}

void iotcs_port_mqtt_network_disconnect() {
    iotcs_port_ssl_disconnect();

    /*Now only SSL is supported. Next code was stored for future.

        if (g_network.ssl_support) {
        iotcs_port_ssl_disconnect();
    } else {
        closesocket(g_network.socket_fd);
    }*/
}

Network* iotcs_port_mqtt_network_connect(char* addr, int port, int ssl_support) {
    if (!ssl_support) {
        return NULL;
    }

    g_network.ssl_support = ssl_support;
    iotcs_result rv = iotcs_port_ssl_connect();
    return (rv == IOTCS_RESULT_OK) ? &g_network : NULL;

    /*Now only SSL is supported. Next code was stored for future.

        g_network.ssl_support = ssl_support;
    if (g_network.ssl_support) {
        iotcs_result rv = iotcs_port_ssl_connect();
        return (rv == IOTCS_RESULT_OK) ? &g_network : NULL;
    } else {
        struct sockaddr_in address;
        struct hostent* host = NULL;
        int rc = -1;

        if ((host = gethostbyname(addr)) == NULL) {
            LOG_ERR("Invalid host name: %s", addr);
            return NULL;
        }

        memset((char *) &address, 0, sizeof (address));
        address.sin_family = AF_INET;
        address.sin_addr.s_addr = *(long*) (host->h_addr);
        address.sin_port = htons(port);

        if (rc == 0) {
            g_network.socket_fd = socket(AF_INET, SOCK_STREAM, 0);
            if (g_network.socket_fd != -1) {
                rc = connect(g_network.socket_fd, (struct sockaddr*) &address, sizeof (address));
            }
        }

        return rc == 0 ? &g_network : NULL;
    }*/
}

static int iotcs_posix_mqtt_read(Network* n, unsigned char* buffer, int len, int timeout_ms) {
    LOG_DBG("MQTT_READ_TIMEOUT: %d", timeout_ms);
    int bytes = 0;
    int timeout = (timeout_ms < MQTT_READ_MIN_TIMEOUT_MS) ? MQTT_READ_MIN_TIMEOUT_MS : timeout_ms;
    if (0 != setsockopt(iotcs_posix_mqtt_ssl_get_socket(), SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof (timeout))) {
        LOG_ERR("Errno=%d", errno);
    }
    while (bytes < len) {
        int rc = iotcs_posix_mqtt_ssl_read(buffer + bytes, (size_t) (len - bytes));
        if (rc == -1) {
            return -1;
        } else {
            bytes += rc;
        }
    }
    return bytes;

    /*Now only SSL is supported. Next code was stored for future.

    if (n->ssl_support) {
            int bytes = 0;
            while (bytes < len) {
                    int rc = iotcs_posix_mqtt_ssl_read(buffer + bytes, (size_t)(len - bytes));
                    if (rc == -1) {
                            return -1;
                    }
                    else {
                            bytes += rc;
                    }
            }
            return bytes;
    }
    else {
            int bytes = 0;
            while (bytes < len) {
                    int rc = recv(n->socket_fd, &buffer[bytes], (size_t)(len - bytes), 0);
                    if (rc == -1) {
                            if (errno != ENOTCONN && errno != ECONNRESET) {
                                    bytes = -1;
                                    break;
                            }
                    } else
                            bytes += rc;
            }
            return bytes;
    }*/
}

static int iotcs_posix_mqtt_write(Network* n, unsigned char* buffer, int len, int timeout_ms) {
    LOG_DBG("MQTT_READ_TIMEOUT: %d", timeout_ms);
    int timeout = (timeout_ms < MQTT_READ_MIN_TIMEOUT_MS) ? MQTT_READ_MIN_TIMEOUT_MS : timeout_ms;
    if (0 != setsockopt(iotcs_posix_mqtt_ssl_get_socket(), SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof (timeout))) {
        LOG_ERR("Errno=%d", errno);
    }
    return iotcs_posix_mqtt_ssl_write(buffer, len);

    /*Now only SSL is supported. Next code was stored for future.

    int rc;

    if (n->ssl_support) {
            rc = iotcs_posix_mqtt_ssl_write(buffer, len);
    }
    else {
            rc = send(n->socket_fd, buffer, len, 0);
    }

    return rc;*/
}
