#include <ltr/include/ltrapi.h>
#ifdef _WIN32
#include <WinSock2.h>
#else
#include <arpa/inet.h>
#include <sys/socket.h>
#endif
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#ifndef _WIN32
    #include <inttypes.h>    
#endif

#include <string.h>

#if defined _WIN32 || defined __QNX4__
    #define PRIu8 "u"
    #define PRIu16 "u"
    #define PRIu32 "u"    
#if defined _WIN32
    #define PRIu64 "%U64d"
#endif
#endif




#define LTRCMD_MAX_CMD_SIZE         512

#define LTR_MAX_IP_ENTRIES         256


typedef int (*t_ltrcmd_func)(TLTR* hnd,  char* paramstr);

typedef struct {
    const char* cmd;
    t_ltrcmd_func func;
    const char* param;
    const char* descr;
} t_ltrcmd_descr;

static int f_crate_list(TLTR* hnd,  char* paramstr);
static int f_ip_list(TLTR* hnd,  char* paramstr);
static int f_ip_add(TLTR* hnd, char* paramstr);
static int f_ip_rem(TLTR* hnd, char* paramstr);
static int f_ip_con(TLTR* hnd, char* paramstr);
static int f_ip_disc(TLTR* hnd, char* paramstr);
static int f_ip_setflags(TLTR* hnd, char* paramstr);
static int f_stat(TLTR* hnd, char* paramstr);
static int f_mstat(TLTR* hnd, char* paramstr);
static int f_mod_rst(TLTR* hnd, char* paramstr);
static int f_get_log_lvl(TLTR* hnd, char* paramstr);
static int f_set_log_lvl(TLTR* hnd, char* paramstr);
static int f_restart(TLTR* hnd,  char* paramstr);
static int f_shutdown(TLTR* hnd,  char* paramstr);
static int f_help(TLTR* hnd,  char* paramstr);


static t_ltrcmd_descr f_cmds[] = {
    {"clist",  f_crate_list, "", "list crates and modules"},
    {"iplist", f_ip_list, "[<ip_addr> <msk>]", "list ip entries from ltrd"},
    {"ipadd",  f_ip_add, "<ip_addr> [auto] [reconnect] [perm]", "add ip entry"},
    {"iprem",  f_ip_rem, "<ip_addr> [perm]", "remove ip entry"},
    {"ipcon",  f_ip_con, "<ip_addr> | auto", "connect to crate with specified ip or to all auto"},
    {"ipdisc", f_ip_disc, "<ip_addr> | all", "disconnect from crate with specified ip or from all ip-crates"},
    {"ipflag", f_ip_setflags, "<ip_addr> auto | reconnect set | clear [perm]", "set or clear specified flag for ip entry"},
    {"cstat",   f_stat,    "[<serial>]", "show statistic from crate"},
    {"mstat",  f_mstat,   "<slot> [<serial>]", "show module statistic"},
    {"mrst",   f_mod_rst, "<slot> [<serial>]", "reset module in crate"},
    {"getloglvl", f_get_log_lvl, "", "get ltrd log level"},
    {"setloglvl", f_set_log_lvl, "<lvl> [perm]", "set log level for session or permanent"},
    {"restart", f_restart, "", "restart ltrd"},
    {"shutdown", f_shutdown, "", "shutdown ltrd"},
    {"help",   f_help, "", "print this help"},
    {"exit", NULL, "", "exit"}
};




#define OPT_ADDR 'a'
#define OPT_PORT 'p'
#define OPT_HELP 'h'


static const struct option f_long_opt[] = {
    {"ip-addr",         required_argument,       0, OPT_ADDR},
    /* вывод сообщений о процессе загрузки */
    {"tcp-port",         required_argument,       0, OPT_PORT},
    {"help",         no_argument,             0, OPT_HELP},
    {0,0,0,0}
};

static const char* f_opt_str = "a:p:h";

typedef struct {
    DWORD addr;
    WORD port;
    int cmd_mode;
    int opt_next;
    int optind;
} t_opts;

