/**
 * @file tkl_wired.c
 * @brief this file was auto-generated by tuyaos v&v tools, developer can add implements between BEGIN and END
 *
 * @warning: changes between user 'BEGIN' and 'END' will be keeped when run tuyaos v&v tools
 *           changes in other place will be overwrited and lost
 *
 * @copyright Copyright 2020-2021 Tuya Inc. All Rights Reserved.
 *
 */

// --- BEGIN: user defines and implements ---
#include "tkl_wired.h"
#include "tuya_error_code.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>

#define ROUTE_PATH "/proc/net/route"

static char *net_utils_space_trim(char *str)
{
    int i = 0;
    int count = 0;
    int size = 0;

    char *tmp = NULL;
    if (!str) {
        return NULL;
    }

    size = strlen(str);
    tmp = str;

    while (*str == ' ' || (unsigned char)(*str - 9) <= (13 - 9)) {
        str++;
        count++;
    }

    if (count) {
        memmove(tmp, str, size - count);
        memset(tmp + (size - count), 0x0, size - count);
    }

    return (char *)str;
}

// only find first devname
static int net_utils_get_devname(char *devname, int size)
{

    FILE *fp = NULL;
    char *fpath = "/proc/net/dev";
    char buf[512];
    int ret = -1;
    char ifname[IFNAMSIZ];
    struct ifreq ifr;
    char *tmp = NULL;

    if (!devname || size <= 0) {
        printf("%s %d: arg err\n", __func__, __LINE__);
        return -1;
    }

    fp = fopen(fpath, "r");
    if (!fp) {
        printf("%s %d: %s is not exist\n", __func__, __LINE__, fpath);
        return -1;
    }

    memset(devname, 0x0, size);
    fgets(buf, sizeof(buf), fp);
    fgets(buf, sizeof(buf), fp);

    while (fgets(buf, sizeof(buf), fp)) {
        sscanf(buf, "%[^:]:", ifname);

        tmp = net_utils_space_trim(ifname);
        if (!tmp) {
            printf("%s %d: %s error\n", __func__, __LINE__, ifname);
            continue;
        } else {
            // printf( "%s %d: found %s\n", __func__, __LINE__, ifname );
            if (strncmp(ifname, "en", 2) == 0 || strncmp(ifname, "eth", 3) == 0) {
                memcpy(devname, ifname, size);
                ret = 0;
                break;
            }
        }
    }

    if (fp) {
        fclose(fp);
    }

    return ret;
}

static int net_utils_check_cable(const char *ifname, int *plugged)
{

    int sk = -1;
    struct ifreq ifr;
    int flag = IFF_UP;
    int ret = -1;

    if (!ifname) {
        printf("%s %d: ifname is null\n", __func__, __LINE__);
        return -1;
    }

    sk = socket(AF_INET, SOCK_DGRAM, 0);
    if (sk < 0) {
        printf("%s %d: failed to create sk\n", __func__, __LINE__);
        return -1;
    }

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1);

    ret = ioctl(sk, SIOCGIFFLAGS, &ifr);
    if (ret < 0) {
        printf("%s %d: failed to get iterface info \n", __func__, __LINE__);
        goto exit_end;
    }

    if (ifr.ifr_flags & IFF_RUNNING) {
        // printf( "%s %d: %s running \n", __func__, __LINE__, ifname );
        *plugged = 1;

    } else {
        // printf( "%s %d: %s not running \n", __func__, __LINE__, ifname );
        *plugged = 0;
    }

exit_end:
    if (sk) {
        close(sk);
    }
    return ret;
}

TKL_WIRED_STATUS_CHANGE_CB status_cb;
/**
 * @brief  set the status change callback
 *
 * @param[in]   cb: the callback when link status changed
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
static void *link_status_thread(void *arg)
{
    TKL_WIRED_STAT_E status;
    int old_status = -1;

    /* Circulate detection of network cable plugging and
       unplugging status, and report status when changing
    */
    while (1) {
        tkl_wired_get_status(&status);
        if (status == old_status) {
            sleep(1);
            continue;
        }
        old_status = status;

        status_cb(status);
        sleep(1);
    }
}

