/* 
 * This software is licensed under terms of the GNU GENERAL PUBLIC LICENSE
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software  is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Source code is available at http://ppcenter.free.fr
 */

#include "../common/satcom.h"
#include "sc_usbdl.h"

#include "../common/SerialComm.h"

/* Include global data stuff for FDL support. */
#include "../../satcom_lib/saturn/sc_global_data.h"
#include "../../satcom_lib/sc_common.h"



////////////////////////////////////////////////////////////////////////
// DataLink protocol version 1.0 / 1.1 differenciation stuff
////////////////////////////////////////////////////////////////////////
typedef struct
{
    char*         comm_init;
    unsigned long datalen;
} usbdl_diff_t;
usbdl_diff_t _usbdl_diff[2] = 
{
    {(char*)"baud=288000 parity=N data=8 stop=2",  79}, // #0: original hardware configuration (green LED)
    {(char*)"baud=375000 parity=N data=8 stop=2", 191}  // #1: final hardware configuration (red LED)
};
/* --- Global stuff used in the program. --- */
/* COM configuration. */
char*         _com_init = NULL;
/* Packet's data maximum size. */
unsigned long _datalen  = 0;
/* Ignore packet's cheksum or not. */
int _nochksum = 0;
/* Enable/disable stream compression. */
int _use_compress = 0;



////////////////////////////////////////////////////////////////////////
// COM stuff
////////////////////////////////////////////////////////////////////////
SerialComm* _com = NULL;
char _com_port[1024];



/* Close communication with USB DataLink. */
int usbdl_close(void)
{
    scl_log(SCLT_DLLPG, 4, "Close USB Data Link connection ...");
    delete _com; _com = NULL;

    return SC_NOERROR;
}

/**
 * USB Data Link hardware type ID
 *  0: original revision
 *  1: final revision
 * -1: try to auto detect HW type
**/

int _usbdl_hw_type = -1;
/* Open COM port to USB DataLink. */
int usbdl_open(void)
{
    int err = SC_NOERROR;

    if((_usbdl_hw_type == 0) || (_usbdl_hw_type == 1))
    {
        /* Manually set HW configuration values. */
        _com_init = _usbdl_diff[_usbdl_hw_type].comm_init;
        _datalen  = _usbdl_diff[_usbdl_hw_type].datalen;
    }
    else
    {
        /* Auto detect HW, and set its configuration values. */
        scl_log(SCLT_DLLPG, 0, "Sorry, hardware auto-detect feature not implemented yet !");

        _com_init = _usbdl_diff[0].comm_init;
        _datalen  = _usbdl_diff[0].datalen;
    }
    scl_log(SCLT_DLLPG, 4, "Set COM init string to \"%s\"", _com_init);
    scl_log(SCLT_DLLPG, 4, "Set packet datalen to %d bytes", _datalen);
    _nochksum = 0;

    /* In the case we re-open port. */
    scl_log(SCLT_DLLPG, 4, "_com#1 = 0x%08X", _com);
    if(_com != NULL) usbdl_close();

    /* Open USB Data Link. */
    scl_log(SCLT_DLLPG, 4, "Open USB Data Link connection on port %s ...", _com_port);
    _com = new SerialComm(_com_port, _com_init, 1000, 1000);
    scl_log(SCLT_DLLPG, 4, "_com#2 = 0x%08X", _com);
    err = _com->Start();
    scl_log(SCLT_DLLPG, 4, "_com#3 = 0x%08X", _com);
    return err;
}





////////////////////////////////////////////////////////////////////////
// DataLink protocol: single packet level
////////////////////////////////////////////////////////////////////////
/* Number of times to try to exchange data before returning a transmission error. */
#define TRY_MAXCOUNT 3


/* DataLink packet, any protocol. */
#pragma pack(1)	
typedef struct _dlPacket
{
    unsigned char directn;   /* PC->AR : 0x5A, AR->PC : 0xA5.                 */
    unsigned char pktsize;   /* packet size (counted from next byte).         */
    unsigned char pktflag;   /* packet type.                                  */
    unsigned long address;   /* read (write) address.                         */
    unsigned char datalen;   /* data length.                                  */
    unsigned char data[300]; /* data (max 191 bytes), then checksum (1 byte). */
}dlPacket;
/**
 *Note : data[300]
 * data[192] (191 data bytes + checksum) may be enough, but 
 * 300 bytes allows to avoid bug when reading bad-formed 
 * packets (example : datalen == 0xFF).
**/
#pragma pack()



/**
 * Compute packet checksum.
**/
unsigned char pck_get_checksum(dlPacket* p)
{
    unsigned long s;
    unsigned char i, len;
    unsigned char* ptr;

    len = p->pktsize;
    ptr = (unsigned char*)(&(p->pktsize));
    s = 0;
    for(i=0; i<len; i++)
    {
        s += ptr[i];
        //scl_log(SCLT_DLLPG, 5, "^ s+=%02X -> %08X", ptr[i], s);
    }
    unsigned char checksum = s&(0xFF);
//scl_log(SCLT_DLLPG, 5, "^ END %08X", checksum);
    return (checksum);
}

