/*
 * 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 <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.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"

#ifdef __CYGWIN__
#include <poll.h>
#include <unistd.h>
#include <fcntl.h>
#endif

#define MQTT_READ_MIN_TIMEOUT_SEC 10

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 {
        close(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();
#ifdef __CYGWIN__
    if (rv == IOTCS_RESULT_OK) {
        int fd = iotcs_posix_mqtt_ssl_get_socket();
        int fd_flags = fcntl(fd, F_GETFL, 0);
        fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK);
    }
#endif       
    return (rv == IOTCS_RESULT_OK) ? &g_network : NULL;

    // Now only SSL is supported. Next code was stored for future.    
    //    if (g_network.ssl_support) {
    //        iotcs_result rv = iotcs_port_ssl_connect();
    //#ifdef __CYGWIN__
    //        if (rv == IOTCS_RESULT_OK) {
    //            int fd = iotcs_posix_mqtt_ssl_get_socket();
    //            int fd_flags = fcntl(fd, F_GETFL, 0);
    //            fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK);
    //        }
    //#endif       
    //        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;
    //        }
    //
    //        bzero((char *) &address, 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 sec = timeout_ms / 1000;
    struct timeval interval;
    interval.tv_sec = (sec < MQTT_READ_MIN_TIMEOUT_SEC) ? MQTT_READ_MIN_TIMEOUT_SEC : sec;
    interval.tv_usec = (timeout_ms % 1000) * 1000;

    if (0 != setsockopt(iotcs_posix_mqtt_ssl_get_socket(), SOL_SOCKET, SO_RCVTIMEO, (char *) &interval, sizeof (struct timeval))) {
        LOG_ERR("Errno=%d", errno);
    }

    while (bytes < len) {
        int rc = iotcs_posix_mqtt_ssl_read(buffer + bytes, (size_t) (len - bytes));
        // Winsock doesn't support read/write timeouts. So neither does Cygwin. Use poll with timeout as a workaround.
#ifdef __CYGWIN__
        if (rc == 0) {
            struct pollfd fd;
            fd.fd = iotcs_posix_mqtt_ssl_get_socket();
            fd.events = POLLIN;
            if (poll(&fd, 1, MQTT_READ_MIN_TIMEOUT_SEC * 1000) <= 0) {
                return bytes > 1 ? bytes : -1;
            }
            continue;
        }
#endif
        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) {
    //        if (0 != setsockopt(iotcs_posix_mqtt_ssl_get_socket(), SOL_SOCKET, SO_RCVTIMEO, (char *) &interval, sizeof (struct timeval))) {
    //            LOG_ERR("Errno=%d", errno);
    //        }
    //
    //
    //        int bytes = 0;
    //        while (bytes < len) {
    //            int rc = iotcs_posix_mqtt_ssl_read(buffer + bytes, (size_t) (len - bytes));
    //            // Winsock doesn't support read/write timeouts. So neither does Cygwin. Use poll with timeout as a workaround.
    //#ifdef __CYGWIN__
    //           if (rc == 0) {
    //                struct pollfd fd;
    //                fd.fd = iotcs_posix_mqtt_ssl_get_socket();
    //                fd.events = POLLIN;
    //                if (poll(&fd, 1, timeout_ms) <= 0) {
    //                    return bytes > 1 ? bytes : -1;
    //                }
    //                continue;
    //            }
    //#endif
    //            if (rc == -1) {
    //                return -1;
    //            } else {
    //                bytes += rc;
    //            }
    //        }
    //        return bytes;
    //    } else {
    //        setsockopt(n->socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char *) &interval, sizeof (struct timeval));
    //
    //        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);
    struct timeval interval;
    int sec = timeout_ms / 1000;
    interval.tv_sec = (sec < MQTT_READ_MIN_TIMEOUT_SEC) ? MQTT_READ_MIN_TIMEOUT_SEC : sec;
    interval.tv_usec = (timeout_ms % 1000) * 1000;

    if (0 != setsockopt(iotcs_posix_mqtt_ssl_get_socket(), SOL_SOCKET, SO_RCVTIMEO, (char *) &interval, sizeof (struct timeval))) {
        LOG_ERR("Errno=%d", errno);
    }

    int rc = iotcs_posix_mqtt_ssl_write(buffer, len);
    //Winsock doesn't support read/write timeouts. So neither does Cygwin. Use poll with timeout as a workaround.
#ifdef __CYGWIN__
    {
        struct pollfd fd;
        fd.fd = iotcs_posix_mqtt_ssl_get_socket();
        fd.events = POLLOUT;
        if (poll(&fd, 1, MQTT_READ_MIN_TIMEOUT_SEC * 1000) <= 0) {
            return -1;
        }
    }
#endif
    // Now only SSL is supported. Next code was stored for future.
    //    if (n->ssl_support) {
    //
    //        setsockopt(iotcs_posix_mqtt_ssl_get_socket(), SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, sizeof (struct timeval));
    //        // Winsock doesn't support read/write timeouts. So neither does Cygwin. Use poll with timeout as a workaround.
    //#ifdef __CYGWIN__
    //        {
    //            struct pollfd fd;
    //            fd.fd = iotcs_posix_mqtt_ssl_get_socket();
    //            fd.events = POLLOUT;
    //            if (poll(&fd, 1, timeout_ms) <= 0) {
    //                return -1;
    //            }
    //        }
    //#endif
    //        rc = iotcs_posix_mqtt_ssl_write(buffer, len);
    //    } else {
    //        setsockopt(n->socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, sizeof (struct timeval));
    //        rc = write(n->socket_fd, buffer, len);
    //    }

    return rc;
}
