#include "ltr/include/ltr030api.h"
#include "ltr/include/ltrapi.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "getopt.h"

#ifdef _WIN32
#include "WinSock2.h"
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif



#define OPT_HELP       'h'
#define OPT_CRATE_SN   'c'
#define OPT_CRATE_TYPE 't'
#define OPT_DSP_FIRM   'd'
#define OPT_FPGA_FIRM  'f'
#define OPT_SRV_ADDR    0x1000
#define OPT_ALL         0x1001


#define ERR_INVALID_USAGE           -1
#define ERR_NO_CRATES               -2


typedef struct {
    int type;
    const char *name;
    const char *descr;
} t_crate_descr;

static t_crate_descr f_crates[] = {
    {LTR_CRATE_TYPE_LTR030, "ltr030", "LTR-EU-8/16"},
    {LTR_CRATE_TYPE_LTR031, "ltr031", "LTR-EU-2"}
};


static const struct option f_long_opt[] = {
    {"help",         no_argument,       0, OPT_HELP},
    {"csn",          required_argument, 0, OPT_CRATE_SN},
    {"crate-type",   required_argument, 0, OPT_CRATE_TYPE},
    {"dsp-firm",     required_argument, 0, OPT_DSP_FIRM},
    {"fpga-firm",    required_argument, 0, OPT_FPGA_FIRM},
    {"all",          no_argument,       0, OPT_ALL},
    {"srv-addr",     required_argument, 0, OPT_SRV_ADDR},
    {0,0,0,0}
};

static const char *f_opt_str = "hc:t:d:f:";

typedef struct {
    int all;
    t_crate_descr* crate;
    const char *csn;
    const char *fpga_firm;
    const char *dsp_firm;
    DWORD srv_addr;
} t_ltrcfg_state;




static const char* f_usage_descr = \
"\nUsage: ltreu-firm-update [OPTIONS]\n\n" \
"   ltreu-firm-update is command line utility for update LTR-EU crate firmware\n"
"   for processor or FPGA (any or both). You must specify crate type with option\n"
"   --crate-type for additional check that you write firmware for supported\n"
"   crate type.\n"
" You must connect crate by USB interface and check that ltrd daemon IS running.\n"
" By default ltreu-firm-update connect to the first crate with usb interface, but\n"
"   you can specify crate serial number with option --csn=<serial> if you have\n"
"   more than one crate. Or you can specify --all option for update all crates\n"
"   with specified type\n\n"
" WARNING!: You can make you crate unusable if you write invalid firmware file!\n\n"
" Update processor firmware for LTR-EU-2:\n"
"   ltreu-firm-update --crate-type=ltr031 --dsp-firm=<fimware-file>\n"
" Update FPGA firmware for LTR-EU-8/16:\n"
"   ltreu-firm-update --crate-type=ltr030 --fpga-firm=<fimware-file>\n";


static const char* f_options_descr =
"Options:\n" \
"    --all                - Batch update firmware for all crates with specified\n"
"                           type\n"
"-c, --csn=crate-serial   - Crate serial which firmware you wish to update \n"
"-d, --dsp-firm=filename  - Name of file with DSP firmware (if you need update \n"
"                           DSP firmware)\n"
"-f, --fpga-firm=filename - Name of file with FPGA firmware (if you need update \n"
"                           FPGA firmware)\n"
"-h, --help              - Print this help and exit\n"
"-t, --crate-type=type   - Specify crate-controllor type (required):\n";



static void  f_print_usage(void) {
    unsigned int i;
    fprintf(stdout, "%s", f_usage_descr);
    fprintf(stdout,"\n");
    fprintf(stdout, "%s", f_options_descr);

    for (i=0; i < sizeof(f_crates)/sizeof(f_crates[0]); i++) {
        printf("                             - %s - %s\n", f_crates[i].name, f_crates[i].descr);
    }
}


static int f_stricmp (const char *p1, const char *p2) {
    unsigned char *s1 = (unsigned char *) p1;
    unsigned char *s2 = (unsigned char *) p2;
    unsigned char c1, c2;

    do {
        c1 = (unsigned char) toupper((int)*s1++);
        c2 = (unsigned char) toupper((int)*s2++);
    } while ((c1 == c2) && (c1 != '\0'));

    return c1 - c2;
}


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


    *out = 0;
    memset(st, 0, sizeof(*st));
    st->csn = "";
    st->srv_addr = LTRD_ADDR_DEFAULT;

    /************************** разбор опций *****************************/
    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_CRATE_SN:
                st->csn = optarg;
                break;
            case OPT_ALL:
                st->all = 1;
                break;
            case OPT_CRATE_TYPE: {
                    unsigned i;
                    for (i=0; i < sizeof(f_crates)/sizeof(f_crates[0]) && (st->crate==NULL); i++) {
                        if (!f_stricmp(optarg, f_crates[i].name)) {
                            st->crate = &f_crates[i];
                        }
                    }

                    if (st->crate == NULL) {
                        fprintf(stderr, "Unsupported crate type specified! Use ltreu-firm-update --help for more info\n");
                        err = ERR_INVALID_USAGE;
                    }
                }
                break;
            case OPT_DSP_FIRM:
                st->dsp_firm = optarg;
                break;
            case OPT_FPGA_FIRM:
                st->fpga_firm = optarg;
                break;
            case OPT_SRV_ADDR:
                st->srv_addr = inet_addr(optarg);
                if ((st->srv_addr == INADDR_NONE) || (st->srv_addr == INADDR_ANY)) {
                    fprintf(stderr, "Invalid ltrd IP-address!\n");
                    err = ERR_INVALID_USAGE;
                }
                break;
            default:
                break;
        }
    }

    if (!err) {
        if (st->crate == NULL) {
            fprintf(stderr, "Crate type is not specified!\n");
            err = ERR_INVALID_USAGE;
        }

        if ((st->dsp_firm == NULL) && (st->fpga_firm == NULL)) {
            fprintf(stderr, "Neither FPGA-firmware nor DSP-firmware files are not specified!\n");
            err = ERR_INVALID_USAGE;
        }
    }
    return err;
}


