/*
 * 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 <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

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

typedef struct {
    const char *name;
    void (*func)(int32_t argc, char **argv);
} option_t;

enum {
    RELAY_MODE_CONNECT    = 0,
    RELAY_MODE_DISCONNECT = 1,
    RELAY_MODE_BYPASS     = 2,
    RELAY_MODE_QUERY      = 0x3F
};

enum {
    RESET_MODE_OFF   = 0,
    RESET_MODE_ON    = 1,
    RESET_MODE_QUERY = 0x3F
};

enum {
    APP_WDT_TIMEOUT_DISABLE = 0,
    APP_WDT_TIMEOUT_ENABLE  = 1,
    APP_WDT_TIMEOUT_QUERY   = 0x3F,
};

enum {
    UPGRADE_MODE_FIN    = 0,
    UPGRADE_MODE_START  = 1,
    UPGRADE_MODE_DELETE = 2,
};

static const char *
get_relay_mode_string(uint8_t mode)
{
    switch (mode) {
    case RELAY_MODE_CONNECT:
        return "connect";
    case RELAY_MODE_DISCONNECT:
        return "disconnect";
    case RELAY_MODE_BYPASS:
        return "bypass";
    default:
        return "unknown";
    }
}

static const char *get_reset_mode_string(uint8_t mode)
{
    switch (mode) {
    case RESET_MODE_OFF:
        return "off";
    case RESET_MODE_ON:
        return "on";
    default:
        return "unknown";
    }
}

static const char *get_app_wdt_timeout_string(uint8_t mode)
{
    switch (mode) {
    case APP_WDT_TIMEOUT_DISABLE:
        return "disabled";
    case APP_WDT_TIMEOUT_ENABLE:
        return "enabled";
    default:
        return "unknown";
    }
}

void print_help()
{
    const char                                                                                        *message =
        "\n" MOXA_COLOR_FG "MOXA" RESET_COLOR " MCU Management Command-line Utility\n\n" MOXA_COLOR_BG WHITE_COLOR_FB " Usage: " RESET_COLOR "\n"
        "    mx-mcu-mgmt [command]\n\n" MOXA_COLOR_BG WHITE_COLOR_FB " Flags: " RESET_COLOR "\n"
        "    -h, --help       Prints help information\n"
        "    -v, --version    Prints utility version\n\n" MOXA_COLOR_BG WHITE_COLOR_FB " Commands: " RESET_COLOR "\n"
        "    mcu_version      Get MCU firmware version\n"
        "    mcu_upgrade      Upgrade MCU firmware version\n"
        "    relay            Control relay mode\n"
        "    wdt_relay        Control watchdog relay mode\n"
        "    poweroff_relay   Control power off (S5) relay mode\n"
        "    app_wdt_relay    Control app watchdog relay mode\n"
        "    app_wdt_timeout  Control app watchdog timeout\n";

    printf("%s", message);
}

ssize_t send_cmd(int32_t fd, char *command, uint8_t *data, size_t data_sz, uint8_t *recv_buff)
{
    ssize_t                pkt_sz;
    socket_request_packet  req_pkt;
    socket_response_packet resp_pkt;

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

    if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
        return -1;
    }

    pkt_sz = make_socket_packet(&req_pkt, command, data, data_sz);

    pkt_sz = write(fd, &req_pkt, pkt_sz);
    if (pkt_sz < 0) {
        return -1;
    }

    pkt_sz = read(fd, &resp_pkt, sizeof(resp_pkt));
    if (pkt_sz < 0) {
        return -1;
    }

    if (resp_pkt.return_code != ERR_CODE_OK) {
        printf("Error: %s\n", get_error_string(resp_pkt.return_code));
        return -1;
    }

    if (resp_pkt.payload.data_sz <= MAX_DATA_SZ) {
        memcpy(recv_buff, resp_pkt.payload.data, resp_pkt.payload.data_sz);
    }
    else {
        memcpy(recv_buff, resp_pkt.payload.data, MAX_DATA_SZ);
    }

    return resp_pkt.payload.data_sz;
}

static bool mcu_firmware_valiade(const char *file_path)
{
    int32_t fd;
    uint8_t offset[1] = {0};
    uint8_t header[4] = {0};

    errno = 0;
    fd    = open(file_path, O_RDONLY);
    if (fd < 0) {
        printf("Error: %s\n", strerror(errno));
        return false;
    }

    errno = 0;
    if (pread(fd, offset, 1, MCU_BIN_HEADER_BASE | (MCU_BIN_HEADER_INDEX_OFFSET & 0xff)) == -1) {
        printf("Error: %s\n", strerror(errno));
        close_fd(fd);
        return false;
    }

    if (pread(fd, header, 4, MCU_BIN_HEADER_BASE | (offset[0] & 0xff)) == -1) {
        printf("Error: %s\n", strerror(errno));
        close_fd(fd);
        return false;
    }

    if (MCU_BIN_HEADER_0 != (header[0] & 0xff) || MCU_BIN_HEADER_1 != (header[1] & 0xff) ||
        MCU_BIN_HEADER_2 != (header[2] & 0xff) || MCU_BIN_HEADER_3 != (header[3] & 0xff)) {
        close_fd(fd);
        return false;
    }

    close_fd(fd);
    return true;
}

#define BAR_WIDTH 50

static void show_progress(int32_t step, int32_t total_steps)
{
    int32_t percent = (step * 100) / total_steps;
    int32_t pos     = percent * BAR_WIDTH / 100;

    printf("\r[");
    for (int32_t i = 0; i < BAR_WIDTH; i++) {
        if (i < pos)
            printf("#");
        else
            printf(" ");
    }
    printf("] %3d%%", percent);
    fflush(stdout);
}

void option_mcu_upgrade(int32_t argc, char **argv)
{
    const char       *help_message =
        MOXA_COLOR_BG WHITE_COLOR_FB " Usage: " RESET_COLOR "\n"
                                     "    Upgrade MCU firmware version\n"
                                     "        $ mx-mcu-mgmt mcu_upgrade [file]\n\n" MOXA_COLOR_BG WHITE_COLOR_FB " Arguments: " RESET_COLOR "\n"
                                     "    file: MCU firmware binary file\n\n";

    const char *fw_file_path = argv[0];
    int32_t     choice;
    uint8_t     send_buff[1] = {0};
    uint8_t     recv_buff[1] = {0};
    int32_t     cli_fd       = 0;
    ssize_t     recv_sz      = 0;
    int32_t     f_fd         = 0;
    ssize_t     f_sz         = 0;
    uint8_t     f_read_buff[MCU_BIN_PACKET_MAX];
    uint8_t     f_write_buff[MCU_BIN_PACKET_MAX + 1]; /* DATA0 (Package index) + DATA1~DATA128 */
    uint32_t    pkt_num     = 0;
    uint32_t    pkt_idx     = 0;
    uint32_t    data_offset = 0;

    if (argc == 1) {
        if (!mcu_firmware_valiade(fw_file_path)) {
            printf("Error: Invalid MCU firmware binary file\n");
            exit(EXIT_FAILURE);
        }
    }
    else {
        printf("%s", help_message);
        exit(EXIT_SUCCESS);
    }

    printf("MCU firmware file path: %s\n", fw_file_path);
    printf("Do you want to upgrade the firmware? (y/n): ");
    choice = tolower(getchar());
    if (choice != 'y') {
        printf("Abort\n");
        exit(EXIT_SUCCESS);
    }

    cli_fd = create_unix_skt_client(MX_MCU_DAEMON_UNIX_SOCK_PATH);
    if (cli_fd < 0) {
        printf("Error: daemon is not available\n");
        exit(EXIT_FAILURE);
    }

    // Enter MCU start mode
    send_buff[0] = UPGRADE_MODE_START;
    recv_sz      = send_cmd(cli_fd, MX_MCU_FW_UPGRADE_CMD, send_buff, 1, recv_buff);
    if (recv_sz < 0 || send_buff[0] != recv_buff[0]) {
        printf("Error: Enter MCU upgrade mode failed\n");
        close_fd(cli_fd);
        exit(EXIT_FAILURE);
    }
    sleep_ms(500); /* wait 500 ms for MCU enter upgrade mode */

    // Enter MCU delete mode
    send_buff[0] = UPGRADE_MODE_DELETE;
    recv_sz      = send_cmd(cli_fd, MX_MCU_FW_UPGRADE_CMD, send_buff, 1, recv_buff);
    if (recv_sz < 0 || send_buff[0] != recv_buff[0]) {
        printf("Error: Enter MCU delete mode failed\n");
        close_fd(cli_fd);
        exit(EXIT_FAILURE);
    }
    sleep_ms(100); /* wait 100 ms for MCU delete old fw */

    // Upgrade binary...
    printf("Upgrade in progress, please DO NOT power off the system!\n");

    f_fd = open(fw_file_path, O_RDONLY);
    if (f_fd < 0) {
        printf("Error: open firmware binary file failed\n");
        close_fd(cli_fd);
        exit(EXIT_FAILURE);
    }

    f_sz    = lseek(f_fd, 0, SEEK_END);
    pkt_num = (f_sz - MCU_BIN_WRITE_BASE) / MCU_BIN_PACKET_MAX + ((f_sz - MCU_BIN_WRITE_BASE) % MCU_BIN_PACKET_MAX != 0);

    for (uint32_t i = 0; i < pkt_num; i++) {
        show_progress(i, pkt_num - 1);

        memset(f_read_buff, ~0, sizeof(char) * MCU_BIN_PACKET_MAX);
        if (pread(f_fd, f_read_buff, MCU_BIN_PACKET_MAX, MCU_BIN_WRITE_BASE + data_offset) == -1) {
            printf("Error: Upgrade binary failed\n");
            close_fd(f_fd);
            close_fd(cli_fd);
            exit(EXIT_FAILURE);
        }

        /*
         * MCU Packet Format
         *
         * Each packet follows the structure below:
         *
         * ┌────────┬────────┬────────────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┬─────────┬───────┐
         * │  ATTN  │  ADDR  │       CMD      │  LEN   │ IHCHK  │ DATA0  │ DATA1  │ DATA2  │ DATA3  │  ...   │ DATA128 │ IDCHK │
         * ├────────┼────────┼────────────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┼─────────┼───────┤
         * │ 0x07   │ 0xFF   │ 0x53 0x46 0x55 │ 0x81   │ 0x8A   │ 0x43   │ 0x21   │ 0xA1   │ 0x5E   │  ...   │ 0xF4    │ 0xFF  │
         * └────────┴────────┴────────────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┴─────────┴───────┘
         * - DATA0: Package index (Range: 0x00 ~ 0xFF)
         *
         * - Last Packet:
         * If the last packet contains less than MCU_BIN_PACKET_MAX bytes, the remaining data should be padded with 0xFF.
         *
         * Example of a partially filled last packet:
         *
         * ┌────────┬────────┬────────────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┬─────────┬───────┐
         * │  ATTN  │  ADDR  │       CMD      │  LEN   │ IHCHK  │ DATA0  │ DATA1  │ DATA2  │ DATA3  │  ...   │ DATA128 │ IDCHK │
         * ├────────┼────────┼────────────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┼─────────┼───────┤
         * │ 0x07   │ 0xFF   │ 0x53 0x46 0x55 │ 0x81   │ 0x8A   │ 0xAA   │ 0x21   │ 0xA1   │ 0xFF   │  ...   │ 0xFF    │ 0xF0  │
         * └────────┴────────┴────────────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┴─────────┴───────┘
         *
         */

        f_write_buff[0] = pkt_idx;
        memcpy(f_write_buff + 1, f_read_buff, MCU_BIN_PACKET_MAX);

        recv_sz = send_cmd(cli_fd, MX_MCU_FW_UPGRADE_CMD, f_write_buff, MCU_BIN_PACKET_MAX + 1, recv_buff);
        if (recv_sz < 0 || recv_buff[0] != pkt_idx) {
            printf("Error: Upgrade binary failed\n");
            close_fd(f_fd);
            close_fd(cli_fd);
            exit(EXIT_FAILURE);
        }

        data_offset += MCU_BIN_PACKET_MAX;
        pkt_idx++;
    }

    close_fd(f_fd);

    // Enter MCU finish mode
    send_buff[0] = UPGRADE_MODE_FIN;
    recv_sz      = send_cmd(cli_fd, MX_MCU_FW_UPGRADE_CMD, send_buff, 1, recv_buff);
    if (recv_sz < 0 || send_buff[0] != recv_buff[0]) {
        printf("Error: Enter MCU finish mode failed\n");
        close_fd(cli_fd);
        exit(EXIT_FAILURE);
    }

    printf("\nUpgrade completed! Please reboot the system.\n");
    close_fd(cli_fd);
    exit(EXIT_SUCCESS);
}