/**
 * Swap unsigned long data (used for setting address).
**/
unsigned long ulong_swap(unsigned long data)
{
    unsigned char tmp[4];
    unsigned long ret = 0;
    tmp[0] = (data >>  0) & 0xFF;
    tmp[1] = (data >>  8) & 0xFF;
    tmp[2] = (data >> 16) & 0xFF;
    tmp[3] = (data >> 24) & 0xFF;
    ret |= tmp[0] << 24;
    ret |= tmp[1] << 16;
    ret |= tmp[2] <<  8;
    ret |= tmp[3] <<  0;
    return ret;
}


/**
 * Print packet contents.
**/
void pck_print(dlPacket* p)
{
    unsigned char chks = pck_get_checksum(p);
    unsigned char* ptr = (unsigned char*)p;

    scl_log(SCLT_DLLPG, 5, " Packet contents : (Header = 0x %02X %02X %02X %02X %02X %02X %02X %02X)", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7]);
    scl_log(SCLT_DLLPG, 5, " |-directn=0x%02X", p->directn);
    scl_log(SCLT_DLLPG, 5, " |-pktsize=0x%02X (%d)", p->pktsize, p->pktsize);
    scl_log(SCLT_DLLPG, 5, " |-pktflag=0x%02X", p->pktflag);
    scl_log(SCLT_DLLPG, 5, " |-address=0x%04X", ulong_swap(p->address));
    scl_log(SCLT_DLLPG, 5, " |-datalen=0x%02X (%d)", p->datalen, p->datalen);

    int i, j, l, m;
    char tmp[256];
    char buff[16*1024];
    l = (p->pktsize-7);
    //l = (p->datalen > 191 ? 191 : p->datalen);
    m = 8;
    for(i=0; i<=l; i+=m)
    {
        buff[0] = '\0';
        for(j=0; j<=m; j++)
        {
            if((i+j) <= l)
            {
                sprintf(tmp, "D[%3d]=0x%02X(%c), ", i+j, p->data[i+j], char2pchar(p->data[i+j]));
                strcat(buff, tmp);
            }
        }
        scl_log(SCLT_DLLPG, 5, " |-data[%3d~%3d]: %s", i, i+m-1, buff);
    }

    scl_log(SCLT_DLLPG, 5, " |-cheksum=0x%02X (CALC=0x%02X -> %s)", p->data[p->pktsize-7], chks, (p->data[p->pktsize-7] == chks ? "OK" : "NG"));
}


/**
 * Reset packet contents.
**/
void pck_reset(dlPacket* p)
{
    char* str = "RESET***";
    int l = strlen(str);
    int i;
    char* ptr = (char*)p;

    for(i=0; i<sizeof(dlPacket); i++)
    {
        ptr[i] = (i < 8 ? 0x42 : str[i%l]);
    }
}


/**
 * Check received packet integrity.
 * Return SC_NOERROR if no error.
**/
int pck_check(dlPacket* p)
{
    /* Packet size check. */
    if ((p->pktsize > 198) || (p->pktsize < 7))
        return SC_ERROR_PKTSIZE;
    /* Packet/data length check. */
    // VBT a valider
    //if (p->pktsize != (p->datalen + 7))
    //    return SC_ERROR_PKTDATALEN;

    /* Checksum. */
    if(_nochksum == 0)
    {
        if (pck_get_checksum(p) != p->data[p->pktsize-7])
            return SC_ERROR_PKTCHECKSUM;
    }

    return SC_NOERROR;
}





/**
 * Exchange a "receive data" packet.
**/
int rpck_exch(unsigned long address, unsigned char* buffer, 
              unsigned long pos, unsigned long len, unsigned long flag)
{
    dlPacket pkt;
    int err = SC_NOERROR;

    scl_log(SCLT_DLLPG, 3, "[rpck_exch]Enter function.\n");
    unsigned char pktflag;
    if(flag == 0)
        pktflag = 0x01; /* Start packet. */
    else if(flag == 1)
        pktflag = 0x11; /* Middle packet. */
    else
        pktflag = 0x21; /* End packet. */

    int trycnt;
    for(trycnt=0; trycnt<TRY_MAXCOUNT; trycnt++)
    {
        scl_log(SCLT_DLLPG, 3, "[rpck_exch]+ Send rcv packet try#%d START", trycnt);
        err = SC_NOERROR;

        /* Set a "receive data" request. */
        pck_reset(&pkt);
        pkt.directn = 0x5A;
        pkt.pktflag = pktflag;
        pkt.address = ulong_swap(address+pos);
        pkt.pktsize = 7;
        pkt.datalen = len;
        pkt.data[pkt.pktsize-7] = pck_get_checksum(&pkt);

        scl_log(SCLT_DLLPG, 5, "[rpck_exch] Send a \"receive data\" request ..."); pck_print(&pkt);
        err = _com->Send((void*)(&pkt), 9, NULL);
        if(err != SC_NOERROR)
        {
            scl_log(SCLT_DLLPG, 2, "[rpck_exch]  ---abort: could not send packet.");
            goto receive_packet_end;
        }

        /* Receive header from USB DataLink. */
        pck_reset(&pkt);
        scl_log(SCLT_DLLPG, 5, "[rpck_exch] Waiting for answer header (9 bytes) & data (%d bytes) ...", len);
        err = _com->Receive((void*)(&pkt), 9+len, NULL);
        pck_print(&pkt);
        if(err != SC_NOERROR)
        {
            scl_log(SCLT_DLLPG, 2, "[rpck_exch]  ---abort: could not receive header.");
            goto receive_packet_end;
        }


        /* Size is too long ? */
        if(pkt.datalen > _datalen)
        {
            err = SC_ERROR_PKTRCV0;
            scl_log(SCLT_DLLPG, 2, "[rpck_exch]  ---abort: received header's datalen is too long.");
            goto receive_packet_end;
        }

        /* Generic error check (size, datalen, checksum). */
        err = pck_check(&pkt);
        if(err != SC_NOERROR)
        {
            scl_log(SCLT_DLLPG, 2, "[rpck_exch]  ---abort: wrong packet integrity.");
            goto receive_packet_end;
        }

        /* Datalink-related error check. */
        if(pkt.datalen != len)
        {
            scl_log(SCLT_DLLPG, 2, "[rpck_exch]  ---abort: wrong datalen.");
            err = SC_ERROR_PKTRCV1;
            goto receive_packet_end;
        }
        if(pkt.pktflag != 0xFF)
        {
            scl_log(SCLT_DLLPG, 2, "[rpck_exch]  ---abort: wrong packet flag.");
            err = SC_ERROR_PKTRCV0;
            goto receive_packet_end;
        }

        /*  No error ? If so, we can copy the received data to the destination buffer. */
        memcpy((void*)(buffer+pos), (void*)(pkt.data), len);

        /* Error check. */
receive_packet_end:
        if(err == SC_NOERROR)
        { /* If no error, exit the try loop. */
            break;
        }
        else
        { /* If error happended, display error and stay in the loop. */
            scl_log(SCLT_DLLPG, 1, "[rpck_exch] *** Warning: Receive data was NG (code = %d, try count = %d).", err, trycnt);
        }

        scl_log(SCLT_DLLPG, 3, "[rpck_exch]+ Send rcv packet try#%d END\n", trycnt);
    }

    scl_log(SCLT_DLLPG, 3, "[rpck_exch]Exit function (err=%d)", err);
    return err;
}