static OPERATE_RET tkl_wired_get_gateway(NW_IP_S *ip)
{
    FILE *fp = NULL;
    char *str = NULL;
    char buf[256] = {0};
    unsigned int dest = 0;
    struct in_addr addr;
    int get_gw = 0;

    /* Open file */
    if ((fp = fopen(ROUTE_PATH, "r")) == NULL) {
        printf("open %s failed, %d, %s\n", ROUTE_PATH, errno, strerror(errno));
        return OPRT_COM_ERROR;
    }

    fgets(buf, sizeof(buf), fp);

    /* Read one line at a time, perform string processing */
    while (fgets(buf, sizeof(buf), fp)) {

        sscanf(buf, "%*s%x %x", &dest, &addr.s_addr);
        if ((dest == 0) && (addr.s_addr != 0)) {
            strncpy(ip->gw, inet_ntoa(addr), sizeof(ip->gw));
            get_gw = 1;
            break;
        }
    }

    if (get_gw == 0)
        printf("not find gw addr\n");

    fclose(fp);
    return OPRT_OK;
}
// --- END: user defines and implements ---

/**
 * @brief  get the link status of wired link
 *
 * @param[out]  is_up: the wired link status is up or not
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_wired_get_status(TKL_WIRED_STAT_E *status)
{
    // --- BEGIN: user implements ---
    FILE *fp = NULL;
    char *str = NULL;
    char buf[256] = {0};
    char state[16] = {0};
    int sock = -1;
    struct ifreq ifr;
    char devname[IFNAMSIZ];
    int ret = -1;
    int plugged = 0;

    ret = net_utils_get_devname(devname, sizeof(devname));
    if (ret < 0) {
        printf("%s %d: not get devname\n", __func__, __LINE__);
        return OPRT_COM_ERROR;
    }

    /* The network cable connection is normal, check whether the ip exists */
    /* Create socket */
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("socket create fail\n");
        return OPRT_SOCK_ERR;
    }

    ret = net_utils_check_cable(devname, &plugged);
    if (!plugged) {
        close(sock);
        *status = TKL_WIRED_LINK_DOWN;
        return OPRT_OK;
    }

    memset(&ifr, 0x0, sizeof(ifr));
    strncpy(ifr.ifr_name, devname, sizeof(ifr.ifr_name) - 1);

    /* get ip addr */
    if (ioctl(sock, SIOCGIFADDR, &ifr) < 0) {
        printf("%s %d: %s get ip, error=%d,%s\n", __func__, __LINE__, devname, errno, strerror(errno));
        close(sock);
        *status = TKL_WIRED_LINK_DOWN;
        return OPRT_OK;
    }
    close(sock);
    *status = TKL_WIRED_LINK_UP;

    return OPRT_OK;
    // --- END: user implements ---
}

/**
 * @brief  set the status change callback
 *
 * @param[in]   cb: the callback when link status changed
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_wired_set_status_cb(TKL_WIRED_STATUS_CHANGE_CB cb)
{
    // --- BEGIN: user implements ---
    pthread_t thread;

    status_cb = cb;

    return pthread_create(&thread, NULL, link_status_thread, NULL);
    // --- END: user implements ---
}

/**
 * @brief  get the ip address of the wired link
 *
 * @param[in]   ip: the ip address
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_wired_get_ip(NW_IP_S *ip)
{
    // --- BEGIN: user implements ---
    int sock = -1;
    struct ifreq ifr;
    struct sockaddr_in *sin;
    char devname[IFNAMSIZ];
    int ret = -1;

    if (ip == NULL) {
        printf("invalid param\n");
        return OPRT_INVALID_PARM;
    }

    ret = net_utils_get_devname(devname, sizeof(devname));
    if (ret < 0) {
        printf("%s %d: not get devname\n", __func__, __LINE__);
        return OPRT_COM_ERROR;
    }

    printf("%s %d: ifname=%s\n", __func__, __LINE__, devname);

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("socket create failed\n");
        return OPRT_SOCK_ERR;
    }

    memset(ip, 0x0, sizeof(NW_IP_S));
    memset(&ifr, 0x0, sizeof(ifr));
    strncpy(ifr.ifr_name, devname, sizeof(ifr.ifr_name) - 1);
    sin = (struct sockaddr_in *)&(ifr.ifr_addr);

    /* get ip addr */
    if (ioctl(sock, SIOCGIFADDR, &ifr) < 0) {
        printf("%s %d: ip ioctl %s, error=%d,%s\n", __func__, __LINE__, devname, errno, strerror(errno));
        goto com_error;
    }
    strncpy(ip->ip, inet_ntoa(sin->sin_addr), sizeof(ip->ip));

    /* get net mask */
    if (ioctl(sock, SIOCGIFNETMASK, &ifr) < 0) {
        printf("%s %d: mask ioctl %s, error=%d,%s\n", __func__, __LINE__, devname, errno, strerror(errno));
        goto com_error;
    }
    strncpy(ip->mask, inet_ntoa(sin->sin_addr), sizeof(ip->mask));

    /* get gateway */
    if (tkl_wired_get_gateway(ip) < 0)
        goto com_error;

    close(sock);
    return OPRT_OK;