void option_mcu_version(int32_t argc, char **argv)
{
    int32_t cli_fd;
    uint8_t recv_buff[32] = {0};
    ssize_t recv_sz       = 0;

    (void)argc;
    (void)argv;

    cli_fd = create_unix_skt_client(MX_MCU_DAEMON_UNIX_SOCK_PATH);
    if (cli_fd < 0) {
        printf("Error: daemon is not available\n");
        exit(EXIT_FAILURE);
    }

    recv_sz = send_cmd(cli_fd, MX_MCU_VERSION_CMD, NULL, 0, recv_buff);
    if (recv_sz < 0) {
        printf("Error: get MCU version failed\n");
        exit(EXIT_FAILURE);
    }

    printf("%s\n", recv_buff);

    close_fd(cli_fd);

    exit(EXIT_SUCCESS);
}

void option_control_relay_mode(int32_t argc, char **argv)
{
    int32_t           cli_fd;
    uint8_t           send_buff[1] = {0};
    uint8_t           recv_buff[1] = {0};
    ssize_t           recv_sz      = 0;
    const char       *help_message =
        MOXA_COLOR_BG WHITE_COLOR_FB " Usage: " RESET_COLOR "\n"
                                     "    Get relay mode\n"
                                     "        $ mx-mcu-mgmt relay get_mode\n"
                                     "    Set relay mode\n"
                                     "        $ mx-mcu-mgmt relay set_mode [mode]\n\n" MOXA_COLOR_BG WHITE_COLOR_FB " Arguments: " RESET_COLOR "\n"
                                     "    mode: connect|disconnect|bypass\n\n";

    if (argc == 1 && !strcmp(argv[0], "get_mode")) {
        send_buff[0] = RELAY_MODE_QUERY;
    }
    else if (argc == 2 && !strcmp(argv[0], "set_mode")) {
        if (!strcmp(argv[1], "connect")) {
            send_buff[0] = RELAY_MODE_CONNECT;
        }
        else if (!strcmp(argv[1], "disconnect")) {
            send_buff[0] = RELAY_MODE_DISCONNECT;
        }
        else if (!strcmp(argv[1], "bypass")) {
            send_buff[0] = RELAY_MODE_BYPASS;
        }
        else {
            printf("Error: invalid relay mode\n");
            printf("%s", help_message);
            exit(EXIT_FAILURE);
        }
    }
    else {
        printf("%s", help_message);
        exit(EXIT_SUCCESS);
    }

    cli_fd = create_unix_skt_client(MX_MCU_DAEMON_UNIX_SOCK_PATH);
    if (cli_fd < 0) {
        printf("Error: daemon is not available\n");
        exit(EXIT_FAILURE);
    }

    recv_sz = send_cmd(cli_fd, MX_MCU_RELAY_MODE_CMD, send_buff, 1, recv_buff);

    if (argc == 1) {
        if (recv_sz < 0) {
            printf("Error: get relay mode failed\n");
            exit(EXIT_FAILURE);
        }
        else {
            printf("%s\n", get_relay_mode_string(recv_buff[0]));
        }
    }
    else {
        if (recv_sz < 0 || send_buff[0] != recv_buff[0]) {
            printf("Error: set relay mode failed\n");
            exit(EXIT_FAILURE);
        }
    }

    close_fd(cli_fd);
    exit(EXIT_SUCCESS);
}