/**
 * Exchange a "send data" packet.
**/
int spck_exch(unsigned long address, unsigned char* buffer, 
              unsigned long pos, unsigned long len, unsigned char pktflag)
{
    dlPacket pkt;
    int err = SC_NOERROR;

    scl_log(SCLT_DLLPG, 3, "[spck_exch]Enter function.\n");
    int trycnt;
    for(trycnt=0; trycnt<TRY_MAXCOUNT; trycnt++)
    {
        scl_log(SCLT_DLLPG, 3, "[spck_exch]+ Send snd packet try#%d START", trycnt);
        err = SC_NOERROR;

        /* Send a "send data" request. */
        pck_reset(&pkt);
        pkt.directn = 0x5A;
        pkt.pktflag = pktflag;
        pkt.address = ulong_swap(address+pos);
        /* Set lengths. */
        pkt.datalen = len;
        pkt.pktsize = pkt.datalen + 7;
        /* Copy data. */
        if(pkt.datalen != 0)
            memcpy((void*)pkt.data, (void*)(buffer+pos), pkt.datalen);
        /* Put checksum. */
        //pkt.data[pkt.pktsize-7] = pck_get_checksum(&pkt);
        // VBT
        pkt.data[pkt.datalen] = pck_get_checksum(&pkt);

        scl_log(SCLT_DLLPG, 5, "[spck_exch] Send a \"send data\" request ..."); pck_print(&pkt);
        //err = _com->Send((void*)(&pkt), 9, NULL);
        // VBT
        err = _com->Send((void*)(&pkt), 9+len, NULL);
        if(err != SC_NOERROR)
        {
            scl_log(SCLT_DLLPG, 2, "[spck_exch]  ---abort: could not send packet.");
            goto send_packet_end;
        }

        /* Receive response from USB DataLink. */
        scl_log(SCLT_DLLPG, 5, "[spck_exch] Waiting for answer ...");
        pck_reset(&pkt);
        err = _com->Receive((void*)(&pkt), 9, NULL);
        if(err != SC_NOERROR)
        {
            scl_log(SCLT_DLLPG, 2, "[spck_exch]  ---abort: could not receive header.");
            goto send_packet_end;
        }
        pck_print(&pkt);

        /* Generic error check (size, datalen, checksum). */
        err = pck_check(&pkt);
        if(err != SC_NOERROR)
        {
            scl_log(SCLT_DLLPG, 2, "[spck_exch]  ---abort: wrong packet integrity.");
            goto send_packet_end;
        }

        /* Datalink-related error check. */
        if(pkt.datalen != 0)
        {
            scl_log(SCLT_DLLPG, 2, "[spck_exch]  ---abort: datalen is not null.");
            err = SC_ERROR_PKTRCV1;
            goto send_packet_end;
        }
        if(pkt.pktflag != 0xFF)
        {
            scl_log(SCLT_DLLPG, 2, "[spck_exch]  ---abort: wrong packet flag.");
            err = SC_ERROR_PKTRCV0;
            goto send_packet_end;
        }

        /* Error check. */
send_packet_end:
        if(err == SC_NOERROR)
        { /* If no error, exit the try loop. */
            break;
        }
        else
        { /* If error happended, display error and stay in the loop. */
            scl_log(SCLT_DLLPG, 1, "[spck_exch] *** Warning: Send data was NG (code = %d, try count = %d).", err, trycnt);
        }

        scl_log(SCLT_DLLPG, 3, "[spck_exch]+ Send snd packet try#%d END\n", trycnt);
    }

    scl_log(SCLT_DLLPG, 3, "[spck_exch]Exit function");
    return err;
}







