#include "lboot_mbtcp.h"
#include "lboot_mb_shared.h"
#include "modbus-tcp.h"
#include "getopt.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/** максимальный размер блока */
#define LBOOT_MBTCP_MAX_BLOCK_SIZE  200


static int f_parse_option(int opt);
static const char* f_get_opt_descr(void);
static const char* f_get_err_str(int err);
static int f_open(const char* devname, const char* serial, const void* params,
                  t_lboot_devinfo* info, int* out);


static int f_app_parse_option(int opt);
static const char* f_app_get_opt_descr(void);
static int f_app_to_boot(int *out);

static const char *f_host_addr  = "192.168.0.1";
static int f_tcp_port           = 502;
static int f_mb_dev_addr        = 0;


static const char *f_app_host_addr  = NULL;
static int f_app_port               = 502;
static int f_app_reg_addr           = 0;
static int f_app_reg_cnt            = 0;
static uint16_t f_app_reg_vals[125];


typedef enum {
    LBOOT_ERR_MBTCP_FIRST           = -450,
    LBOOT_ERR_MBTCP_NOTSET_IPADDR   = -450,
    LBOOT_ERR_MBTCP_NOTSET_REGADDR  = -451,
    LBOOT_ERR_MBTCP_NOTSET_REGVALS  = -452,
    LBOOT_ERR_MBTCP_INVALID_PORT    = -453,
    LBOOT_ERR_MBTCP_INVALID_REGADDR = -454,
    LBOOT_ERR_MBTCP_INVALID_REGVALS = -455,
    LBOOT_ERR_MBTCP_INVALID_REGCNT  = -456,
    LBOOT_ERR_MBTCP_CREATE_CONTEXT  = -457,
    LBOOT_ERR_MBTCP_CONNECT         = -458,
    LBOOT_ERR_MBTCP_WRITE_REGS      = -459,
    LBOOT_ERR_MBTCP_DEV_ADDRESS     = -460,
} t_lboot_mbtcp_errs;

static const char *f_err_str[] = {
    "board IP-addres is not specified",
    "modbus registers start address is not specified",
    "modbus registers write values are not specified",
    "invalid modbus TCP port value",
    "invalid modbus registers start address",
    "invalid modbus register values format",
    "invalid modbus register values count",
    "modbus context creation error",
    "modbus connection error",
    "modbus registers write error",
    "invalid modbus slave device address (must be in [1,247])"
};


/* Описание опций интерфейса записи прошивки по modbus tcp */
static const char* f_opt_descr =
"modbus tcp options:\n"\
"    --mbtcp-ip-addr      - device IP-address (for instance \"192.168.0.1\")\n"\
"    --mbtcp-port         - modbus TCP port (default 502)\n"\
"    --mbtcp-dev-addr     - device address on modbus bus, must be in\n"\
"                             interval [1; 247]\n"\
"    --mbtcp-tout=ms      - modbus command response wait timeout in ms\n"\
"    --mbtcp-sign-tout=ms - modbus signature check response wait timeout in ms\n"\
"    --mbtcp-retry-cnt=n  - modbus read/write registers retry count on error\n";

/* Описание опций интерфейса перехода в загрузчик по modbus tcp */
static const char *f_app_opt_descr =
"mbtcp app-to-boot options:\n"\
"    --mbtcp-app-ip-addr=addr  - device IP-address (for instance \"192.168.0.1\")\n"\
"    --mbtcp-app-port=port     - modbus TCP port (default 502)\n"\
"    --mbtcp-app-reg-addr=addr - first modbus register address (starting from 1)\n"
"    --mbtcp-app-reg-vals=vals - register values to write (multiple values\n"\
"                                  separated by ';' or ',')\n";
;
/** параметры интерфейса Modbus TCP и его интерфейсные функции */
const t_boot_iface g_mbtcp_intf_info = {
    { "mbtcp",
     "Modbus TCP protocol over Ethernet interface",
     NULL,
     NULL,
     f_parse_option,
     f_get_opt_descr,
     f_get_err_str,
     },
    NULL,
    LBOOT_MBTCP_MAX_BLOCK_SIZE,
    f_open,
    lboot_mb_get_devmode,
    lboot_mb_get_bootloader_version,
    lboot_mb_get_dev_flags,
    lboot_mb_get_features,
    lboot_mb_start_write,
    lboot_fw_transfer_std_block,
    lboot_mb_write,
    lboot_mb_write_sign,
    lboot_mb_speccmd,
    lboot_mb_close
};


const t_app_iface g_mbtcp_app_iface = {
    { "mbtcp",
      "Write registers over Modbus-TCP",
      NULL,
      NULL,
      f_app_parse_option,
      f_app_get_opt_descr,
      f_get_err_str,
    },
    f_app_to_boot
};


static int f_parse_option(int opt) {
    int err = 0;
    switch (opt) {
    case LBOOT_OPT_MBTCP_IPADDR: {
            f_host_addr = optarg;
        }
        break;
    case LBOOT_OPT_MBTCP_PORT: {
            int port = atoi(optarg);
            if ((port <= 0) || (port > 65535)) {
                err = LBOOT_ERR_MBTCP_INVALID_PORT;
            } else {
                f_tcp_port = port;
            }
        }
        break;
    case LBOOT_OPT_MBTCP_DEVADDR:
        /* получаем адрес и проверяем, что лижит в допустимых пределах адреса
               в соответствии с протоколом Modbus */
        f_mb_dev_addr = atoi(optarg);
        if (!f_mb_dev_addr || (f_mb_dev_addr >= 248))
            err = LBOOT_ERR_MBTCP_DEV_ADDRESS;
        break;
    case LBOOT_OPT_MBTCP_TOUT:
        err = lboot_mb_set_tout(atoi(optarg));
        break;
    case LBOOT_OPT_MBTCP_SIGN_TOUT:
        err = lboot_mb_set_sign_tout(atoi(optarg));
        break;
    case LBOOT_OPT_MBTCP_RETRY_CNT:
        lboot_mb_set_max_retry_cnt(atoi(optarg));
        break;
    default:
        err = LBOOT_ERR_INVALID_OPTION;
        break;
    }
    return err;
}