void option_control_wdt_reset_mode(int32_t argc, char **argv)
{
    int32_t           cli_fd;
    uint8_t           send_buff[1] = {0};
    uint8_t           recv_buff[1] = {0};
    ssize_t           recv_sz      = 0;
    const char       *help_message =
        MOXA_COLOR_BG WHITE_COLOR_FB " Usage: " RESET_COLOR "\n"
                                     "    Get watchdog reset mode\n"
                                     "        $ mx-mcu-mgmt wdt_reset get_mode\n"
                                     "    Set watchdog reset mode\n"
                                     "        $ mx-mcu-mgmt wdt_reset set_mode [mode]\n\n" MOXA_COLOR_BG WHITE_COLOR_FB " Arguments: " RESET_COLOR "\n"
                                     "    mode: off|on\n\n";

    if (argc == 1 && !strcmp(argv[0], "get_mode")) {
        send_buff[0] = RESET_MODE_QUERY;
    }
    else if (argc == 2 && !strcmp(argv[0], "set_mode")) {
        if (!strcmp(argv[1], "off")) {
            send_buff[0] = RESET_MODE_OFF;
        }
        else if (!strcmp(argv[1], "on")) {
            send_buff[0] = RESET_MODE_ON;
        }
        else {
            printf("Error: invalid reset mode\n");
            printf("%s", help_message);
            exit(EXIT_FAILURE);
        }
    }
    else {
        printf("%s", help_message);
        exit(EXIT_SUCCESS);
    }

    cli_fd = create_unix_skt_client(MX_MCU_DAEMON_UNIX_SOCK_PATH);
    if (cli_fd < 0) {
        printf("Error: daemon is not available\n");
        exit(EXIT_FAILURE);
    }

    recv_sz = send_cmd(cli_fd, MX_MCU_WDT_RESET_MODE_CMD, send_buff, 1, recv_buff);

    if (argc == 1) {
        if (recv_sz < 0) {
            printf("Error: get reset mode failed\n");
            exit(EXIT_FAILURE);
        }
        else {
            printf("%s\n", get_reset_mode_string(recv_buff[0]));
        }
    }
    else {
        if (recv_sz < 0 || send_buff[0] != recv_buff[0]) {
            printf("Error: set reset mode failed\n");
            exit(EXIT_FAILURE);
        }
    }

    close_fd(cli_fd);
    exit(EXIT_SUCCESS);
}