////////////////////////////////////////////////////////////////////////
// DataLink: arbitrary data transfer
////////////////////////////////////////////////////////////////////////


/**
 *  Receive data from Saturn.
 *  Custom error assignment
 *   SC_ERROR_PKTRCV0 : Could send packet, but received an "error with request" packet.
 *   SC_ERROR_PKTRCV1 : Received data's size differs from requested data size.
 *   SC_ERROR_PKTRCV2 : 
 *   SC_ERROR_PKTRCV3 : 
**/
char usbdl_receive(unsigned long address, unsigned char* buffer, unsigned long length, unsigned char update_status)
{
    int err = SC_NOERROR;
    unsigned long psz = _datalen;

    /* Trivial case where nothing to send. */
    if(length == 0) return SC_NOERROR;

    unsigned long p=0, f=0, l=psz, lok=0;
    for(p=0; p<length; p+=psz)
    {
        l = psz;
        if((length-p) < psz) {l=length-p;}
        if((p+psz) >= length)
        {
            if(f != 0)
            {
                lok = 1;
                f = 2;
            }
            else
            {
                l = l/2; if(l==0) {l=1;}
            }
        }

        if(p%10 == 0)
        {
            if(update_status) { STATUS_CALLBACK(CLBK_RECEIVE, address, buffer, p, length); }
        }
        scl_log(SCLT_DLLPG, 5, "[SC_usbdl::sc_receive] rpck_exch p=%4d, l=%4d, f=%3d, lok=%d", p, l, f, lok);
        err = rpck_exch(address, buffer, p, l, f);
        scl_log(SCLT_DLLPG, 5, "[SC_usbdl::sc_receive] err1=%d", err);
        if(err != SC_NOERROR)
        {
            return err;
        }

        f=1;
    }
    if(lok==0)
    {
        f = 2;
        if(length == 1)
        { /* special case where we have to read twice the same byte. */
            p = 0;
            l = 1;
        }
        else
        {
            p = p-psz + l;
            l = length - p;
        }
        scl_log(SCLT_DLLPG, 5, "[SC_usbdl::sc_receive] rpck_exch %4d,%4d, %3d*", p, l, f);
        err = rpck_exch(address, buffer, p, l, f);
        scl_log(SCLT_DLLPG, 5, "[SC_usbdl::sc_receive] err2=%d", err);
        if(err != SC_NOERROR)
        {
            return err;
        }
    }

    return SC_NOERROR;
}




/**
 *  Send data to Saturn.
 *  Custom error assignment
 *   SC_ERROR_PKTSND0 : Could send packet, but received an "error with request" packet.
 *   SC_ERROR_PKTSND1 : Received packet size is not equal to zero.
 *   SC_ERROR_PKTSND2 : 
 *   SC_ERROR_PKTSND3 : 
**/
char usbdl_send(unsigned long address, unsigned char* buffer, unsigned long length, char exec_flag, unsigned char update_status)
{
    int err = SC_NOERROR;
    unsigned long psz = _datalen;
    unsigned long pktflag = (exec_flag==SC_EXEC ? 0x19 : 0x09);

    /* Trivial case where nothing to send. */
    if(length == 0) return SC_NOERROR;

    unsigned long fp=0, fl=0;
    unsigned long p=0, l=psz;
    for(p=0; p<length; p+=psz)
    {
        l = ((length-p) < psz ? length-p : psz);
        if(p==0)
        {
            fp = p;
            fl = l;
        }
        else
        {
            if(p%10 == 0)
            {
                if(update_status) { STATUS_CALLBACK(CLBK_RECEIVE, address, buffer, p, length); }
            }
            err = spck_exch(address, buffer, p, l, 0x09);
            if(err != SC_NOERROR)
            {
                return err;
            }
        }
    }

    err = spck_exch(address, buffer, fp, fl, pktflag);
    if(err != SC_NOERROR)
    {
        return err;
    }

    return SC_NOERROR;
}



////////////////////////////////////////////////////////////////////////
// Fast Data Link (FDL) related
////////////////////////////////////////////////////////////////////////
sc_global_t sc_global;
/* Temporary buffer where to put the compressed data. */
unsigned char _comp_buff[SC_BUFF_MAXSIZE];


void swap_ushort(unsigned short* ptr)
{
    unsigned short val = *ptr;
    unsigned char* str = (unsigned char*)ptr;
    str[0] = (val >>  8) & 0xFF;
    str[1] = (val >>  0) & 0xFF;
}
void swap_ulong(unsigned long* ptr)
{
    unsigned long val = *ptr;
    unsigned char* str = (unsigned char*)ptr;
    str[0] = (val >> 24) & 0xFF;
    str[1] = (val >> 16) & 0xFF;
    str[2] = (val >>  8) & 0xFF;
    str[3] = (val >>  0) & 0xFF;
}