static const char* f_usage_descr = \
"\nUsage: ltrcmd [OPTIONS]\n" \
"         ltrcmd [OPTIONS] command [ARGS]\n\n"
" ltrctl is a command line frontend for ltrd daemon.\n"
" There is two modes of operation:\n"
"   - You can specify single command with arguments in command line.\n"
"     Then ltrctl will connect to ltrd, execute single command and close\n"
"     connection.\n"
"   - If you not specify command in ltrctl arguments then you enter interactive \n"
"     mode: ltrctl connect to ltrd and prompt for commands. Then you can enter\n"
"     any commands. Command 'exit' closes connection\n"
"\nOptions:\n"
"-a, --ip-addr=ltrd-addr    - address of host on which ltrd is running. \n"
"                               localhost address is used by default\n"
"-p, --tcp-port=tcp-port    - TCP port used to connect to ltrd (if not default)\n"
"-h, --help                 - Print this help and exit\n";




#ifdef _WIN32
static int inet_pton(int af, const char *src, void *dst) {
    struct sockaddr_storage ss;
    int sslen = sizeof(ss);
    int err = WSAStringToAddress((char*)src, af, NULL, (LPSOCKADDR)&ss, &sslen);
    if (!err) {
        if (af==AF_INET) {
            memcpy(dst, &(((struct sockaddr_in *)&ss)->sin_addr), sizeof(struct in_addr));
#ifdef HAVE_SOCKADDR_IN6
        } else if (af==AF_INET6) {
            memcpy(dst, &(((struct sockaddr_in6 *)&ss)->sin6_addr), sizeof(struct in6_addr));
#endif
        } else {
            err = -1;
        }
    }
    return err ? -1 : 1;
}
#endif


static void f_print_usage(void) {
    printf("%s\n", f_usage_descr);
    f_help(NULL, NULL);
}

static int f_parse_options(t_opts* st, int argc, char **argv, int* out) {
    int err = 0;
    int opt = 0;

    *out = 0;
    st->addr = LTRD_ADDR_DEFAULT;
    st->port = LTRD_PORT_DEFAULT;
    st->optind = 0;

    /************************** разбор опций *****************************/
    while ((opt!=-1) && !err && !*out) {
        opt = getopt_long(argc, argv, f_opt_str,
                          f_long_opt, 0);
        switch (opt) {
            case -1:
                break;
            case OPT_HELP:
                f_print_usage();
                *out = 1;
                break;
            case OPT_PORT:
                st->port = atoi(optarg);
                break;
            case OPT_ADDR: {
                    int a[4],i;
                    if (sscanf(optarg, "%d.%d.%d.%d", &a[0], &a[1], &a[2], &a[3])!=4) {
                        fprintf(stderr, "Invalid ltrd address format!\n");
                        err = -1;
                    }

                    for (i=0; (i < 4) && !err; i++) {
                        if ((a[i]<0) || (a[i] > 255)) {
                            fprintf(stderr, "Invalid ltrd address format!\n");
                            err = -1;
                        }
                    }

                    if (!err) {
                        st->addr = (a[0] << 24) | (a[1]<<16) | (a[2]<<8) | a[3];
                    }
                }
            default:
                break;
        }
    }

    if (!err && !*out) {
        if (optind == argc) {
            /* если нет аргумент входим в комндный режим и ожидаем ввода команды */
            st->cmd_mode = 1;
        } else {
            st->cmd_mode = 0;
            st->optind = optind;
        }
    }
    return err;
}





static char* f_split_space(char* str) {
    for (;*str && (*str!=' ') && (*str!='\n'); str++)
    {}
    if (*str=='\n') {
        *str = 0;
    } else if (*str==' ') {
        *str = 0;
        return str+1;
    }
    return NULL;
}

static const char* f_crate_type_str(WORD type, int* modules_cnt) {
    const char* ret = "Unknown";
    int mcnt = 0;
    switch (type) {
        case LTR_CRATE_TYPE_LTR010:
            ret = "LTR010";
            mcnt = 16;
            break;
        case LTR_CRATE_TYPE_LTR021:
            ret = "LTR021";
            mcnt = 2;
            break;
        case LTR_CRATE_TYPE_LTR030:
            ret = "LTR030";
            mcnt = 16;
            break;
        case LTR_CRATE_TYPE_LTR031:
            ret = "LTR031";
            mcnt = 2;
        case LTR_CRATE_TYPE_LTR032:
            ret = "LTR032";
            mcnt = 16;
            break;
        case LTR_CRATE_TYPE_BOOTLOADER:
            ret = "BOOT";
            mcnt = 0;
            break;
    }
    if (modules_cnt)
        *modules_cnt = mcnt;
    return ret;
}

static const char* f_crate_intf_str(WORD intf) {
    const char* ret = "Unknown";
    switch (intf) {
        case LTR_CRATE_IFACE_TCPIP:
            ret = "TCP";
            break;
        case LTR_CRATE_IFACE_USB:
            ret = "USB";
            break;
    }
    return ret;
}


