/*
 * Copyright (C) MOXA Inc. All rights reserved.
 * Authors:
 *     2024  Wilson YS Huang  <wilsonys.huang@moxa.com>
 * This software is distributed under the terms of the MOXA SOFTWARE NOTICE.
 * See the file LICENSE for details.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

#include <logger.h>
#include <mcu.h>
#include <socket.h>

const char *get_error_string(uint8_t err)
{
    switch (err) {
    case ERR_CODE_COMMAND_NOT_SUPPORT:
        return "Command is not support";
    case ERR_CODE_COMMAND_ILLEGAL_MCU_PKT:
        return "Illegal MCU packet";
    case ERR_CODE_COMMAND_NACK_MCU_PKT:
        return "Negative MCU ACK Packet";
    case ERR_CODE_COMMAND_UNEXPECTED_RESP:
        return "Unexpected response";
    case ERR_CODE_MCU_NOT_AVAILABLE:
        return "MCU is not available";
    case ERR_CODE_UNKNOWN:
    default:
        return "Unknown";
    }

    return NULL;
}

bool is_valid_fd(int32_t fd)
{
    errno = 0;
    return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}

void close_fd(int32_t fd)
{
    if (is_valid_fd(fd))
        close(fd);
}

int32_t create_unix_socket(struct sockaddr_un *addr_un, const char *unix_path)
{
    int32_t fd;

    fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);

    addr_un->sun_family = AF_UNIX;
    strncpy(addr_un->sun_path, unix_path, sizeof(addr_un->sun_path) - 1);

    return fd;
}

int32_t create_unix_skt_client(const char *unix_server_path)
{
    struct sockaddr_un addr = {0};
    int32_t            fd   = 0;

    errno = 0;
    if ((fd = create_unix_socket(&addr, unix_server_path)) == -1) {
        goto err_close;
    }

    errno = 0;
    if (connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
        goto err_close;
    }

    return fd;

err_close:
    log_error("Create unix client socket failed.: %s(%d)\n", strerror(errno), errno);
    close_fd(fd);
    return -1;
}

int32_t create_unix_skt_server(const char *unix_path)
{
    struct sockaddr_un addr = {0};
    int32_t            fd   = 0;

    log_debug("Unix socket server path: %s", unix_path);

    errno = 0;
    if (remove(unix_path) == -1 && errno != ENOENT) {
        log_error("Remove unix socket path: %s", unix_path);
        return -1;
    }

    if ((fd = create_unix_socket(&addr, unix_path)) == -1) {
        log_error("Create unix sokcet server failed.");
        goto err_close;
    }

    errno = 0;
    if (fchmod(fd, S_IRUSR | S_IWUSR) == -1) {
        log_error("Set permissions on socket failed. %s", strerror(errno));
        goto err_unlink;
    }

    errno = 0;
    if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
        log_error("Bind unix socket failed %s", strerror(errno));
        goto err_unlink;
    }

    if (listen(fd, 5) == -1) {
        log_error("Listen unix socket failed.");
        goto err_unlink;
    }

    return fd;

err_close:
    close_fd(fd);
    return -1;

err_unlink:
    unlink(unix_path);
    close_fd(fd);
    return -1;
}

size_t make_socket_packet(socket_request_packet *skt_pkt, char command[3], uint8_t *data, size_t data_sz)
{
    socket_request_packet *pkt    = NULL;
    size_t                 pkt_sz = 0;

    if (skt_pkt == NULL) {
        return -1;
    }

    pkt = (socket_request_packet *)calloc(1, sizeof(socket_request_packet) + data_sz);

    memcpy(pkt->command, command, MX_MCU_CMD_SIZE);

    if (data != NULL) {
        pkt->payload.data_sz = data_sz;
        memcpy(pkt->payload.data, data, data_sz);
    }
    else {
        pkt->payload.data_sz = 0;
    }

    pkt_sz = sizeof(socket_request_packet);

    memcpy(skt_pkt, pkt, pkt_sz);
    free(pkt);

    return pkt_sz;
}