/**
 *  Get FDL setting values.
**/
char fdl_init(void)
{
    int err = SC_NOERROR;

    /* Reset global data structure. */
    memset((void*)(&sc_global), 0, sizeof(sc_global_t));

    /* Retrieve FDL status. */
    unsigned long offs, len;
    unsigned char* ptr = (unsigned char*)(&sc_global);
    offs = (unsigned long)(&(sc_global.fdl_int_buff)) - (unsigned long)(&sc_global);
    len  = 4 + 2 + 1 + 1;

    err = usbdl_receive(SC_GLOBAL_DATA_ADR+offs/*address*/, ptr+offs/*buffer*/, len, 0/*update_status*/);
    if(err != SC_NOERROR) return err;

    /* Swap received data. */
    //unsigned long fdl_int_buff;
    swap_ulong(&(sc_global.fdl_int_buff));
    scl_log(SCLT_DLLPG, 4, "fdl_init: fdl_int_buff = 0x%08X", sc_global.fdl_int_buff);

    //unsigned short fdl_int_leng;
    swap_ushort(&(sc_global.fdl_int_leng));
    scl_log(SCLT_DLLPG, 4, "fdl_init: fdl_int_leng = %d", sc_global.fdl_int_leng);

    //unsigned char fdl_magic1;
    scl_log(SCLT_DLLPG, 4, "fdl_init: fdl_magic1 = 0x%02X", sc_global.fdl_magic1);

    //unsigned char fdl_magic2;
    scl_log(SCLT_DLLPG, 4, "fdl_init: fdl_magic2 = 0x%02X", sc_global.fdl_magic2);

    return SC_NOERROR;
}

/**
 *  Request a data (De)Compression, and receive its return value.
**/
char fdl_request(unsigned char req, unsigned long usr_buff, unsigned long usr_leng)
{
    int err = SC_NOERROR;

    /* Set and swap parameters. */
    //unsigned char fdl_req;
    sc_global.fdl_req = req;
    //unsigned long fdl_usr_buff;
    sc_global.fdl_usr_buff = usr_buff; swap_ulong(&(sc_global.fdl_usr_buff));
    //unsigned long fdl_usr_leng;
    sc_global.fdl_usr_leng = usr_leng; swap_ulong(&(sc_global.fdl_usr_leng));


    /* Send FDL request. */
    unsigned long offs, len;
    unsigned char* ptr = (unsigned char*)(&sc_global);
    offs = (unsigned long)(&(sc_global.fdl_int_buff)) - (unsigned long)(&sc_global);
    len  = 1 + 4 + 4;

    err = usbdl_send(SC_GLOBAL_DATA_ADR+offs/*address*/, ptr+offs/*buffer*/, len, 0/*exec_flag*/, 0/*update_status*/);
    if(err != SC_NOERROR) return err;



    /* Retrieve request return value. */
    offs = (unsigned long)(&(sc_global.fdl_return)) - (unsigned long)(&sc_global);
    len  = 2;

    err = usbdl_receive(SC_GLOBAL_DATA_ADR+offs/*address*/, ptr+offs/*buffer*/, len, 0/*update_status*/);
    if(err != SC_NOERROR) return err;

    /* Swap received data. */
    //unsigned char fdl_req;
    scl_log(SCLT_DLLPG, 4, "fdl_request: fdl_req = %d (%c)", sc_global.fdl_req, char2pchar(sc_global.fdl_req));

    //unsigned long fdl_usr_buff;
    swap_ulong(&(sc_global.fdl_usr_buff));
    scl_log(SCLT_DLLPG, 4, "fdl_request: fdl_usr_buff = 0x%08X", sc_global.fdl_usr_buff);

    //unsigned long fdl_usr_leng;
    swap_ulong(&(sc_global.fdl_usr_leng));
    scl_log(SCLT_DLLPG, 4, "fdl_request: fdl_usr_leng = %d", sc_global.fdl_usr_leng);

    //unsigned short fdl_return;
    swap_ushort(&(sc_global.fdl_return));
    scl_log(SCLT_DLLPG, 4, "fdl_request: fdl_return = %d", sc_global.fdl_return);

    return SC_NOERROR;
}

/**
 *  Return the size of a buffer after compressing it.
 *  Useful in the case we want to evaluate a compression ratio.
**/
unsigned long fdl_get_compressed_size(unsigned char* ind, unsigned long inl)
{
    unsigned char* buff = NULL;
    unsigned long len = 0;

    buff = (unsigned char*)malloc((inl + SC_COMP_HEADER_MAXSIZE)*sizeof(unsigned char));
    if(buff == NULL) return inl;

    /* Compress data. */
    len = comp_exec(ind, inl, buff);
    /* In the case compression failed (?) */
    if(len == 0) len = inl;

    free(buff); buff = NULL;
    return len;
}



////////////////////////////////////////////////////////////////////////
// Derived class functions
////////////////////////////////////////////////////////////////////////