com_error:
    close(sock);
    return OPRT_COM_ERROR;
    // --- END: user implements ---
}

/**
 * @brief  get the mac address of the wired link
 *
 * @param[in]   mac: the mac address
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_wired_get_mac(NW_MAC_S *mac)
{
    // --- BEGIN: user implements ---
    int i;
    int sock = -1;
    struct ifreq ifr;
    struct sockaddr *addr;
    char devname[IFNAMSIZ];
    int ret = -1;

    if (mac == NULL) {
        printf("invalid param\n");
        return OPRT_INVALID_PARM;
    }

    ret = net_utils_get_devname(devname, sizeof(devname));
    if (ret < 0) {
        printf("%s %d: not get devname\n", __func__, __LINE__);
        return OPRT_COM_ERROR;
    }

    printf("%s %d: ifname=%s\n", __func__, __LINE__, devname);

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("socket create fail\n");
        return OPRT_SOCK_ERR;
    }

    memset(&ifr, 0x0, sizeof(ifr));
    strncpy(ifr.ifr_name, devname, sizeof(ifr.ifr_name) - 1);
    addr = (struct sockaddr *)&ifr.ifr_hwaddr;
    addr->sa_family = 1;

    /* get mac addr */
    if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) {
        printf("%s %d: %s get mac, error=%d,%s\n", __func__, __LINE__, devname, errno, strerror(errno));
        close(sock);
        return OPRT_COM_ERROR;
    }

    memcpy(mac->mac, addr->sa_data, MAC_ADDR_LEN);

    close(sock);
    return OPRT_OK;
    // --- END: user implements ---
}

/**
 * @brief  set the mac address of the wired link
 *
 * @param[in]   mac: the mac address
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_wired_set_mac(const NW_MAC_S *mac)
{
    // --- BEGIN: user implements ---
    int i;
    int sock = -1;
    struct ifreq ifr;
    struct sockaddr *addr;
    char devname[IFNAMSIZ];
    int ret = -1;

    if (mac == NULL) {
        printf("invalid param\n");
        return OPRT_INVALID_PARM;
    }
    printf("%s %d: %02x:%02x:%02x:%02x:%02x:%02x\n", __func__, __LINE__, mac->mac[0], mac->mac[1], mac->mac[2],
           mac->mac[3], mac->mac[4], mac->mac[5]);

    ret = net_utils_get_devname(devname, sizeof(devname));
    if (ret < 0) {
        printf("%s %d: not get devname\n", __func__, __LINE__);
        return OPRT_COM_ERROR;
    }

    printf("%s %d: ifname=%s\n", __func__, __LINE__, devname);

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("socket create fail\n");
        return OPRT_SOCK_ERR;
    }

    memset(&ifr, 0x0, sizeof(ifr));
    strncpy(ifr.ifr_name, devname, sizeof(ifr.ifr_name) - 1);
    addr = (struct sockaddr *)&ifr.ifr_hwaddr;
    addr->sa_family = 1;
    memcpy(addr->sa_data, mac->mac, MAC_ADDR_LEN);

    /* set mac addr */
    if (ioctl(sock, SIOCSIFHWADDR, &ifr) < 0) {
        printf("%s %d: %s set mac, error=%d,%s\n", __func__, __LINE__, devname, errno, strerror(errno));
        close(sock);
        return OPRT_COM_ERROR;
    }

    close(sock);
    return OPRT_OK;
    // --- END: user implements ---
}