static const char* f_crate_mode_str(WORD intf) {
    const char* ret = "Unknown";
    switch (intf) {
        case LTR_CRATE_MODE_WORK:
            ret = "Work";
            break;
        case LTR_CRATE_MODE_BOOTLOADER:
            ret = "Bootloader";
            break;
        case LTR_CRATE_MODE_CONTROL:
            ret = "control";
            break;
    }
    return ret;
}


static const char* f_crate_mid_str(WORD mid) {
    const char* ret = "Unknown";
    switch (mid) {
        case LTR_MID_EMPTY:
            ret = "-";
            break;
        case LTR_MID_IDENTIFYING:
            ret = "--initialized--";
            break;
        case LTR_MID_LTR22:
            ret = "LTR22";
            break;
        case LTR_MID_LTR24:
            ret = "LTR24";
            break;
        case LTR_MID_LTR25:
            ret = "LTR24";
            break;
        case LTR_MID_LTR27:
            ret = "LTR27";
            break;
        case LTR_MID_LTR212:
            ret = "LTR212";
            break;
        case LTR_MID_LTR210:
            ret = "LTR210";
            break;
        case LTR_MID_LTR114:
            ret = "LTR114";
            break;
        case LTR_MID_LTR51:
            ret = "LTR51";
            break;
        case LTR_MID_LTR34:
            ret = "LTR34";
            break;
        case LTR_MID_LTR35:
            ret = "LTR35";
            break;
        case LTR_MID_LTR41:
            ret = "LTR41";
            break;
        case LTR_MID_LTR42:
            ret = "LTR42";
            break;
        case LTR_MID_LTR43:
            ret = "LTR43";
            break;
        case LTR_MID_LTR11:
            ret = "LTR11";
            break;
    }
    return ret;
}


static const char* f_ip_entry_status_str(BYTE status) {
    const char* ret = "Unknown status";
    switch (status) {
        case LTR_CRATE_IP_STATUS_OFFLINE:
            ret = "offline   ";
            break;
        case LTR_CRATE_IP_STATUS_CONNECTING:
            ret = "connecting";
            break;
        case LTR_CRATE_IP_STATUS_ONLINE:
            ret = "online    ";
            break;
        case LTR_CRATE_IP_STATUS_ERROR:
            ret = "error     ";
            break;
    }
    return ret;
}

static int f_crate_list(TLTR* hnd,  char* paramstr) {
    char crates[LTR_CRATES_MAX][LTR_CRATE_SERIAL_SIZE];
    int i;
    int err = LTR_GetCrates(hnd, (BYTE*)crates);
    if (err == LTR_OK) {
        printf("Crate list:\n");
        for (i=0; i < LTR_CRATES_MAX; i++) {
            if (crates[i][0]!=0) {
                TLTR crate;
                WORD mids[LTR_MODULES_PER_CRATE_MAX];
                int crate_err = 0, is_open = 0, modules_cnt;

                LTR_Init(&crate);                
                crate_err = LTR_OpenCrate(&crate, hnd->saddr, hnd->sport,
                                          LTR_CRATE_IFACE_UNKNOWN, crates[i]);
                if (crate_err != LTR_OK) {
                    fprintf(stderr, "Cannot open channel with crate %s. Error %d.%s\n",
                           crates[i], crate_err, LTR_GetErrorString(err));
                } else {
                    is_open = 1;
                }

                if (!crate_err) {
                    TLTR_CRATE_INFO info;
                    crate_err = LTR_GetCrateInfo(&crate, &info);
                    if (crate_err != LTR_OK) {
                        fprintf(stderr, "Cannot get info for crate %s. Error %d.%s\n",
                               crates[i], crate_err, LTR_GetErrorString(err));
                    } else {
                        printf("Crate %s, interface  %s, serial %s\n",
                               f_crate_type_str(info.CrateType, &modules_cnt),
                               f_crate_intf_str(info.CrateInterface), crates[i]);
                    }
                }

                if (crate_err == LTR_OK) {
                    int slot;
                    crate_err = LTR_GetCrateModules(&crate, mids);
                    if (crate_err != LTR_OK) {
                        fprintf(stderr, "Cannot get modules list from crate %s. Error %d.%s\n",
                               crates[i], crate_err, LTR_GetErrorString(err));
                    } else {
                        for (slot=0; slot < modules_cnt; slot++) {
                            printf("    slot %2d: %s\n", slot+1, f_crate_mid_str(mids[slot]));
                        }
                    }
                }

                if (is_open) {
                    LTR_Close(&crate);
                }
            }
        }
    } else {
        fprintf(stderr, "Cannot get crates list. Error %d:%s\n", err, LTR_GetErrorString(err));
    }
    return err;
}