/**
 *  DLL startup : Display help screen.
**/
char SC_usbdl::sc_start(void)
{
    /* Display help screen. */
    scl_log(SCLT_DLLPG, 4, "USB Data Link (Red/Green LED Hardware) interface.");
    scl_log(SCLT_DLLPG, 4, "Available parameters :");
    scl_log(SCLT_DLLPG, 4, " port       <name > : Set specified port (ex: COM1,COM2,etc).");
    scl_log(SCLT_DLLPG, 4, "                      Need to be set before setting 'device_led'.");
    scl_log(SCLT_DLLPG, 4, " device_led <color> : Notify device LED color (= hardware revision).");
    scl_log(SCLT_DLLPG, 4, "                       green: original hardware configuration (green LED).");
    scl_log(SCLT_DLLPG, 4, "                       red: final hardware configuration (red LED).");
    scl_log(SCLT_DLLPG, 4, "                       auto: try device ato-detection. You need to set 'port' before !");
    scl_log(SCLT_DLLPG, 4, " compress   <0/1  > : Enable/disable stream compression.");
    scl_log(SCLT_DLLPG, 4, " datalen    <len  > : Set packets's maximum data length. (debug use)");
    scl_log(SCLT_DLLPG, 4, " nochksum   <0/1  > : Ignore packet's checksum. (debug use)");
    scl_log(SCLT_DLLPG, 4, " display    <1    > : Display configuration settings.");
    
    /* Init internal settings. (final HW config on COM1) */
    strcpy(_com_port, "COM1");
    _com_init = _usbdl_diff[1].comm_init;
    _datalen  = _usbdl_diff[1].datalen;

    return SC_NOERROR;
}

/**
 *  Set internal parameter.
**/
char SC_usbdl::sc_set(char* parameter, char* value)
{
    int err = SC_NOERROR;

    /* Parse parameters. */
    //scl_log(SCLT_DLLPG, 9, "param = %s", parameter);
    if(!stricmp(parameter, "port"))
    {
        strcpy(_com_port, value);
        scl_log(SCLT_DLLPG, 4, "Set COM port to \"%s\"", _com_port);

        /* In the case we re-define port, re-open connection. */
        if(_com != NULL)
        {
            if(usbdl_open() != SC_NOERROR) return _com->GetErrorCode();
        }
    }
    else if(!stricmp(parameter, "device_led"))
    {
        if(!stricmp(value, "green"))
        { /* Original Hardware configuration. */
            _usbdl_hw_type = 0;
        }
        else if(!stricmp(value, "red"))
        { /* Final Hardware configuration. */
            _usbdl_hw_type = 1;
        }
        else
        { /* Auto-detect. */
            _usbdl_hw_type = -1;
        }
        /* Open USB Data Link. */
        err = usbdl_open();
        if(err != SC_NOERROR) return err;
    }
    else if(!stricmp(parameter, "compress"))
    {
        _use_compress = strtoul(value, NULL, 0);
        scl_log(SCLT_DLLPG, 4, "Set compression usage to %d", _use_compress);
    }
    else if(!stricmp(parameter, "datalen"))
    {
        _datalen = strtoul(value, NULL, 0);
        scl_log(SCLT_DLLPG, 4, "Set datalen to %d bytes", _datalen);
    }
    else if(!stricmp(parameter, "nochksum"))
    {
        _nochksum = strtoul(value, NULL, 0);
        scl_log(SCLT_DLLPG, 4, "Set nochksum to %d", _nochksum);
    }
    else if(!stricmp(parameter, "display"))
    {
        /* Display parameters contents to log. */
        scl_log(SCLT_DLLPG, 1, "USB Data Link internal settings");
        scl_log(SCLT_DLLPG, 1, "|COM port        : \"%s\"", _com_port);
        scl_log(SCLT_DLLPG, 1, "|COM init string : \"%s\"", _com_init);
        scl_log(SCLT_DLLPG, 1, "|Compression use : %d", _use_compress);
        scl_log(SCLT_DLLPG, 1, "|Packet datalen  : %d bytes", _datalen);
        scl_log(SCLT_DLLPG, 1, "|Ignore checksum : %d", _nochksum);
    }
    else
    {
        scl_log(SCLT_DLLPG, 0, "ERROR: Unknown parameter : \"%s\"", parameter);
        return SC_ERROR_PARAMETER;
    }

    return SC_NOERROR;
}

/**
 *  Close USB Data Link.
**/
char SC_usbdl::sc_end(void)
{
    return usbdl_close();
}