void option_control_wdt_relay_mode(int32_t argc, char **argv)
{
    int32_t           cli_fd;
    uint8_t           send_buff[1] = {0};
    uint8_t           recv_buff[1] = {0};
    ssize_t           recv_sz      = 0;
    const char       *help_message =
        MOXA_COLOR_BG WHITE_COLOR_FB " Usage: " RESET_COLOR "\n"
                                     "    Get watchdog relay mode\n"
                                     "        $ mx-mcu-mgmt wdt_relay get_mode\n"
                                     "    Set watchdog relay mode\n"
                                     "        $ mx-mcu-mgmt wdt_relay set_mode [mode]\n\n" MOXA_COLOR_BG WHITE_COLOR_FB " Arguments: " RESET_COLOR "\n"
                                     "    mode: connect|disconnect|bypass\n\n";

    if (argc == 1 && !strcmp(argv[0], "get_mode")) {
        send_buff[0] = RELAY_MODE_QUERY;
    }
    else if (argc == 2 && !strcmp(argv[0], "set_mode")) {
        if (!strcmp(argv[1], "connect")) {
            send_buff[0] = RELAY_MODE_CONNECT;
        }
        else if (!strcmp(argv[1], "disconnect")) {
            send_buff[0] = RELAY_MODE_DISCONNECT;
        }
        else if (!strcmp(argv[1], "bypass")) {
            send_buff[0] = RELAY_MODE_BYPASS;
        }
        else {
            printf("Error: invalid relay mode\n");
            printf("%s", help_message);
            exit(EXIT_FAILURE);
        }
    }
    else {
        printf("%s", help_message);
        exit(EXIT_SUCCESS);
    }

    cli_fd = create_unix_skt_client(MX_MCU_DAEMON_UNIX_SOCK_PATH);
    if (cli_fd < 0) {
        printf("Error: daemon is not available\n");
        exit(EXIT_FAILURE);
    }

    recv_sz = send_cmd(cli_fd, MX_MCU_WDT_RELAY_MODE_CMD, send_buff, 1, recv_buff);

    if (argc == 1) {
        if (recv_sz < 0) {
            printf("Error: get relay mode failed\n");
            exit(EXIT_FAILURE);
        }
        else {
            printf("%s\n", get_relay_mode_string(recv_buff[0]));
        }
    }
    else {
        if (recv_sz < 0 || send_buff[0] != recv_buff[0]) {
            printf("Error: set relay mode failed\n");
            exit(EXIT_FAILURE);
        }
    }

    close_fd(cli_fd);
    exit(EXIT_SUCCESS);
}