static int f_ip_list(TLTR* hnd,  char* paramstr) {
    int err = 0;

    char *ip_str = NULL, *msk_str = NULL;
    DWORD msk=0, addr=0, fnd=0;

    ip_str = paramstr;
    if (ip_str!=NULL) {
        msk_str = f_split_space(paramstr);
        if (msk_str!=NULL)
            f_split_space(msk_str);
    }

    if (ip_str && msk_str) {
        if (inet_pton(AF_INET, ip_str, &addr)!=1) {
            err =-2;
            fprintf(stderr, "Cannot conver string %s to ip address\n", ip_str);
        } else {
            if (inet_pton(AF_INET, msk_str, &msk)!=1) {
                err = -3;
                fprintf(stderr, "Cannot convert string %s to ip mask\n", msk_str);
            }
        }
    }

    if (!err) {
        msk = ntohl(msk);
        addr = ntohl(addr);

        err = LTR_GetListOfIPCrates(hnd, 0, addr, msk, &fnd, NULL, NULL);
        if (err) {
            fprintf(stderr, "Cannot get ip entry cnt. Error %d: %s\n", err, LTR_GetErrorString(err));
        } else {
            printf("Found %d ip entries:\n", fnd);
        }
    }

    if (!err && fnd) {
        TLTR_CRATE_IP_ENTRY* entry_lst = malloc(sizeof(TLTR_CRATE_IP_ENTRY)*fnd);
        if (entry_lst != NULL) {
            err = LTR_GetListOfIPCrates(hnd, fnd, addr, msk, NULL, &fnd, entry_lst);
            if (err) {
                fprintf(stderr, "Cannot get ip entries. Error %d: %s\n", err, LTR_GetErrorString(err));
            } else {
                DWORD i;
                for (i=0; i < fnd; i++) {
                    char ip_addr_str[16];
                    sprintf(ip_addr_str, "%d.%d.%d.%d",
                            (entry_lst[i].ip_addr>>24)&0xFF, (entry_lst[i].ip_addr>>16)&0xFF,
                            (entry_lst[i].ip_addr>>8)&0xFF, entry_lst[i].ip_addr&0xFF);
                    printf("%d: %-16s  status = %s serial = %10s  autocon = %3s, reconnect = %3s\n",
                           i+1, ip_addr_str, f_ip_entry_status_str(entry_lst[i].status),
                           entry_lst[i].serial_number,
                           entry_lst[i].flags & LTR_CRATE_IP_FLAG_AUTOCONNECT ?  "yes" : "no",
                           entry_lst[i].flags & LTR_CRATE_IP_FLAG_RECONNECT ?  "yes" : "no");
                }
            }
            free(entry_lst);
        } else {
            err = LTR_ERROR_MEMORY_ALLOC;
            fprintf(stderr, "Memory allocation error!\n");
        }
    }
    return err;
}



static int f_get_ip_addr(char* paramstr, DWORD* paddr, char** next_str) {
    char* next = f_split_space(paramstr);
    int err = 0;
    DWORD addr;

    if (inet_pton(AF_INET, paramstr, &addr)!=1) {
        fprintf(stderr, "Cannot conver string %s to ip address\n", paramstr);
        err = -1;
    } else {
        addr = ntohl(addr);
        if (paddr)
            *paddr = addr;
    }

    if (next_str)
        *next_str = next;

    return err;
}


static int f_ip_add(TLTR* hnd, char* paramstr) {
    int err = 0;
    DWORD perm = 0;
    DWORD flags = 0;
    DWORD addr = 0;
    char *next_str;

    if (paramstr==NULL) {
        fprintf(stderr, "IP addres is not specified\n");
        err = -2;
    } else {
        err = f_get_ip_addr(paramstr, &addr, &paramstr);

        while (paramstr!=NULL) {
            next_str = f_split_space(paramstr);
            if (!strcmp(paramstr, "auto")) {
                flags |= LTR_CRATE_IP_FLAG_AUTOCONNECT;
            } else if (!strcmp(paramstr, "reconnect")) {
                flags |= LTR_CRATE_IP_FLAG_RECONNECT;
            } else if (!strcmp(paramstr, "perm")) {
                perm = 1;
            }
            paramstr = next_str;
        }
    }

    if (!err) {
        err = LTR_AddIPCrate(hnd, addr, flags, perm);

        if (err) {
            fprintf(stderr, "Cannot add ipentry. Error %d: %s\n", err, LTR_GetErrorString(err));
        } else {
            printf("IP-address added %s successfully\n", perm ? "permanently" : "for session");
        }
    }

    return err;
}

