/*
 * 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 <string.h>
#include <stdio.h>
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdlib.h>
#include <sys/types.h>
#include <time.h>
#include <iphlpapi.h>
#pragma comment(lib, "IPHLPAPI.lib")

#include "iotcs_port_system.h"
#include "iotcs_port_diagnostic.h"
#include "util/util_memory.h"
#include "util/util.h"

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

/**
 * @brief Writes ip4 address of the of the given network adapter as a null terminated string to a given buffer
 * @param buffer output buffer
 * @param buf_len length of the output buffer
 * @param iface network adapter
 * @return The number of characters written to \a buffer, not counting the terminating null character.
 * @return Negative value otherwise (including cases when \a buffer is too small for the string)
 */
static int get_interface_ip4_address_string(char* buffer, int buffer_length, PIP_ADAPTER_INFO iface) {
    int length = -1;
    GOTO_ERR_MSG(!iface || !buffer || buffer_length <= 0, "Invalid parameters");
    length = (int) strlen(iface->IpAddressList.IpAddress.String);
    GOTO_ERR_MSG(length >= buffer_length, "Buffer length is too small for ip4 address.");
    strcpy_s(buffer, length + 1, iface->IpAddressList.IpAddress.String);
    return length;
error:
    return -1;
}

/**
 * @brief Writes mac address of the of the given network adapter as a null terminated string to a given buffer
 * @param buffer output buffer
 * @param buf_len length of the output buffer
 * @param iface network adapter
 * @return The number of characters written to \a buffer, not counting the terminating null character.
 * @return Negative value otherwise (including cases when \a buffer is too small for the string)
 */
static int get_interface_mac_address_string(char* buffer, int buffer_length, PIP_ADAPTER_INFO iface) {
    GOTO_ERR_MSG(!iface || !buffer || buffer_length <= 0, "Invalid parameters");
    GOTO_ERR(iface->AddressLength == 0);
    GOTO_ERR_MSG(util_size_t_to_int(iface->AddressLength) < 0, "Loss of data found");
    GOTO_ERR_MSG((int) iface->AddressLength > buffer_length / 3, "Buffer length is too small for mac address.");
    /* Assuming that iface->AddressLength > 0*/
    GOTO_ERR_MSG(util_size_t_to_int(iface->Address[0]) < 0, "Loss of data found");
    sprintf(buffer, "%.2X", (int) iface->Address[0]);
    for (int i = 1; i < (int) iface->AddressLength; i++) {
        GOTO_ERR_MSG(util_size_t_to_int(iface->Address[i]) < 0, "Loss of data found");
        sprintf(buffer + 3 * i - 1, "-%.2X", (int) iface->Address[i]);
    }
    /* null termination */
    buffer[3 * iface->AddressLength - 1] = 0;
    return 3 * iface->AddressLength - 1;
error:
    return -1;
}

/**
 * returns the network adapters list as a continuous memory chunk
 */
static PIP_ADAPTER_INFO list_network_interfaces() {
    PIP_ADAPTER_INFO pAdapterInfo = NULL;
    ULONG ulOutBufLen = 0;
    DWORD dwRetVal = 0;

    /* retrieving size of buffer required for adapters linked list */
    GOTO_ERR_MSG(GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) != ERROR_BUFFER_OVERFLOW,
            "Failed to retrieve adapters linked list size.");
    GOTO_ERR_CRIT_MSG(NULL == (pAdapterInfo = (IP_ADAPTER_INFO *) util_malloc(ulOutBufLen)), "Out of memory");
    /* retrieving size linked list of adapters */
    if ((dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) != NO_ERROR) {
        util_free(pAdapterInfo);
        pAdapterInfo = NULL;
    }
error:
    return pAdapterInfo;
}

#define  IP_ADDRESS_IS_NOT_ZERO(address_list) \
    (address_list).IpAddress.String[0] && _strnicmp((address_list).IpAddress.String, "0.0.0.0", 7)