void option_control_poweroff_relay_mode(int32_t argc, char **argv)
{
    int32_t           cli_fd;
    uint8_t           send_buff[1] = {0};
    uint8_t           recv_buff[1] = {0};
    ssize_t           recv_sz      = 0;
    const char       *help_message =
        MOXA_COLOR_BG WHITE_COLOR_FB " Usage: " RESET_COLOR "\n"
                                     "    Get power off relay mode\n"
                                     "        $ mx-mcu-mgmt poweroff_relay get_mode\n"
                                     "    Set power off relay mode\n"
                                     "        $ mx-mcu-mgmt poweroff_relay set_mode [mode]\n\n" MOXA_COLOR_BG WHITE_COLOR_FB " Arguments: " RESET_COLOR "\n"
                                     "    mode: disconnect|bypass\n\n";

    if (argc == 1 && !strcmp(argv[0], "get_mode")) {
        send_buff[0] = RELAY_MODE_QUERY;
    }
    else if (argc == 2 && !strcmp(argv[0], "set_mode")) {
        if (!strcmp(argv[1], "disconnect")) {
            send_buff[0] = RELAY_MODE_DISCONNECT;
        }
        else if (!strcmp(argv[1], "bypass")) {
            send_buff[0] = RELAY_MODE_BYPASS;
        }
        else {
            printf("Error: invalid relay mode\n");
            printf("%s", help_message);
            exit(EXIT_FAILURE);
        }
    }
    else {
        printf("%s", help_message);
        exit(EXIT_SUCCESS);
    }

    cli_fd = create_unix_skt_client(MX_MCU_DAEMON_UNIX_SOCK_PATH);
    if (cli_fd < 0) {
        printf("Error: daemon is not available\n");
        exit(EXIT_FAILURE);
    }

    recv_sz = send_cmd(cli_fd, MX_MCU_POWEROFF_RELAY_MODE_CMD, send_buff, 1, recv_buff);

    if (argc == 1) {
        if (recv_sz < 0) {
            printf("Error: get relay mode failed\n");
            exit(EXIT_FAILURE);
        }
        else {
            printf("%s\n", get_relay_mode_string(recv_buff[0]));
        }
    }
    else {
        if (recv_sz < 0 || send_buff[0] != recv_buff[0]) {
            printf("Error: set relay mode failed\n");
            exit(EXIT_FAILURE);
        }
    }

    close_fd(cli_fd);
    exit(EXIT_SUCCESS);
}