static int f_ip_rem(TLTR* hnd, char* paramstr) {
    int err = 0;
    DWORD perm = 0;
    DWORD addr = 0;
    char *next_str;

    if (paramstr==NULL) {
        fprintf(stderr, "IP addres is not specified\n");
        err = -2;
    } else {
        err = f_get_ip_addr(paramstr, &addr, &paramstr);

        while (paramstr!=NULL) {
            next_str = f_split_space(paramstr);
            if (!strcmp(paramstr, "perm"))
                perm = 1;
            paramstr = next_str;
        }
    }


    err = LTR_DeleteIPCrate(hnd, addr, perm);

    if (err)
        fprintf(stderr, "Remove crate ip error %d: %s\n", err, LTR_GetErrorString(err));
    else
        printf("IP-address removed successfully\n");
    return err;
}

static int f_ip_con(TLTR* hnd, char* paramstr) {
    int err=0;
    int all =0;
    DWORD addr=0;
    if (paramstr==NULL) {
        fprintf(stderr, "parameter is not specified\n");
        err = -2;
    } else {
        f_split_space(paramstr);
        if (!strcmp(paramstr, "auto")) {
            all = 1;
        } else {
            err = f_get_ip_addr(paramstr, &addr, &paramstr);
        }

        if (!err) {
            if (all) {
                err = LTR_ConnectAllAutoIPCrates(hnd);
            } else {
                err = LTR_ConnectIPCrate(hnd, addr);
            }

            if (err)
                fprintf(stderr, "Crate connect error %d: %s\n", err, LTR_GetErrorString(err));
        }
    }

    if (!err)
        printf("Connection successfully started.\nFor check connection status use `iplist` command\n");
    return err;
}

static int f_ip_disc(TLTR* hnd, char* paramstr) {
    int err=0;
    int all =0;
    DWORD addr=0;
    if (paramstr==NULL) {
        fprintf(stderr, "Parameter is not specified!\n");
        err = -2;
    } else {
        f_split_space(paramstr);
        if (!strcmp(paramstr, "all")) {
            all = 1;
        } else {
            err = f_get_ip_addr(paramstr, &addr, &paramstr);
        }

        if (!err) {
            if (all) {
                err = LTR_DisconnectAllIPCrates(hnd);
            } else {
                err = LTR_DisconnectIPCrate(hnd, addr);
            }

            if (err)
                fprintf(stderr, "Crate disconnect error %d: %s!\n", err, LTR_GetErrorString(err));
        }
    }

    if (!err)
        printf("Crate successfully disconnected\n");
    return err;
}


static int f_ip_setflags(TLTR* hnd, char* paramstr) {
    int err=0;
    int flag =0;
    int set;
    int perm = 0;
    char *next_str;
    DWORD addr=0;
    if (paramstr==NULL) {
        fprintf(stderr, "ip addres is not specified\n");
        err = -2;
    } else {
        next_str = f_split_space(paramstr);
        err = f_get_ip_addr(paramstr, &addr, &paramstr);
    }

    if (!err) {
        /* определяем параемтр */
        paramstr = next_str;
        if (paramstr==NULL) {
            fprintf(stderr, "parameter is not specified\n");
            err = -2;
        }

        if (!err) {
            next_str = f_split_space(paramstr);
            if (!strcmp("auto", paramstr)) {
                flag = LTR_CRATE_IP_FLAG_AUTOCONNECT;
            } else if (!strcmp("reconnect", paramstr)) {
                flag = LTR_CRATE_IP_FLAG_RECONNECT;
            } else {
                fprintf(stderr, "unsupported ip parameter\n");
                err = -3;
            }
        }
    }

    if (!err) {
        /* определяем операцию (set | clear) */
        paramstr = next_str;
        if (paramstr==NULL) {
            fprintf(stderr, "operation is not specified\n");
            err = -2;
        }

        if (!err) {
            next_str = f_split_space(paramstr);
            if (!strcmp("set", paramstr)) {
                set = 1;
            } else if (!strcmp("clear", paramstr)) {
                set = 0;
            } else {
                fprintf(stderr, "unsupported parameter operation\n");
                err = -3;
            }
        }
    }

    if (!err) {
        /* определяем операцию (set | clear) */
        paramstr = next_str;
        if (paramstr!=NULL) {
            next_str = f_split_space(paramstr);
            if (!strcmp(paramstr, "perm")) {
                perm = 1;
            }
        }
    }

    if (!err) {
        TLTR_CRATE_IP_ENTRY ipentry;
        DWORD fnd;

        err = LTR_GetListOfIPCrates(hnd, 1, addr, 0xFFFFFFFF, NULL, &fnd, &ipentry);
        if (err) {
            fprintf(stderr, "Cannot get ip entries. Error %d: %s\n", err, LTR_GetErrorString(err));
        } else if (!fnd) {
            fprintf(stderr, "Cannot find specified ip entry!\n");
        } else {
            if (set) {
                ipentry.flags |= flag;
            } else {
                ipentry.flags &= ~flag;
            }

            err = LTR_SetIPCrateFlags(hnd, addr, ipentry.flags, perm);
            if (err) {
                fprintf(stderr, "Cannot set ip entry flags. Error %d: %s\n", err, LTR_GetErrorString(err));
            }
        }
    }

    if (!err)
        printf("Flag for ipentry changed %s successfully\n", perm ? "permanently" : "for session");

    return err;
}