/**
 *  Receive data from Saturn.
**/
char SC_usbdl::sc_receive(unsigned long address, unsigned char* buffer, unsigned long length)
{
    int err = SC_NOERROR;
    int fdl = 0;

    /* Indicates that the process started. */
    STATUS_CALLBACK(CLBK_START, 0, buffer, 0, length);

    if((_use_compress == 0)
    || (length <= SC_COMP_MINSIZE))
    { /* Don't use FDL. */
        fdl = 0;
        scl_log(SCLT_DLLPG, 4, "sc_receive: Don't use FDL");
    }
    else
    { /* Can use FDL ? */
        err = fdl_init();
        if(err != SC_NOERROR)
        {
            STATUS_CALLBACK(CLBK_END, 0, buffer, length, length);
            return err;
        }

        /* Check that FDL is enabled on Saturn. */
        if((sc_global.fdl_magic1 == FDL_MAGIC1)
        && (sc_global.fdl_magic2 == FDL_MAGIC2))
        { /* Can use FDL. */
            fdl = 1;
            scl_log(SCLT_DLLPG, 4, "sc_receive: Can use FDL");
        }
        else
        { /* Unable to use FDL. */
            fdl = 0;
            scl_log(SCLT_DLLPG, 4, "sc_receive: Unable to use FDL (Magic numbers = 0x%02X 0x%02X)", sc_global.fdl_magic1, sc_global.fdl_magic2);
        }
    }


    if(fdl == 1)
    { /* Decompress data little by little while receiving it. */

        unsigned long  paddress = address;
        unsigned char* pbuffer  = buffer;
        unsigned long  plength  = length;

        /* Get compression buffer address. */
        unsigned long buffer_address, buffer_length;
        buffer_address = sc_global.fdl_int_buff;
        buffer_length  = sc_global.fdl_int_leng;
        scl_log(SCLT_DLLPG, 5, "[FDL_receive]*** Compression buffer address = 0x%08X length = %d", buffer_address, buffer_length);


        do
        {

            //unsigned short csrc_length = (csrc_length > plength ? plength : buffer_length - SC_COMP_HEADER_MAXSIZE);
            unsigned short csrc_length = MIN(plength, buffer_length - SC_COMP_HEADER_MAXSIZE);
            unsigned short cdst_length;
            scl_log(SCLT_DLLPG, 8, "[FDL_receive]+++ plength=%d"    , plength);
            scl_log(SCLT_DLLPG, 8, "[FDL_receive]+++ MAX=%d"        , buffer_length - SC_COMP_HEADER_MAXSIZE);
            scl_log(SCLT_DLLPG, 8, "[FDL_receive]+++ csrc_length=%d", csrc_length);

            /* Perform compression on Saturn side. */
            scl_log(SCLT_DLLPG, 5, "[FDL_receive]*** Compress Data (paddress=0x%08X, csrc_length=%d) ...", paddress, csrc_length);
            err = fdl_request('C'/*req*/, paddress/*usr_buff*/, csrc_length/*usr_leng*/);
            if(err != SC_NOERROR)
            {
                STATUS_CALLBACK(CLBK_END, 0, buffer, pbuffer-buffer, length);
                return err;
            }

            /* Get compressed length. */
            cdst_length = sc_global.fdl_return;
            scl_log(SCLT_DLLPG, 5, "[FDL_receive]*** Compressed length = %d bytes", cdst_length);


            /* Request compressed data. */
            scl_log(SCLT_DLLPG, 5, "[FDL_receive]*** Receive compressed data (buffer_address=0X%08X, cdst_length=%d) ...", buffer_address, cdst_length);
            err = usbdl_receive(buffer_address, _comp_buff, cdst_length, 0/*update_status*/);
            if(err != SC_NOERROR)
            {
                STATUS_CALLBACK(CLBK_END, 0, buffer, pbuffer-buffer, length);
                return err;
            }


            /* Decompress data. */
            scl_log(SCLT_DLLPG, 5, "[FDL_receive]*** Decompress data ...");
            unsigned short decomp_sz = decomp_exec(_comp_buff, cdst_length, pbuffer);

            /* Update status on host program. */
            STATUS_CALLBACK(CLBK_RECEIVE, address, buffer, pbuffer-buffer, length);

            /* Update pointers and remaining size. */
            paddress += csrc_length;
            pbuffer  += csrc_length;
            plength  -= csrc_length;

            scl_log(SCLT_DLLPG, 4, "[FDL_receive] SAT SZ=%d -> COMP=%d Bytes, DECOMP=%d Bytes, REMAIN=%d Bytes.", csrc_length, cdst_length, decomp_sz, plength);
            if(decomp_sz != csrc_length)
            {
                scl_log(SCLT_DLLPG, 0, "[FDL_receive]*** ERROR when decompressing data !");
                return SC_ERROR_COMPRESSION;
            }
        } while(plength != 0);

    }
    else
    { /* Transfer data without compression. */
        /* Receive data without using compression. */
        err = usbdl_receive(address, buffer, length, 1/*update_status*/);
        if(err != SC_NOERROR)
        {
            STATUS_CALLBACK(CLBK_END, 0, buffer, length, length);
            return err;
        }
    }

    /* Indicates that the process ended */
    STATUS_CALLBACK(CLBK_END, 0, buffer, length, length);

    return SC_NOERROR;
}