void option_control_app_wdt_reset_mode(int32_t argc, char **argv)
{
    int32_t           cli_fd;
    uint8_t           send_buff[1] = {0};
    uint8_t           recv_buff[1] = {0};
    ssize_t           recv_sz      = 0;
    const char       *help_message =
        MOXA_COLOR_BG WHITE_COLOR_FB " Usage: " RESET_COLOR "\n"
                                     "    Get app watchdog reset mode\n"
                                     "        $ mx-mcu-mgmt app_wdt_reset get_mode\n"
                                     "    Set app watchdog reset mode\n"
                                     "        $ mx-mcu-mgmt app_wdt_reset set_mode [mode]\n\n" MOXA_COLOR_BG WHITE_COLOR_FB " Arguments: " RESET_COLOR "\n"
                                     "    mode: off|on\n\n";

    if (argc == 1 && !strcmp(argv[0], "get_mode")) {
        send_buff[0] = RESET_MODE_QUERY;
    }
    else if (argc == 2 && !strcmp(argv[0], "set_mode")) {
        if (!strcmp(argv[1], "off")) {
            send_buff[0] = RESET_MODE_OFF;
        }
        else if (!strcmp(argv[1], "on")) {
            send_buff[0] = RESET_MODE_ON;
        }
        else {
            printf("Error: invalid reset mode\n");
            printf("%s", help_message);
            exit(EXIT_FAILURE);
        }
    }
    else {
        printf("%s", help_message);
        exit(EXIT_SUCCESS);
    }

    cli_fd = create_unix_skt_client(MX_MCU_DAEMON_UNIX_SOCK_PATH);
    if (cli_fd < 0) {
        printf("Error: daemon is not available\n");
        exit(EXIT_FAILURE);
    }

    recv_sz = send_cmd(cli_fd, MX_MCU_APP_WDT_RESET_MODE_CMD, send_buff, 1, recv_buff);

    if (argc == 1) {
        if (recv_sz < 0) {
            printf("Error: get reset mode failed\n");
            exit(EXIT_FAILURE);
        }
        else {
            printf("%s\n", get_reset_mode_string(recv_buff[0]));
        }
    }
    else {
        if (recv_sz < 0 || send_buff[0] != recv_buff[0]) {
            printf("Error: set reset mode failed\n");
            exit(EXIT_FAILURE);
        }
    }

    close_fd(cli_fd);
    exit(EXIT_SUCCESS);
}