static int f_stat(TLTR* hnd, char* paramstr) {
    TLTR_CRATE_STATISTIC stat;
    int err=0;
    char* serial = paramstr;
    if (serial)
        f_split_space(serial);


    err = LTR_GetCrateStatistic(hnd, LTR_CRATE_IFACE_UNKNOWN, serial, &stat, sizeof(stat));
    if (!err) {
        printf("Crate statistic:\n");
        printf("  Crate type                  : %s\n", f_crate_type_str(stat.crate_type, NULL));
        printf("  Crate interface             : %s\n", f_crate_intf_str(stat.crate_intf));
        printf("  Crate mode                  : %s\n", f_crate_mode_str(stat.crate_mode));
        printf("  Control clients             : %"PRIu16"\n", stat.ctl_clients_cnt);
        printf("  Total modules clients       : %"PRIu16"\n", stat.total_mod_clients_cnt);
#ifdef LTRAPI_HAVE_UINT64
        printf("  Words received              : %"PRIu64"\n", stat.wrd_recv);
        printf("  Words sent                  : %"PRIu64"\n", stat.wrd_sent);
        printf("  Words recv from crate ctrl  : %"PRIu64"\n", stat.crate_wrd_recv);
#else
        printf("  Words received              : %"PRIu32"\n", stat.wrd_recv.l);
        printf("  Words sent                  : %"PRIu32"\n", stat.wrd_sent.l);
        printf("  Words recv from crate ctrl  : %"PRIu32"\n", stat.crate_wrd_recv.l);
#endif
        printf("  Send bandwidth              : %.1f words/sec\n",   stat.bw_send);
        printf("  Receive bandwidth           : %.1f words/sec\n",   stat.bw_recv);
        printf("  Receive buffer overflow cnt : %"PRIu32"\n", stat.rbuf_ovfls);
        printf("  Total second marks          : %"PRIu32"\n", stat.total_sec_marks);
        printf("  Total start marks           : %"PRIu32"\n", stat.total_start_marks);
        printf("  Crate second marks          : %"PRIu32"\n", stat.crate_sec_marks);
        printf("  Crate start marks           : %"PRIu32"\n", stat.crate_start_marks);
    } else {
        fprintf(stderr, "Get statistic error %d: %s\n", err, LTR_GetErrorString(err));
    }
    return err;
}