/**
 *  Send data to Saturn.
**/
char SC_usbdl::sc_send(unsigned long address, unsigned char* buffer, unsigned long length, char exec_flag)
{
    int err = SC_NOERROR;
    int fdl = 0;

    /* Indicates that the process started. */
    STATUS_CALLBACK(CLBK_START, 0, buffer, 0, length);

    if((_use_compress == 0)
    || (length <= SC_COMP_MINSIZE))
    { /* Don't use FDL. */
        fdl = 0;
        scl_log(SCLT_DLLPG, 4, "sc_receive: Don't use FDL");
    }
    else
    { /* Can use FDL ? */
        err = fdl_init();
        if(err != SC_NOERROR)
        {
            STATUS_CALLBACK(CLBK_END, 0, buffer, length, length);
            return err;
        }

        /* Check that FDL is enabled on Saturn. */
        if((sc_global.fdl_magic1 == FDL_MAGIC1)
        && (sc_global.fdl_magic2 == FDL_MAGIC2))
        { /* Can use FDL. */
            fdl = 1;
            scl_log(SCLT_DLLPG, 4, "sc_receive: Can use FDL");

            unsigned long comp_len = fdl_get_compressed_size(buffer, length);
            scl_log(SCLT_DLLPG, 5, "sc_receive: comp_test: %d bytes -> %d bytes", length, comp_len);
            /* If compressed length > length*95%, don't use FDL. */
            if((comp_len*100) > (length*95))
            {
                scl_log(SCLT_DLLPG, 1, "sc_receive: bad compression ratio -> don't use FDL");
                fdl = 0;
            }
        }
        else
        { /* Unable to use FDL. */
            fdl = 0;
            scl_log(SCLT_DLLPG, 4, "sc_receive: Unable to use FDL (Magic numbers = 0x%02X 0x%02X)", sc_global.fdl_magic1, sc_global.fdl_magic2);
        }
    }


    if(fdl == 1)
    { /* Send data while compressing it little by little. */
        unsigned long  paddress = address;
        unsigned char* pbuffer  = buffer;
        unsigned long  plength  = length;

        /* Get compression buffer address. */
        unsigned long buffer_address, buffer_length;
        buffer_address = sc_global.fdl_int_buff;
        buffer_length  = sc_global.fdl_int_leng;
        scl_log(SCLT_DLLPG, 5, "[FDL_send]*** Compression buffer address = 0x%08X length = %d", buffer_address, buffer_length);


        do
        {
            //unsigned short csrc_length = (csrc_length > plength ? plength : buffer_length - SC_COMP_HEADER_MAXSIZE);
            unsigned short csrc_length = MIN(plength, buffer_length - SC_COMP_HEADER_MAXSIZE);
            unsigned short cdst_length;
            scl_log(SCLT_DLLPG, 8, "[FDL_send]+++ plength=%d"    , plength);
            scl_log(SCLT_DLLPG, 8, "[FDL_send]+++ MAX=%d"        , buffer_length - SC_COMP_HEADER_MAXSIZE);
            scl_log(SCLT_DLLPG, 8, "[FDL_send]+++ csrc_length=%d", csrc_length);

            /* Compress data. */
            scl_log(SCLT_DLLPG, 5, "[FDL_send]*** Compress Data (csrc_length=%d) ...", csrc_length);
            unsigned short comp_sz = comp_exec(pbuffer, csrc_length, _comp_buff);
            if(comp_sz == 0)
            {
                STATUS_CALLBACK(CLBK_END, 0, buffer, pbuffer-buffer, length);
                return SC_ERROR_COMPRESSION;
            }


            /* Send compressed data. */
            scl_log(SCLT_DLLPG, 5, "[FDL_send]*** Send compressed data (comp_sz = %d) ...", comp_sz);
            err = usbdl_send(buffer_address, _comp_buff, comp_sz, 0/*exec_flag*/, 0/*update_status*/);
            if(err != SC_NOERROR)
            {
                STATUS_CALLBACK(CLBK_END, 0, buffer, pbuffer-buffer, length);
                return err;
            }


            /* Perform decompression on Saturn side. */
            scl_log(SCLT_DLLPG, 5, "[FDL_send]*** Decompress data (comp_sz=%d, dst_adr=0x%08X) ...", comp_sz, paddress);
            err = fdl_request('D'/*req*/, paddress/*usr_buff*/, comp_sz/*usr_leng*/);
            if(err != SC_NOERROR)
            {
                STATUS_CALLBACK(CLBK_END, 0, buffer, pbuffer-buffer, length);
                return err;
            }

            /* Get decompressed length (just for error-checking). */
            cdst_length = sc_global.fdl_return;
            scl_log(SCLT_DLLPG, 5, "[FDL_receive]*** Compressed length = %d bytes", cdst_length);


            /* Update status on host program. */
            STATUS_CALLBACK(CLBK_SEND, address, buffer, pbuffer-buffer, length);

            /* Update pointers and remaining size. */
            paddress += csrc_length;
            pbuffer  += csrc_length;
            plength  -= csrc_length;

            scl_log(SCLT_DLLPG, 4, "[FDL_send] COMP SZ=%d -> %d Bytes, SAT DECOMP=%d Bytes, REMAIN=%d Bytes.", csrc_length, comp_sz, cdst_length, plength);
            if(csrc_length != cdst_length)
            {
                scl_log(SCLT_DLLPG, 0, "[FDL_send]*** ERROR when decompressing data !");
                return SC_ERROR_COMPRESSION;
            }
        } while(plength != 0);



        /* If needed, execute code */
        if(exec_flag)
        {
            scl_log(SCLT_DLLPG, 8, "[FDL_send]+++ Execute: exec_adr=0x%08X, length=%d", address, length);

            /* Send and execute first byte without using compression. */
            err = usbdl_send(address, buffer, /*length*/1, /*exec_flag*/1, 0/*update_status*/);
            if(err != SC_NOERROR)
            {
                STATUS_CALLBACK(CLBK_END, 0, buffer, pbuffer-buffer, length);
                return err;
            }
        }

    }
    else
    { /* Transfer data without compression. */
        /* Send data without using compression. */
        err = usbdl_send(address, buffer, length, exec_flag, 1/*update_status*/);
        if(err != SC_NOERROR)
        {
            STATUS_CALLBACK(CLBK_END, 0, buffer, length, length);
            return err;
        }
    }

    /* Indicates that the process ended */
    STATUS_CALLBACK(CLBK_END, 0, buffer, length, length);

    return SC_NOERROR;
}



/**
 *  Return DLL version informations.
**/
char* SC_usbdl::sc_version(void)
{
    sprintf(_version, (char*)("USB Saturn DataLink interface Ver. 1.1"));
    return _version;
}