void control_app_wdt_relay_mode(int32_t argc, char **argv)
{
    int32_t           cli_fd;
    uint8_t           send_buff[1] = {0};
    uint8_t           recv_buff[1] = {0};
    ssize_t           recv_sz      = 0;
    const char       *help_message =
        MOXA_COLOR_BG WHITE_COLOR_FB " Usage: " RESET_COLOR "\n"
                                     "    Get app watchdog relay mode\n"
                                     "        $ mx-mcu-mgmt app_wdt_relay get_mode\n"
                                     "    Set app watchdog relay mode\n"
                                     "        $ mx-mcu-mgmt app_wdt_relay set_mode [mode]\n\n" MOXA_COLOR_BG WHITE_COLOR_FB " Arguments: " RESET_COLOR "\n"
                                     "    mode: connect|disconnect|bypass\n\n";

    if (argc == 1 && !strcmp(argv[0], "get_mode")) {
        send_buff[0] = RELAY_MODE_QUERY;
    }
    else if (argc == 2 && !strcmp(argv[0], "set_mode")) {
        if (!strcmp(argv[1], "connect")) {
            send_buff[0] = RELAY_MODE_CONNECT;
        }
        else if (!strcmp(argv[1], "disconnect")) {
            send_buff[0] = RELAY_MODE_DISCONNECT;
        }
        else if (!strcmp(argv[1], "bypass")) {
            send_buff[0] = RELAY_MODE_BYPASS;
        }
        else {
            printf("Error: invalid relay mode\n");
            printf("%s", help_message);
            exit(EXIT_FAILURE);
        }
    }
    else {
        printf("%s", help_message);
        exit(EXIT_SUCCESS);
    }

    cli_fd = create_unix_skt_client(MX_MCU_DAEMON_UNIX_SOCK_PATH);
    if (cli_fd < 0) {
        printf("Error: daemon is not available\n");
        exit(EXIT_FAILURE);
    }

    recv_sz = send_cmd(cli_fd, MX_MCU_APP_WDT_RELAY_MODE_CMD, send_buff, 1, recv_buff);

    if (argc == 1) {
        if (recv_sz < 0) {
            printf("Error: get relay mode failed\n");
        }
        else {
            printf("%s\n", get_relay_mode_string(recv_buff[0]));
        }
    }
    else {
        if (recv_sz < 0 || send_buff[0] != recv_buff[0]) {
            printf("Error: set relay mode failed\n");
            exit(EXIT_FAILURE);
        }
    }

    close_fd(cli_fd);
    exit(EXIT_SUCCESS);
}