static int f_mstat(TLTR* hnd, char* paramstr) {

    int err=0;
    if (!paramstr) {
        printf("slot is not specified\n");
        err = -1;
    } else {
        int slot;
        char* serial = f_split_space(paramstr);
        TLTR_MODULE_STATISTIC stat;
        if (serial)
            f_split_space(serial);
        slot = atoi(paramstr);
        if ((slot == LTR_CC_CHNUM_CONTROL) || (slot > LTR_MODULES_PER_CRATE_MAX)) {
            printf("invalid slot number\n");
            err = -2;
        } else {
            err = LTR_GetModuleStatistic(hnd, LTR_CRATE_IFACE_UNKNOWN, serial, slot,
                                         &stat, sizeof(stat));
            if (!err) {
                printf("Module statistic from slot %d:\n", slot);
                printf("  Module                      : %s\n", f_crate_mid_str(stat.mid));
                printf("  Module speed                : %s\n",
                       stat.flags & LTR_MODULE_FLAGS_HIGH_BAUD ? "high" : "slow");
                printf("  Client count                : %"PRIu16"\n", stat.client_cnt);
#ifdef LTRAPI_HAVE_UINT64
                printf("  Words received from module  : %"PRIu64"\n", stat.wrd_recv);
                printf("  Words sent to module        : %"PRIu64"\n", stat.wrd_sent);
                printf("  Words received from clients : %"PRIu64"\n", stat.wrd_recv_from_client);
                printf("  Words sent to clients       : %"PRIu64"\n", stat.wrd_sent_to_client);

                printf("  Send bandwidth              : %.1f words/sec\n",  stat.bw_send);
                printf("  Receive bandwidth           : %.1f words/sec\n",  stat.bw_recv);
#else
                printf("  Words received from module  : %"PRIu32"\n", stat.wrd_recv.l);
                printf("  Words sent to module        : %"PRIu32"\n", stat.wrd_sent.l);
                printf("  Words received from clients : %"PRIu32"\n", stat.wrd_recv_from_client.l);
                printf("  Words sent to clients       : %"PRIu32"\n", stat.wrd_sent_to_client.l);
#endif
                printf("  Receive buffer fullness     : %.2f %%\n", ((double)stat.recv_srvbuf_full)*100./stat.recv_srvbuf_size);
                printf("  Send buffer fullness        : %.2f %%\n", ((double)stat.send_srvbuf_full)*100./stat.send_srvbuf_size);
                printf("  Max receive buffer fullness : %.2f %%\n", ((double)stat.recv_srvbuf_full_max)*100./stat.recv_srvbuf_size);
                printf("  Max send buffer fullness    : %.2f %%\n", ((double)stat.send_srvbuf_full_max)*100./stat.send_srvbuf_size);
                printf("  Receive buffer overflow cnt : %"PRIu32"\n", stat.rbuf_ovfls);
#ifdef LTRAPI_HAVE_UINT64
                printf("  Words dropped               : %"PRIu64"\n", stat.wrd_recv_drop);
#else
                printf("  Words dropped               : %"PRIu32"\n", stat.wrd_recv_drop.l);
#endif

                if (stat.flags & LTR_MODULE_FLAGS_USE_SYNC_MARK) {
                    printf("  Second marks from module    : %"PRIu32"\n", stat.sec_mark);
                    printf("  Start marks from module     : %"PRIu32"\n", stat.start_mark);
                }
                if (stat.flags & LTR_MODULE_FLAGS_USE_HARD_SEND_FIFO) {
                    printf("  Module send fifo size             : %"PRIu32"\n", stat.hard_send_fifo_size);
                    printf("  Module send fifo unack. words     : %"PRIu32"\n", stat.hard_send_fifo_unack_words);
                    printf("  Module send fifo overrun          : %"PRIu32"\n", stat.hard_send_fifo_overrun);
                    printf("  Module send fifo underrun         : %"PRIu32"\n", stat.hard_send_fifo_underrun);
                    printf("  Module send fifo internal status  : %"PRIu32"\n", stat.hard_send_fifo_internal);
                }
            } else {
                fprintf(stderr, "Get statistic error %d: %s\n", err, LTR_GetErrorString(err));
            }
        }
    }

    return err;
}

static int f_mod_rst(TLTR* hnd, char* paramstr) {
    int err=0;
    if (!paramstr) {
        fprintf(stderr, "slot is not specified\n");
        err = -1;
    } else {
        int slot;
        char* serial = f_split_space(paramstr);
        if (serial)
            f_split_space(serial);
        slot = atoi(paramstr);
        if ((slot == LTR_CC_CHNUM_CONTROL) || (slot > LTR_MODULES_PER_CRATE_MAX)) {
            fprintf(stderr, "invalid slot number\n");
            err = -2;
        } else {
            err = LTR_ResetModule(hnd, LTR_CRATE_IFACE_UNKNOWN, serial, slot, 0);
            if (err) {
                fprintf(stderr, "Reset module error %d: %s\n", err, LTR_GetErrorString(err));
            } else {
                printf("Module reset done successfully\n");
            }
        }
    }
    return err;
}