/**
 * searches for an adapter which has IPv4 & gateway addresses both non equal to zero
 */
static PIP_ADAPTER_INFO lookup_main_network_interface(PIP_ADAPTER_INFO first) {
    PIP_ADAPTER_INFO pAdapter = first;
    while (pAdapter != NULL) {

        /**
         * skip local loopback
         */
        switch (pAdapter->Type) {
            case MIB_IF_TYPE_LOOPBACK:
                continue;
            default:
                ;
        };
        /**
         * consider the main adapter the one has both ipv4 address and gateway address non equal to zero
         */
        if (IP_ADDRESS_IS_NOT_ZERO(pAdapter->IpAddressList) &&
                IP_ADDRESS_IS_NOT_ZERO(pAdapter->GatewayList)) {
            return pAdapter;
        }
        pAdapter = pAdapter->Next;
    }
    return NULL;
}

/**
 * @brief Writes ip address of the device as a null terminated string to a given buffer
 * Returned string is sent to IoT server in response to \a /info request 
 * of \a urn:oracle:iot:dcd:capability:diagnostics device model
 * as a value of \a ipAddress property.
 * @note Optional API. Called by the Library if IOTCS_USE_DIAGNOSTIC_CAPABILITY option is defined.
 * @param buffer output buffer
 * @param buf_len length of the output buffer
 * @return The number of characters written to \a buffer, not counting the terminating null character.
 * @return Negative value otherwise (including cases when \a buffer is too small for the string)
 */
int iotcs_port_get_ip_address(char* buffer, int buffer_length) {
    int length = -1;
    PIP_ADAPTER_INFO adapters = list_network_interfaces();
    if (adapters != NULL) {
        PIP_ADAPTER_INFO iface = lookup_main_network_interface(adapters);
        if (iface != NULL) {
            length = get_interface_ip4_address_string(buffer, buffer_length, iface);
        }
        util_free(adapters);
    }
    return length;
}

/**
 * @brief Writes mac address of the device as a null terminated string to a given buffer
 * Returned string is sent to IoT server in response to \a /info request 
 * of \a urn:oracle:iot:dcd:capability:diagnostics device model
 * as a value of \a macAddress property.
 * @note Optional API. Called by the Library if IOTCS_USE_DIAGNOSTIC_CAPABILITY option is defined.
 * @param buffer output buffer
 * @param buf_len length of the output buffer
 * @return The number of characters written to \a buffer, not counting the terminating null character.
 * @return Negative value otherwise (including cases when \a buffer is too small for the string)
 */
int iotcs_port_get_mac_address(char* buffer, int buffer_length) {
    int length = -1;
    PIP_ADAPTER_INFO adapters = list_network_interfaces();
    if (adapters != NULL) {
        PIP_ADAPTER_INFO iface = lookup_main_network_interface(adapters);
        if (iface != NULL) {
            length = get_interface_mac_address_string(buffer, buffer_length, iface);
        }
        util_free(adapters);
    }
    return length;
}

/**
 * returns the time of the last system startup expressed in milliseconds elapsed since 1970-Jan-01
 */
int64_t iotcs_port_get_start_time(void) {
    return iotcs_port_get_current_time_millis() - GetTickCount64();
}

const char* iotcs_port_get_version(void) {
    return "C-Windows Library 1.1 release";
}

/**
 * receives the total number of bytes on a disk that are available
 * to the user who is associated with the calling thread
 */
uint64_t iotcs_port_get_total_disk_space(void) {

    uint64_t result = -1;
    GetDiskFreeSpaceEx(NULL, NULL, (PULARGE_INTEGER) & result, NULL);
    return result;
}

/**
 * receives the total number of free bytes on a disk
 */
uint64_t iotcs_port_get_free_disk_space(void) {

    uint64_t result = -1;
    GetDiskFreeSpaceEx(NULL, NULL, NULL, (PULARGE_INTEGER) & result);
    return result;
}