static void f_cb(DWORD stage, DWORD done_size, DWORD full_size, void *cb_data) {
    if ((stage == LTR030_LOAD_STAGE_WRITE) && (done_size == 0)) {
        printf("Write firmware...\n");
    } else if ((stage == LTR030_LOAD_STAGE_VERIFY) && (done_size == 0)) {
        printf("\nVerify firmware...\n");
    } else {
        printf(".");
    }
    fflush(stdout);
}


static int f_update_fiwrm(t_ltrcfg_state *st, const char *serial) {
    TLTR030 hltr;
    INT err = LTR_OK;

    LTR030_Init(&hltr);
    err = LTR030_Open(&hltr, st->srv_addr, LTRD_PORT_DEFAULT, LTR_CRATE_IFACE_USB, serial);
    if (err != LTR_OK) {
        fprintf(stderr, "Cannot write crate settings! Error %d: %s\n",
                err, LTR030_GetErrorString(err));
    } else {
        if (st->dsp_firm) {
            printf("Burn dsp firmware. file = %s, serial = %s\n", st->dsp_firm, serial);
            err = LTR030_LoadDspFirmware(&hltr, st->dsp_firm, f_cb, NULL);
            if (err == LTR_OK) {
                printf("\nDSP firmware was writen successfully!\n");
            } else {
                fprintf(stderr, "\nCannot write DSP firmware! Error %d: %s\n",
                        err, LTR030_GetErrorString(err));
            }
        }

        if ((err == LTR_OK) && st->fpga_firm) {
            printf("Burn FPGA firmware. file = %s, serial = %s\n", st->fpga_firm, serial);
            err = LTR030_LoadFpgaFirmware(&hltr, st->fpga_firm, f_cb, NULL);
            if (err == LTR_OK) {
                printf("\nFPGA firmware was writen successfully!\n");
            } else {
                fprintf(stderr, "\nCannot write FPGA firmware! Error %d: %s\n",
                        err, LTR030_GetErrorString(err));
            }
        }

        LTR030_Close(&hltr);
    }
    return err;
}


int main(int argc, char **argv) {
    t_ltrcfg_state st;
    int out, err;

    memset(&st, 0, sizeof(st));

    /* разбор опций */
    err = f_parse_options(&st, argc, argv, &out);

    if (!err && !out) {
        TLTR srv;

        LTR_Init(&srv);
        err = LTR_OpenSvcControl(&srv, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT);
        if (err != LTR_OK) {
            fprintf(stderr, "Cannot open server control connection! Error %d: %s\n",
                    err, LTR030_GetErrorString(err));
        } else {
            char (*serial_list)[LTR_CRATE_SERIAL_SIZE] = 0;
            TLTR_CRATE_INFO *info=0;
            int *fnd_pos=0;
            DWORD fnd;

            /* получаем полный список крейтов со всеми игтерфайсами */
            err = LTR_GetCratesEx(&srv, 0, 0, &fnd, NULL, NULL,NULL);
            if (err != LTR_OK) {
                fprintf(stderr, "Cannot get crate list! Error %d: %s\n",
                        err, LTR030_GetErrorString(err));
            } else if (fnd == 0) {
                fprintf(stderr, "Cannot find LTR crate!\n");
                err = ERR_NO_CRATES;
            }

            if (err == LTR_OK) {
                serial_list = malloc(LTR_CRATE_SERIAL_SIZE*fnd);
                info = malloc(sizeof(TLTR_CRATE_INFO)*fnd);
                fnd_pos = malloc(sizeof(fnd_pos[0])*fnd);
                if ((info == NULL) || (serial_list == NULL)) {
                    err = LTR_ERROR_MEMORY_ALLOC;
                } else {
                    err = LTR_GetCratesEx(&srv, fnd, 0, NULL, &fnd, serial_list, info);
                }
            }
            LTR_Close(&srv);


            if (err == LTR_OK) {
                DWORD res_cnt = 0, i;
                /* отбираем крейты нужного типа и только подключенные по USB */
                for (i = 0; (i < fnd) && ((res_cnt==0) || st.all); i++) {
                    if ((info[i].CrateType == st.crate->type) &&
                            (info[i].CrateInterface == LTR_CRATE_IFACE_USB) &&
                            ((st.csn==0) || (st.csn[0]==0) || !strcmp(st.csn, serial_list[i]))) {
                        fnd_pos[res_cnt++] = i;
                    }
                }

                if (res_cnt == 0) {
                    fprintf(stderr, "Cannot find specified crate!\n");
                    err = ERR_NO_CRATES;
                } else {
                    int up_ok = 0, up_err = 0;
                    for (i = 0; i < res_cnt; i++) {
                        if (f_update_fiwrm(&st, serial_list[fnd_pos[i]]) == 0) {
                            up_ok++;
                        } else {
                            up_err++;
                        }
                    }
                    printf("Total crates were updated: %d, successful: %d, with error: %d\n",
                           up_ok+up_err, up_ok, up_err);
                }
            }

            free(serial_list);
            free(info);
            free(fnd_pos);
        }
    }
    return err;
}