static int f_get_log_lvl(TLTR* hnd, char* paramstr) {
    INT lvl;
    int err = LTR_GetLogLevel(hnd, &lvl);
    if (err==LTR_OK) {
        printf("ltrd log level = %d\n", lvl);
    } else {
        fprintf(stderr, "Get log level error %d: %s\n", err, LTR_GetErrorString(err));
    }
    return err;
}

static int f_set_log_lvl(TLTR* hnd, char* paramstr) {
    INT err = 0;
    if (!paramstr) {
        fprintf(stderr, "log level is not specified\n");
        err = -1;
    } else {
        char* perm_str = f_split_space(paramstr);
        INT perm = 0;
        INT lvl = atoi(paramstr);

        if (perm_str!=NULL) {
            f_split_space(perm_str);
            if (!strcmp(perm_str, "perm"))
                perm = 1;
        }

        err = LTR_SetLogLevel(hnd, lvl, perm);
        if (err == LTR_OK) {
            printf("Set new log level %s successfully\n", perm ? "permanently" :
                                                                 "for session");
        } else {
            fprintf(stderr, "Get log level error %d: %s\n", err, LTR_GetErrorString(err));
        }
    }
    return err;
}

static int f_restart(TLTR* hnd,  char* paramstr) {
    LTR_ServerRestart(hnd);
    LTR_Close(hnd);
    return 0;
}

static int f_shutdown(TLTR* hnd,  char* paramstr) {
    LTR_ServerShutdown(hnd);
    LTR_Close(hnd);
    return 0;
}

static int f_help(TLTR* hnd,  char* paramstr) {
    unsigned int i;
    printf("Supported commands:\n");
    for (i=0; i < sizeof(f_cmds)/sizeof(f_cmds[0]); i++ ) {
        printf("%-10s %-25s- %s\n", f_cmds[i].cmd,  f_cmds[i].param, f_cmds[i].descr);
    }
    return 0;
}


static void f_exec_cmd(char* cmd, char* param, TLTR* hnd) {
    int  fnd;
    unsigned int i;

    for (i=0, fnd = 0; (i < sizeof(f_cmds)/sizeof(f_cmds[0])) && !fnd; i++ ) {
        if (!strcmp(f_cmds[i].cmd, cmd)) {
            fnd = 1;
            f_cmds[i].func(hnd, param);
        }
    }

    if (!fnd)
        printf("Unsupported command\n");
}

int main(int argc, char** argv) {
    int err = 0;
    TLTR hnd;
    DWORD ver;
    t_opts opts;
    int out=0;


    err = f_parse_options(&opts, argc, argv, &out);
    if (!err && !out) {
        LTR_Init(&hnd);
        err = LTR_OpenSvcControl(&hnd, opts.addr, opts.port);
        if (err) {
            printf("Cannot open control channel. error = %d, %s\n", err, LTR_GetErrorString(err));
        } else {
            err = LTR_GetServerVersion(&hnd, &ver);
            if (err) {
                printf("Cannot get ltrd version. error = %d, %s\n", err, LTR_GetErrorString(err));
            } else {
                printf("Open control channel. ltrd version is %d.%d.%d.%d\n",
                       (ver>>24)&0xFF, (ver>>16)&0xFF, (ver>>8)&0xFF, ver&0xFF);
                fflush(stdout);
            }

            /* командный режим */
            if (!err && !out) {
                char cmd[LTRCMD_MAX_CMD_SIZE];

                if (opts.cmd_mode) {
                    printf("You are in command mode. Enter command for control ltrd or help for command list\n");
                    while (!out) {
                        printf(">");
                        if (fgets(cmd, LTRCMD_MAX_CMD_SIZE, stdin)!=NULL) {
                            char* param = f_split_space(cmd);

                            if (!strcmp("exit", cmd)) {
                                out=1;
                            } else {
                                f_exec_cmd(cmd, param, &hnd);
                            }
                        }
                    }
                } else {
                    int i;
                    char* param;
                    cmd[0]=0;
                    for (i=opts.optind; i < argc; i++) {
                        strncat(cmd, argv[i], LTRCMD_MAX_CMD_SIZE-1);
                        strncat(cmd, " ", LTRCMD_MAX_CMD_SIZE-1);
                        cmd[LTRCMD_MAX_CMD_SIZE-1]=0;
                    }
                    cmd[sizeof(cmd)-1] = 0;
                    param = f_split_space(cmd);
                    f_exec_cmd(cmd, param, &hnd);
                }
            }
            LTR_Close(&hnd);
        }
    }

    return err;
}