void control_app_wdt_timeout(int32_t argc, char **argv)
{
    int32_t           cli_fd;
    uint8_t           send_buff[2] = {0};
    uint8_t           recv_buff[2] = {0};
    ssize_t           send_sz      = 0;
    ssize_t           recv_sz      = 0;
    const char       *help_message =
        MOXA_COLOR_BG WHITE_COLOR_FB " Usage: " RESET_COLOR "\n"
                                     "    Get app watchdog timeout\n"
                                     "        $ mx-mcu-mgmt app_wdt_timeout get_timeout\n"
                                     "    Set app watchdog timeout\n"
                                     "        $ mx-mcu-mgmt app_wdt_timeout set_timeout [seconds]\n\n" MOXA_COLOR_BG WHITE_COLOR_FB " Arguments: " RESET_COLOR "\n"
                                     "    seconds: disable:0, enable:5-15\n\n";

    if (argc == 1 && !strcmp(argv[0], "get_timeout")) {
        send_buff[0] = APP_WDT_TIMEOUT_QUERY;
        send_sz      = 1;
    }
    else if (argc == 2 && !strcmp(argv[0], "set_timeout")) {
        send_buff[1] = atoi(argv[1]);

        if (send_buff[1] == 0) {
            send_buff[0] = APP_WDT_TIMEOUT_DISABLE;
            send_sz      = 1;
        }
        else if (5 <= send_buff[1] && send_buff[1] <= 15) {
            send_buff[0] = APP_WDT_TIMEOUT_ENABLE;
            send_sz      = 2;
        }

        else {
            printf("Error: invalid timeout value\n");
            printf("%s", help_message);
            exit(EXIT_FAILURE);
        }
    }
    else {
        printf("%s", help_message);
        exit(EXIT_SUCCESS);
    }

    cli_fd = create_unix_skt_client(MX_MCU_DAEMON_UNIX_SOCK_PATH);
    if (cli_fd < 0) {
        printf("Error: daemon is not available\n");
        exit(EXIT_FAILURE);
    }

    recv_sz = send_cmd(cli_fd, MX_MCU_APP_WDT_TIMEOUT_CMD, send_buff, send_sz, recv_buff);

    if (argc == 1) {
        if (recv_sz < 0) {
            printf("Error: get app watchdog timeout failed\n");
            exit(EXIT_FAILURE);
        }
        else {
            if (recv_buff[0] == APP_WDT_TIMEOUT_DISABLE) {
                printf("%s\n", get_app_wdt_timeout_string(recv_buff[0]));
            }
            else if (recv_buff[0] == APP_WDT_TIMEOUT_ENABLE) {
                printf("%d\n", recv_buff[1]);
            }
            else {
                printf("unknown\n");
                exit(EXIT_FAILURE);
            }
        }
    }
    else {
        if (recv_sz < 0 || send_buff[0] != recv_buff[0]) {
            printf("Error: set app watchdog timeout failed\n");
            exit(EXIT_FAILURE);
        }
    }

    close_fd(cli_fd);
    exit(EXIT_SUCCESS);
}

static option_t options[] = {
    {"mcu_version", option_mcu_version},
    {"mcu_upgrade", option_mcu_upgrade},
    {"relay", option_control_relay_mode},
    {"wdt_relay", option_control_wdt_relay_mode},
    {"poweroff_relay", option_control_poweroff_relay_mode},
    {"app_wdt_relay", control_app_wdt_relay_mode},
    {"app_wdt_timeout", control_app_wdt_timeout},
    {NULL, NULL}};

static bool handle_option(int32_t argc, char **argv)
{
    for (int32_t i = 0; options[i].name != NULL; ++i) {
        if (!strcmp(argv[0], options[i].name)) {
            options[i].func(argc - 1, argv + 1);
            return true;
        }
    }
    return false;
}

int32_t main(int32_t argc, char **argv)
{
    if (argc == 1) {
        print_help();
        return 0;
    }
    else if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
        print_help();
        return 0;
    }
    else if (argc == 2 && (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version"))) {
        // Project version macro is passed from automake configure
        printf("%s\n", PROJECT_VERSION);
        return 0;
    }
    else {
        if (!handle_option(argc - 1, argv + 1)) {
            printf("Error: Unknown command\n");
            print_help();
            return -1;
        }
    }

    return 0;
}