static  const char* f_get_opt_descr(void) {
    return f_opt_descr;
}

static const char *f_get_err_str(int err) {
    const char *mb_err_str = lboot_mb_get_err_str(err);
    return mb_err_str != NULL ? mb_err_str :
               (((err > LBOOT_ERR_MBTCP_FIRST) ||
                 (err <  (LBOOT_ERR_MBTCP_FIRST -
                         (int32_t)(sizeof(f_err_str)/sizeof(f_err_str[0]))+1))) ?
                    NULL : f_err_str[LBOOT_ERR_MBTCP_FIRST-err]);
}

static int f_open(const char* devname, const char* serial, const void* params,
                  t_lboot_devinfo* info, int* out) {
    int err = 0;

    if (!err) {
        /* содаем контекст modbus в соответствии с настройками из опций*/
        modbus_t *mb = modbus_new_tcp(f_host_addr, f_tcp_port);
        if (mb == NULL) {
            err = LBOOT_ERR_MBTCP_CREATE_CONTEXT;
        } else {
            err = lboot_mb_open(mb, f_mb_dev_addr, LBOOT_ERR_MBTCP_CONNECT);
        }

        if (err) {
            *out = 1;
        }
    }

    if (!err) {
        err = lboot_mb_read_dev_info(info);
    }

    if (err) {
        lboot_mb_close();
    }
    return err;
}







static int f_app_parse_option(int opt) {
    int err = 0;
    switch (opt) {
        case LBOOT_OPT_MBTCP_APP_IP_ADDR: {
                f_app_host_addr = optarg;
            }
            break;
        case LBOOT_OPT_MBTCP_APP_PORT: {
                int port = atoi(optarg);
                if ((port <= 0) || (port > 65535)) {
                    err = LBOOT_ERR_MBTCP_INVALID_PORT;
                } else {
                    f_app_port = port;
                }
            }
            break;
        case LBOOT_OPT_MBTCP_APP_REG_ADDR: {
                int regaddr;
                if (sscanf(optarg, "%i", &regaddr) != 1) {
                    err = LBOOT_ERR_MBTCP_INVALID_REGADDR;
                } else if ((regaddr <= 0) || (regaddr > 65536)) {
                    err = LBOOT_ERR_MBTCP_INVALID_REGADDR;
                } else {
                    f_app_reg_addr = regaddr;
                }
            }
            break;
        case LBOOT_OPT_MBTCP_APP_REG_VALS: {
                static char val_buf[32];
                const char *start_str = optarg;
                int parse_done = 0;
                for (const char *c = start_str; (!parse_done && !err); ++c) {
                    if ((*c == ';') || (*c == ',') || (*c == ' ') || (*c == '\0')) {
                        int len = c - start_str;
                        if (len != 0) {
                            if (len > sizeof(val_buf)) {
                                err = LBOOT_ERR_MBTCP_INVALID_REGVALS;
                            } else {
                                int val;
                                memcpy(val_buf, start_str, len);
                                val_buf[len] = '\0';
                                if (sscanf(val_buf, "%i", &val) != 1) {
                                    err = LBOOT_ERR_MBTCP_INVALID_REGVALS;
                                } else if ((val < 0) || (val > 0xFFFF)) {
                                    err = LBOOT_ERR_MBTCP_INVALID_REGVALS;
                                } else if (f_app_reg_cnt == sizeof(f_app_reg_vals)/sizeof(f_app_reg_vals[0])) {
                                    err = LBOOT_ERR_MBTCP_INVALID_REGCNT;
                                } else {
                                    f_app_reg_vals[f_app_reg_cnt++] = val & 0xFFFF;
                                }
                            }
                        }

                        if (*c == '\0')
                            parse_done = 1;

                        start_str = c + 1;
                    }
                }

                if (!err && (f_app_reg_cnt == 0)) {
                    err = LBOOT_ERR_MBTCP_INVALID_REGCNT;
                }
            }
            break;
        default:
            err = LBOOT_ERR_INVALID_OPTION;
            break;
    }
    return err;
}

static const char *f_app_get_opt_descr(void) {
    return f_app_opt_descr;
}

static int f_app_to_boot(int *out) {
    int err = f_app_host_addr == NULL ? LBOOT_ERR_MBTCP_NOTSET_IPADDR :
              f_app_reg_addr  == 0 ? LBOOT_ERR_MBTCP_NOTSET_REGADDR :
              f_app_reg_cnt   == 0 ? LBOOT_ERR_MBTCP_NOTSET_REGVALS : 0;

    if (!err) {
        /* содаем контекст modbus в соответствии с настройками из опций*/
        modbus_t *mbctx = modbus_new_tcp(f_app_host_addr, f_app_port);
        if (mbctx == NULL) {
            err = LBOOT_ERR_MBTCP_CREATE_CONTEXT;
            *out=1;
        }

        if (!err ) {
            if (modbus_connect(mbctx)) {
                err = LBOOT_ERR_MBTCP_CONNECT;
            }
        }

        if (!err) {
             if (modbus_write_registers(mbctx, f_app_reg_addr - 1, f_app_reg_cnt, f_app_reg_vals) < 0) {
                 err = LBOOT_ERR_MBTCP_WRITE_REGS;
             }
        }

        if (mbctx) {
            modbus_close(mbctx);
        }
    }
    return err;
}
