#include "fgpaprogrammer.h"

#include <QTimer>
#include <QTimerEvent>
#include <QThread>
#include <QEventLoop>
#include <QEvent>
#include <QQueue>
#include <QApplication>
#include <QElapsedTimer>
#include <QMessageBox>
#include <QTimer>

#include "mydebug.h"

#include <QAbstractEventDispatcher>

#include <stdint.h>

#include <exception>

#include "fpgaflashengine.h"
#include "fpgarfifflashengine.h"

//#include "FpgaBridgeTftp.h"
#include "BupTFTP.h"

#include "fileutils.h"

#include "fpgabeammeupstring.h"

#include "historydb.h"

static unsigned char dummy_buffer[1024];

class my_exception: public std::exception
{
public:
    int code;
    my_exception(int c): code(c) {}
};

enum UserEventId
{
    evn_start=QEvent::User+100,

    evn_timeout,
    evn_write_completed,
    evn_read_completed,

    evn_break,
    evn_abort,
    evn_error,

    evn_erase,
    evn_reset,
    evn_readFlashID,
    evn_readStatusSPI,
    evn_terminate,
    evn_program_finish
};

struct event_data_t
{
    int type;
    unsigned int len;
    char data[DIM_DATA_BUFFER];

    event_data_t(QEvent::Type type_=QEvent::User, const void* data_=0, unsigned int len_=0):
        type(type_), len(len_)
    {
        (void)data_;
    }
};



class MyEvent: public QEvent
{
public:
    MyEvent(int type): QEvent(QEvent::Type(type)), data(QEvent::Type(type))
    {
    }
    event_data_t data;
};



class FgpaProgrammer::FpgaProgrammerImplementation: public QThread,public FpgaFlashInterface
{
public:
    //int tId;
    QTimer* pTimer;

    FgpaProgrammer* tFpgaProg;

    BupTFTPID rId;

    //FpgaBridgeTftp* tftp;
    BupTFTP* tftp;

    bool simulate;
    bool SRIOMemory;        //0 for address BASE_ADDRESS_IF
                            //=!0 for address BASE_ADDRESS_MEM

    FpgaFlashEngine engine;

    FpgaFlashEngineRFIF engineRFIF;

    dbHistory *history;

    FpgaFlashProfile profile;

    QElapsedTimer elapsedTimer;
    //FpgaFlashInterface
    virtual unsigned long timeNowMs() override
    {
       return elapsedTimer.elapsed();
    }


    //***********************************************************************
    // rtgWrite
    // Input parameters:    address = address to write
    //                      data = buffer to write
    //                      len = buffer's length
    //                      mess = message to write into debug log
    // Returns:             true, no error, operation completed
    //                      false, generic error into write function
    //***********************************************************************
    virtual bool rtgWriteDB(uint32_t address, const void* data, unsigned int len=DIM_DATA_BUFFER, unsigned int doorbell=0) override
    {
        if (tftp && !simulate)
        {
            QString remoteFile = "";
            if (SRIOMemory)
            {
                remoteFile =QString("$SRIO:%1!0x%2/0x%3+%4").arg(profile.slotAddress).arg(doorbell, 0, 16).arg(BASE_ADDRESS_MEM+address, 0, 16);
            }
            else
            {
                remoteFile =QString("$SRIO:%1!0x%2/0x%3+%4").arg(profile.slotAddress).arg(doorbell, 0, 16).arg(address, 0, 16).arg(len);
            }

            MyDebug.logMsg( type_log_info,
                                  origin_msg_srio ,
                                  QString(STR_COMMAND_WRITE).arg(remoteFile));

            Q_EMIT tFpgaProg->writeRequest(remoteFile, data, len);
            bool ok=waitTxEvent();
            int countRepeat = NUM_REPEAT_WRITE;

            //repeatwrite until is ok or error
            while ((!ok) && (countRepeat>0))
            {
                MyDebug.logMsg( type_log_error,
                                      origin_msg_tftp ,
                                      STR_TRY_TO_REPEAT_WRITE);

                Q_EMIT tFpgaProg->writeRequest(remoteFile, data, len);
                ok=waitTxEvent();
                if (WAIT_TIME_AFTER_WR>0)
                    waitEventTimeout(WAIT_TIME_AFTER_WR);

                countRepeat--;
            }

            if (countRepeat==0)
            {
                MyDebug.logMsg( type_log_error,
                                      origin_msg_tftp ,
                                      STR_TRY_TOO_WRITE);
                return false;
            }

            if (WAIT_TIME_AFTER_WR>0)
                waitEventTimeout(WAIT_TIME_AFTER_WR);

            return ok;
        }
        if (simulate)
        {
            if (profile.isFpgaRFIF)
                engineRFIF.simulateWrite(address, data, len);
            else
                engine.simulateWrite(address, data, len);
        }
        if (WAIT_TIME_AFTER_WR>0)
            waitEventTimeout(WAIT_TIME_AFTER_WR);
        return true;
    }

    //***********************************************************************
    // rtgWrite
    // Input parameters:    address = address to write
    //                      data = buffer to write
    //                      len = buffer's length
    //                      mess = message to write into debug log
    // Returns:             true, no error, operation completed
    //                      false, generic error into write function
    //***********************************************************************
    virtual bool rtgWrite(uint32_t address, const void* data, unsigned int len=DIM_DATA_BUFFER) override
    {
        if (tftp && !simulate)
        {
            QString remoteFile = "";
            if (SRIOMemory)
            {
                remoteFile =QString("$SRIO:%1/0x%2+%3").arg(profile.slotAddress).arg(BASE_ADDRESS_MEM+address, 0, 16).arg(len);
            }
            else
            {
                remoteFile =QString("$SRIO:%1/0x%2+%3").arg(profile.slotAddress).arg(BASE_ADDRESS_IF+address, 0, 16).arg(len);
            }

            MyDebug.logMsg( type_log_info,
                                  origin_msg_srio ,
                                  QString(STR_COMMAND_WRITE).arg(remoteFile));

            Q_EMIT tFpgaProg->writeRequest(remoteFile, data, len);
            bool ok=waitTxEvent();
            int countRepeat = NUM_REPEAT_WRITE;

            //repeatwrite until is ok or error
            while ((!ok) && (countRepeat>0))
            {
                MyDebug.logMsg( type_log_error,
                                      origin_msg_tftp ,
                                      STR_TRY_TO_REPEAT_WRITE);

                Q_EMIT tFpgaProg->writeRequest(remoteFile, data, len);
                ok=waitTxEvent();
                if (WAIT_TIME_AFTER_WR>0)
                    waitEventTimeout(WAIT_TIME_AFTER_WR);

                countRepeat--;
            }

            if (countRepeat==0)
            {
                MyDebug.logMsg( type_log_error,
                                      origin_msg_tftp ,
                                      STR_TRY_TOO_WRITE);
                return false;
            }

            if (WAIT_TIME_AFTER_WR>0)
                waitEventTimeout(WAIT_TIME_AFTER_WR);

            return ok;
        }
        if (simulate)
        {
            if (profile.isFpgaRFIF)
                engineRFIF.simulateWrite(address, data, len);
            else
                engine.simulateWrite(address, data, len);
        }
        if (WAIT_TIME_AFTER_WR>0)
            waitEventTimeout(WAIT_TIME_AFTER_WR);
        return true;
    }

    //***********************************************************************
    // rtgRead
    // Input parameters:    address = address to read
    //                      data = ouput buffer
    //                      len = buffer's length
    //
    // Returns:             true, no error, operation completed
    //                      false, generic error into read function
    //***********************************************************************
    virtual bool rtgReadDB(uint32_t address, void* data, unsigned int len=DIM_DATA_BUFFER, unsigned int doorbell=0) override
    {
        if (tftp && !simulate)
        {
            QString remoteFile= "";
            if (SRIOMemory)
            {
                remoteFile=QString("$SRIO:%1!0x%2/0x%3+%4").arg(profile.slotAddress).arg(doorbell, 0, 16).arg(BASE_ADDRESS_MEM+address, 0, 16).arg(len);
            }
            else
            {
                remoteFile=QString("$SRIO:%1!0x%2/0x%3+%4").arg(profile.slotAddress).arg(doorbell, 0, 16).arg(address, 0, 16).arg(len);
            }

            MyDebug.logMsg( type_log_info,
                                  origin_msg_srio ,
                                  QString(STR_COMMAND_READ).arg(remoteFile));

            Q_EMIT tFpgaProg->readRequest(remoteFile, data, len);
            bool ok=waitRxEvent();

            int countRepeat = NUM_REPEAT_READ;

            //repeatwrite until is ok or error
            while ((!ok) && (countRepeat>0))
            {
                MyDebug.logMsg( type_log_error,
                                      origin_msg_tftp ,
                                      STR_TRY_TO_REPEAT_READ);

                Q_EMIT tFpgaProg->readRequest(remoteFile, data, len);
                ok=waitTxEvent();
                if (WAIT_TIME_AFTER_RD>0)
                    waitEventTimeout(WAIT_TIME_AFTER_RD);

                countRepeat--;
            }

            if (countRepeat==0)
            {
                MyDebug.logMsg( type_log_error,
                                      origin_msg_tftp ,
                                      STR_TRY_TOO_READ );
                return false;
            }

            if (WAIT_TIME_AFTER_RD>0)
                waitEventTimeout(WAIT_TIME_AFTER_RD);
            return ok;
        }
        if (simulate)
        {
            if (profile.isFpgaRFIF)
                engineRFIF.simulateRead(address, data, len);
            else
                engine.simulateRead(address, data, len);


        }
        if (WAIT_TIME_AFTER_RD>0)
            waitEventTimeout(WAIT_TIME_AFTER_RD);
        return true;
    }

    //***********************************************************************
    // rtgRead
    // Input parameters:    address = address to read
    //                      data = ouput buffer
    //                      len = buffer's length
    //
    // Returns:             true, no error, operation completed
    //                      false, generic error into read function
    //***********************************************************************
    virtual bool rtgRead(uint32_t address, void* data, unsigned int len=DIM_DATA_BUFFER) override
    {
        if (tftp && !simulate)
        {
            QString remoteFile= "";
            if (SRIOMemory)
            {
                remoteFile=QString("$SRIO:%1/0x%2+%3").arg(profile.slotAddress).arg(BASE_ADDRESS_MEM+address, 0, 16).arg(len);
            }
            else
            {
                remoteFile=QString("$SRIO:%1/0x%2+%3").arg(profile.slotAddress).arg(BASE_ADDRESS_IF+address, 0, 16).arg(len);
            }

            MyDebug.logMsg( type_log_info,
                                  origin_msg_srio ,
                                  QString(STR_COMMAND_READ).arg(remoteFile));

            Q_EMIT tFpgaProg->readRequest(remoteFile, data, len);
            bool ok=waitRxEvent();

            int countRepeat = NUM_REPEAT_READ;

            //repeatwrite until is ok or error
            while ((!ok) && (countRepeat>0))
            {
                MyDebug.logMsg( type_log_error,
                                      origin_msg_tftp ,
                                      STR_TRY_TO_REPEAT_READ);

                Q_EMIT tFpgaProg->readRequest(remoteFile, data, len);
                ok=waitTxEvent();
                if (WAIT_TIME_AFTER_RD>0)
                    waitEventTimeout(WAIT_TIME_AFTER_RD);

                countRepeat--;
            }

            if (countRepeat==0)
            {
                MyDebug.logMsg( type_log_error,
                                      origin_msg_tftp ,
                                      STR_TRY_TOO_READ );
                return false;
            }

            if (WAIT_TIME_AFTER_RD>0)
                waitEventTimeout(WAIT_TIME_AFTER_RD);
            return ok;
        }
        if (simulate)
        {
            if (profile.isFpgaRFIF)
                engineRFIF.simulateRead(address, data, len);
            else
                engine.simulateRead(address, data, len);


        }
        if (WAIT_TIME_AFTER_RD>0)
            waitEventTimeout(WAIT_TIME_AFTER_RD);
        return true;
    }

    //***********************************************************************
    // rtgRead Direct
    // Input parameters:    address = address to read
    //                      data = ouput buffer
    //                      len = buffer's length
    //
    // Returns:             true, no error, operation completed
    //                      false, generic error into read function
    //***********************************************************************
    virtual bool rtgReadDirect(uint32_t address, void* data, unsigned int len=DIM_DATA_BUFFER) override
    {
        if (tftp && !simulate)
        {
            QString remoteFile= "";

            remoteFile=QString("$SRIO:%1/0x%2+%3").arg(profile.slotAddress).arg(address, 0, 16).arg(len);

            if (LOG_LEVEL>log_level_min)
            {

                MyDebug.logMsg( type_log_info,
                                  origin_msg_srio ,
                                  QString(STR_COMMAND_READ_DIRECT).arg(remoteFile));
            }

            Q_EMIT tFpgaProg->readRequest(remoteFile, data, len);
            return waitRxEvent();
        }
        waitEventTimeout(100);
        return true;
    }
#if 0
    virtual bool rtgErase(uint32_t address, void* data, unsigned int len=DIM_DATA_BUFFER) override
    {
        engine.simulateErase(address, data, len);
        waitEventTimeout(100);
        return true;
    }
#endif
    virtual bool rtgWaitEvent(unsigned int timeout_ms=5000)
    {
        if (timeout_ms==0)
            return true;
        /*event_data_t tmp=*/waitEvent(timeout_ms);
        return true;
    }

    virtual void notify(step_t sts, int progress, const char* msg, ...) override
    {
        va_list args;
        va_start(args, msg);
        notifyStatus(sts, progress, QString::vasprintf(msg, args));
        MyDebug.logMsg( type_log_info,
                          origin_msg_engine ,
                          QString::vasprintf(msg, args));
        va_end(args);
    }

    virtual void log(type_log_t level, const char* msg, ...) override //level<0: error, ==0: info, >0: success
    {
        va_list args;
        va_start(args, msg);

        MyDebug.logMsg(level,
                         origin_msg_engine,
                         QString::vasprintf(msg, args));

        va_end(args);
    }

    struct state_t
    {
        FpgaProgrammerDriver* driver;
        int timer_pending;

        int state;
    };

    state_t s;
    volatile unsigned sectorSize;
    volatile unsigned int baseAddress;

    FpgaProgrammerImplementation():
        //tId(0),
        tFpgaProg(0),
        tftp(0),
        simulate(false),
        history(0)
    {
        //pTimer.setSingleShot(true);
    }

    //***********************************************************************
    // Set profile of the selected FPGA-flash
    // Input parameters:    _profile = parameters of selected fpga/flash
    //
    // Returns:
    //***********************************************************************
    void setProfile(const FpgaFlashProfile& _profile)
    {
        profile = _profile;
        profile.BaseOffset = profile.user_address_start_area;

        //
        profile.SectorSize_KBytes = FLASH_SECTOR_DIM;
        sectorSize = FLASH_SECTOR_DIM;
    }

    //***********************************************************************
    // Inizialize programmer with default parameters
    // Input parameters:    _profile = parameters of selected fpga/flash
    //
    // Returns:
    //***********************************************************************
    void initialize(unsigned int /*write_size*/, int sector_size, int base_address)
    {
        sectorSize=sector_size;
        baseAddress=base_address;
        profile.reset();
        profile.SectorSize_KBytes = sectorSize;
        profile.BaseAddress = baseAddress;
        profile.BaseOffset = 0;
    }

    void waitTxCompleted()
    {
        waitEventTimeout();
    }

    void waitSpiBusy()
    {
        event_data_t evn=waitEventTimeout();
        if (evn.type!=evn_read_completed)
            throw my_exception(1);
    }

    void waitSpiRdy()
    {
        event_data_t evn=waitEventTimeout();
        if (evn.type!=evn_read_completed)
            throw my_exception(1);
    }

    enum states_t
    {
        s_reset_wait=1,
        s_id_request,
        s_progr_enable,
        s_erase_block,
        s_program_block
    };

    void stateReset()
    {
    }

    void state_RequestId()
    {
    }

    void timerExpired()
    {
        //QCoreApplication::postEvent(this, new MyEvent(evn_timeout));
    }

    QQueue<event_data_t> qevn;

    bool waitTxEvent(unsigned int tm_ms=0)
    {
        event_data_t e=waitEvent(tm_ms);
        return e.type==evn_write_completed;
    }

    bool waitRxEvent(unsigned int tm_ms=0)
    {
        event_data_t e=waitEvent(tm_ms);
        return e.type==evn_read_completed;
    }

    event_data_t waitEventTimeout(unsigned int tm_ms=5000)
    {
        if (tm_ms==0)
        {
            static event_data_t myeven;
            myeven.type=evn_timeout;
            return myeven;
        }
        return waitEvent(tm_ms);
    }

    event_data_t waitEvent(unsigned int tm_ms=0)
    {
        if (pTimer->isActive())
        {
            pTimer->stop();
        }
#if 0
        if (tId)
        {
            killTimer(tId);
            tId=0;
        }
#endif
#if 0
        if (!qevn.isEmpty())
        {
            event_data_t evn=qevn.takeFirst();
            //if (evn.type==evn_abort)
            //    throw(std::system_error(1));

            MyDebug()<<"Event pending:"<<evn.type;
            return evn;
        }
#endif

        if (tm_ms)
        {
           //tId=startTimer(tm_ms); //w->waitStart(tm_ms);
            pTimer->start(tm_ms);
        }
retry_:
        /*MyDebug.logMsg(type_log_error,
                         origin_msg_generic,
                         "qui 1");*/
        QEventLoop::ProcessEventsFlags flags=QEventLoop::WaitForMoreEvents;
        bool processed=this->eventDispatcher()->processEvents(flags);
        /*MyDebug.logMsg(type_log_error,
                         origin_msg_generic,
                         "qui 2");*/
        int count_event = 0;
        if (processed)
        {

            /*MyDebug.logMsg(type_log_error,
                             origin_msg_generic,
                             "qui 3");*/

            if (!qevn.isEmpty())
            {
                count_event = qevn.count();
#if 0
                if (tId)
                {
                    killTimer(tId);
                    tId=0;
                }
#endif
                pTimer->stop();

                event_data_t evn=qevn.takeFirst();
                if (evn.type==evn_break)
                {
                    QString msg = QString("%1 - %2").arg(STR_EXCP_BREAK).arg(MyDebug.getLastErrorStr());
                    MyDebug.logMsg(type_log_error,
                                     origin_msg_generic,
                                     msg);
                    throw my_exception(3);
                }

                switch (evn.type)
                {
                    case evn_timeout:
                        MyDebug.logMsg(type_log_info,
                                         origin_msg_event,
                                         QString(STR_TIMEOUT_QUEUE).arg(count_event));

                    break;
                    case evn_write_completed:
                        /*MyDebug.logMsg(type_log_info,
                                         origin_msg_event,
                                         QString(STR_WRITE_COMPLETED_QUEUE).arg(count_event));*/
                    break;
                    case evn_read_completed:
                        /*MyDebug.logMsg(type_log_info,
                                         origin_msg_event,
                                         QString(STR_READ_COMPLETED_QUEUE).arg(count_event));*/
                    break;
                    default:
                         /*MyDebug.logMsg(type_log_info,
                                          origin_msg_event,
                                          QString(STR_QUEUE_EVENT).arg(evn.type).arg(count_event));*/
                        break;
                }

                return evn;
            }
            else
            {
                MyDebug.logMsg(type_log_info,
                             origin_msg_event,
                             STR_QUEUE_EVENT_EMPTY);
            }
        }
        goto retry_;
        return event_data_t(QEvent::User, 0, 0);
    }

    void customEvent(QEvent* evn) override
    {
        if (evn->type()>=QEvent::User)
        {
            MyEvent* me=reinterpret_cast<MyEvent*>(evn);
            if (me)
                qevn.append(me->data);
        }
    }
    void timerEvent(QTimerEvent* evn)
    {
        if (evn->timerId()==pTimer->timerId()) //tId)
        {
            //tId=0;
            //killTimer(tId);
            pTimer->stop();
            static event_data_t data;
            data.type=evn_timeout;
            qevn.append(data);
        }
        else
            QThread::timerEvent(evn);
    }

    void run()
    {
        pTimer=new QTimer(this);
        pTimer->setSingleShot(true);
        pTimer->moveToThread(this);
        connect(pTimer, &QTimer::timeout, this, [this]()
        {
            QCoreApplication::postEvent(this, new MyEvent(evn_timeout), Qt::HighEventPriority);
        });
        execute();
    }

    void notifyStatus(int sts, int progress, const QString& msg)
    {

        Q_EMIT tFpgaProg->statusChanged(sts, progress, msg);

        //in case of error, update history work
        /*if (sts==s_error)
        {
            history->changeErrorLast();
            history->changeErrorLast();
        }*/
    }

    //***********************************************************************
    // Start program flash with erase first
    // Input parameters:
    //
    // Returns:
    //***********************************************************************
    void startProgram()
    {
        //setto il profilo di fpga da processare
        if (profile.isFpgaRFIF)
            engineRFIF.setProfile(profile);
        else
            engine.setProfile(profile);

        event_data_t evn;

        bool ok_fpgaProgram = false;

        //reset dummy_buffer
        for(unsigned int i=0; i<sizeof dummy_buffer; ++i)
            dummy_buffer[i]=0;

        if (profile.writeRamp)
		{
            //notifico lo stato di start
            notifyStatus(0, -1, STR_WRITE_RAMP);
            elapsedTimer.start();

            for(unsigned int i=0; i<sizeof dummy_buffer; ++i)
                dummy_buffer[i]=i;

            //inizio la programmazione
            if (profile.isFpgaRFIF)
                ok_fpgaProgram = engineRFIF.fpgaProgram(dummy_buffer, sizeof dummy_buffer);
            else
                ok_fpgaProgram = engine.fpgaProgram(dummy_buffer, sizeof dummy_buffer);
        }
        else
        {
            if (profile.FileToSend=="")
            {
                QCoreApplication::postEvent(this, new MyEvent(evn_error), Qt::HighEventPriority);
                return;
            }

            //read binary file to write into flash
            //notifico lo stato di start
            QString msg = QString(STR_WRITE_FILE).arg(profile.FileToSend);
            notifyStatus(0, -1, msg);

            elapsedTimer.start();

            binary_data_t *binary_data = read_file(profile.FileToSend.toStdString().c_str());

            if (binary_data == NULL )
            {
                msg = QString(STR_ERROR_LOAD_FILE).arg(profile.FileToSend);
                notifyStatus(0, -1, msg);
                QCoreApplication::postEvent(this, new MyEvent(evn_error), Qt::HighEventPriority);
                return;
            }

            QString address = QString("0x%1-0x%2").arg(profile.user_address_start_area, 0, 16).arg(profile.user_address_stop_area, 0, 16);
            QString srio = QString("%1@%2:%3").arg(profile.slotAddress).arg(profile.ip).arg(profile.port);
            int spi = int(profile.spiport);

            history->addNewFile(profile.FileToSend,
                                       db_error_no_error,
                                       profile.fName,
                                       db_status_started,
                                       address,
                                       srio,
                                       spi);


#ifdef DIM_FILE_WRITE
            ok_fpgaProgram = engine.fpgaProgram(binary_data->data, DIM_FILE_WRITE);
#else
            if (profile.isFpgaRFIF)
            ok_fpgaProgram = engineRFIF.fpgaProgram(binary_data->data, binary_data->size);
                else
            ok_fpgaProgram = engine.fpgaProgram(binary_data->data, binary_data->size);

#endif
            if (profile.write2Flash==1)
            {
                if (profile.FileToSend2=="")
                {
                    QCoreApplication::postEvent(this, new MyEvent(evn_error), Qt::HighEventPriority);
                    return;
                }

                //set secondary spi
                profile.spiport = secondarySpiPort;

                //read binary file to write into flash
                //notifico lo stato di start
                QString msg = QString(STR_WRITE_FILE).arg(profile.FileToSend2);
                notifyStatus(0, -1, msg);

                elapsedTimer.start();

                binary_data_t *binary_data = read_file(profile.FileToSend2.toStdString().c_str());

                if (binary_data == NULL )
                {
                    msg = QString(STR_ERROR_LOAD_FILE).arg(profile.FileToSend2);
                    notifyStatus(0, -1, msg);
                    QCoreApplication::postEvent(this, new MyEvent(evn_error), Qt::HighEventPriority);
                    return;
                }

                QString address = QString("0x%1-0x%2").arg(profile.user_address_start_area, 0, 16).arg(profile.user_address_stop_area, 0, 16);
                QString srio = QString("%1@%2:%3").arg(profile.slotAddress).arg(profile.ip).arg(profile.port);
                int spi = int(profile.spiport);

                history->addNewFile(profile.FileToSend,
                                           db_error_no_error,
                                           profile.fName,
                                           db_status_started,
                                           address,
                                           srio,
                                           spi);
                if (profile.isFpgaRFIF)
                    ok_fpgaProgram = engineRFIF.fpgaProgram(binary_data->data, binary_data->size);
                else
                    ok_fpgaProgram = engine.fpgaProgram(binary_data->data, binary_data->size);
            }
        }
        
        for(;;)
        {

            if (ok_fpgaProgram)
            {
                QString msg = QString(STR_OK_PROGRAM).arg(profile.fName);
                notifyStatus(s_end, -1, msg);
                history->changeErrorLast(db_error_no_error, "");
                history->changeStatusLast(db_status_programmed);

                /*QEvent evn = ;
                pushEvent();*/

                return;
            }


            evn=waitEvent(5000);
            if (evn.type==evn_break)
            {
                //se l'evento è break, vuol dire che ha finito di processare
                notifyStatus(0, -1, STR_STOP_PROGRAM);
                break;
            }

            if (evn.type==evn_timeout)
            {
                QString msg = QString("%1").arg(MyDebug.getLastErrorStr());
                notifyStatus(s_error, -1, msg);
                break;
            }
            //verifico se è stato richiesto un interrupt
            if (isInterruptionRequested())
            {
                return;
            }
        }
    }

    //***********************************************************************
    // Start esare flash
    // Input parameters:
    //
    // Returns:
    //***********************************************************************
    void startErase()
    {
        //setto il profilo di fpga da processare
        //FpgaFlashProfile profile={sectorSize, 0, baseAddress, 1};
        if (profile.isFpgaRFIF)
            engineRFIF.setProfile(profile);
        else
            engine.setProfile(profile);
        //notifico lo stato di start
        notifyStatus(s_erase, -1, STR_START_ERASE);
        elapsedTimer.start();

        //inizio la cancellazione
        if (profile.isFpgaRFIF)
            engineRFIF.fpgaErase(sizeof dummy_buffer);
        else
            engine.fpgaErase(sizeof dummy_buffer);

        event_data_t evn;

        for(;;)
        {
            evn=waitEvent(5000);
            if (evn.type==evn_break)
            {
                //se l'evento è break, vuol dire che ha finito di processare
                notifyStatus(s_erase, -1, STR_STOP_ERASE);
                break;
            }
            //verifico se è stato richiesto un interrupt
            if (isInterruptionRequested())
            {
                return;
            }
        }
    }

    //***********************************************************************
    // Start reset flash communication with erase first
    // Input parameters:
    //
    // Returns:
    //***********************************************************************
    void startReset()
    {
        //setto il profilo di fpga da processare
        //FpgaFlashProfile profile={sectorSize, 0, baseAddress, 1};
        if (profile.isFpgaRFIF)
            engineRFIF.setProfile(profile);
        else
            engine.setProfile(profile);
        //notifico lo stato di start
        notifyStatus(s_reset, -1, STR_START_RESET);
        elapsedTimer.start();

        //inizio la cancellazione
        if (profile.isFpgaRFIF)
            engineRFIF.fpgaReset();
        else
            engine.fpgaReset();

        event_data_t evn;

        for(;;)
        {
            evn=waitEvent(5000);
            if (evn.type==evn_break)
            {
                //se l'evento è break, vuol dire che ha finito di processare
                notifyStatus(s_reset, -1, STR_STOP_RESET);
                break;
            }
            //verifico se è stato richiesto un interrupt
            if (isInterruptionRequested())
            {
                return;
            }
        }
    }

    //***********************************************************************
    // Start read flash ID
    // Input parameters:
    //
    // Returns:
    //***********************************************************************
    void startReadFlashID()
    {
        //setto il profilo di fpga da processare
        //FpgaFlashProfile profile={sectorSize, 0, baseAddress, 1};
        if (profile.isFpgaRFIF)
            engineRFIF.setProfile(profile);
        else
            engine.setProfile(profile);
        //notifico lo stato di start
        notifyStatus(s_read_id, -1, STR_START_READ_FLASH_ID);
        elapsedTimer.start();

        //inizio la cancellazione
        if (profile.isFpgaRFIF)
            engineRFIF.readFlashID();
        else
            engine.readFlashID();

        event_data_t evn;

        for(;;)
        {
            evn=waitEvent(5000);
            if (evn.type==evn_break)
            {
                //se l'evento è break, vuol dire che ha finito di processare
                notifyStatus(s_read_id, -1, STR_STOP_READ_FLASH_ID);
                break;
            }
            //verifico se è stato richiesto un interrupt
            if (isInterruptionRequested())
            {
                return;
            }
        }
    }

    //***********************************************************************
    // Return spi status
    // Input parameters:
    //
    // Returns:
    //***********************************************************************
    void startStatusSPI()
    {
        //setto il profilo di fpga da processare
        //FpgaFlashProfile profile={sectorSize, 0, baseAddress, 1};
        if (profile.isFpgaRFIF)
            engineRFIF.setProfile(profile);
        else
            engine.setProfile(profile);
        //notifico lo stato di start
        notifyStatus(s_status, -1, STR_START_READ_SPI_STATUS);
        elapsedTimer.start();

        //inizio la cancellazione
        if (profile.isFpgaRFIF)
            engineRFIF.readStatusSPI();
        else
            engine.readStatusSPI();

        event_data_t evn;

        for(;;)
        {
            evn=waitEvent(5000);
            if (evn.type==evn_break)
            {
                //se l'evento è break, vuol dire che ha finito di processare
                notifyStatus(s_status, -1, STR_STOP_READ_SPI_STATUS);
                break;
            }
            //verifico se è stato richiesto un interrupt
            if (isInterruptionRequested())
            {
                return;
            }
        }
    }

    //***********************************************************************
    // terminate connection
    // Input parameters:
    //
    // Returns:
    //***********************************************************************
    /*void startTerminate()
    {
        //setto il profilo di fpga da processare
        //FpgaFlashProfile profile={sectorSize, 0, baseAddress, 1};
        engine.setProfile(profile);
        //notifico lo stato di start
        notifyStatus(s_end, -1, STR_START_TERMINATE);
        elapsedTimer.start();

        //inizio la cancellazione
        engine.fpgaTerminate();

        event_data_t evn;

        for(;;)
        {
            evn=waitEvent(5000);
            if (evn.type==evn_break)
            {
                //se l'evento è break, vuol dire che ha finito di processare
                notifyStatus(s_end, -1, STR_STOP_TERMINATE);
                break;
            }
            //verifico se è stato richiesto un interrupt
            if (isInterruptionRequested())
            {
                return;
            }
        }
    }*/

    //***********************************************************************
    // main control for all operations
    // Input parameters:
    //
    // Returns:
    //***********************************************************************
    void execute()
    {
        //if (profile.isFpgaRFIF)
            engineRFIF.setInterface(this);
        //else
            engine.setInterface(this);

        for(;;)
        {
            try
            {
                //attendo il prossimo evento, verificare il funzionamento
                event_data_t evn=waitEvent(); //5000);

                //se l'evento da processare non è l'evento di start, allora rimango in stato di idle.
                if (evn.type!=evn_start &&
                    evn.type!=evn_erase &&
                    evn.type!=evn_reset &&
                    evn.type!=evn_readFlashID &&
                    evn.type!=evn_readStatusSPI &&
                    evn.type!=evn_terminate    )
                {
                    notifyStatus(0, -1, "idle");

                    if (evn.type==evn_error)
                    {
                        notifyStatus(0, -1, QString(STR_ENGINE_ERROR).arg(MyDebug.getLastErrorStr()));
                        history->changeErrorLast(db_error_generic);
                        history->changeStatusLast(db_status_error);
                        break;
                    }
                    continue;
                }
                try
                {
                    if (evn.type==evn_start)
                    {
                        startProgram();
                    }
                    else
                    if (evn.type==evn_erase)
                    {
                        startErase();
                    }
                    else
                    if (evn.type==evn_reset)
                    {
                        startReset();
                    }
                    else
                    if (evn.type==evn_readFlashID)
                    {
                        startReadFlashID();
                    }
                    else
                    if (evn.type==evn_readStatusSPI)
                    {
                        startStatusSPI();
                    }
                }
                catch(my_exception& e)
                {
                    QString msg1 = QString(STR_EXCEPTION).arg(e.code);
                    QString msg = QString("%1 - %2").arg(msg1).arg(MyDebug.getLastErrorStr());
                    //notifico eccezione durante la programmazione
                    notifyStatus(0, -1, msg);
                    history->changeErrorLast(db_error_generic, msg);
                    history->changeStatusLast(db_status_error);
                }
            }
            catch(my_exception& e)
            {
                QString msg1 = QString(STR_EXCEPTION).arg(e.code);
                QString msg = QString("%1 - %2").arg(msg1).arg(MyDebug.getLastErrorStr());
                notifyStatus(0, -1, msg);
                history->changeErrorLast(db_error_generic, msg);
                history->changeStatusLast(db_status_error);
            }
        }
    }

    void pushEvent(QEvent* evn)
    {
        QApplication::sendEvent(this, evn);
    }
};

FgpaProgrammer::FgpaProgrammer(QObject *parent) : QObject(parent), p_(*new FpgaProgrammerImplementation)
{
    p_.tFpgaProg=this;
    p_.initialize(0, 1, 0);

    p_.moveToThread(&p_);
    p_.start();

    connect(this, &FgpaProgrammer::writeRequest, this, &FgpaProgrammer::onWriteRequest, Qt::QueuedConnection);
    connect(this, &FgpaProgrammer::readRequest, this, &FgpaProgrammer::onReadRequest, Qt::QueuedConnection);
}

//***********************************************************************
// Set simulate flag for simulate write/read info
// Input parameters:
//
// Returns:
//***********************************************************************
void FgpaProgrammer::setSimulate(bool enable)
{
    p_.simulate=enable;
}

//***********************************************************************
// Set SRIOMemory flag for select BASE_ADDRESS_IF(0x47000000) or BASE_ADDRESS_MEM(0xA0000000)
// Input parameters:
//
// Returns:
//***********************************************************************
void FgpaProgrammer::setSRIOMemory(bool enable)
{
    p_.SRIOMemory=enable;
}

//***********************************************************************
// Connect programmer with tftp instance
// Input parameters:
//
// Returns:
//***********************************************************************
//void FgpaProgrammer::setTftp(FpgaBridgeTftp *tftp)
void FgpaProgrammer::setTftp(BupTFTP *tftp)
{
    if (tftp!=p_.tftp)
    {
        p_.tftp=tftp;
        //connect(p_.tftp, &FpgaBridgeTftp::sendTerminated, this, [this]()
        connect(p_.tftp, &BupTFTP::sendTerminated, this, [this]()
        {
            if (p_.profile.isFpgaRFIF)
            {
                if (p_.engineRFIF.getEnableMsgBlk())
                    QMessageBox::information(0, STR_TFTP_WRITE_COMPLETED, STR_TFTP_WRITE_COMPLETED);
            }
            else
            {
                if (p_.engine.getEnableMsgBlk())
                        QMessageBox::information(0, STR_TFTP_WRITE_COMPLETED, STR_TFTP_WRITE_COMPLETED);
            }
            this->writeCompleted(true);
        });

        //connect(p_.tftp, &FpgaBridgeTftp::sendError, this, [this]()
        connect(p_.tftp, &BupTFTP::sendError, this, [this]()
        {
            MyDebug.logMsg(type_log_error,
                             origin_msg_tftp,
                             STR_TFTP_SEND_ERROR);
            QCoreApplication::postEvent(&p_, new MyEvent(evn_break), Qt::HighEventPriority);
        });

        //connect(p_.tftp, &FpgaBridgeTftp::transferError, this, [this](int code, const QString& codeError)
        connect(p_.tftp, &BupTFTP::transferError, this, [this](BupTFTP*, BupTFTPID*, int code, const QString& msg)
        {
            QString msg1 = QString("Transfer Error, code %1 %2").arg(code).arg(msg);
            MyDebug.logMsg(type_log_error,
                             origin_msg_tftp,
                             msg1.toStdString().c_str());
            QCoreApplication::postEvent(&p_, new MyEvent(evn_break), Qt::HighEventPriority);
        });

        //connect(p_.tftp, &FpgaBridgeTftp::receiveTerminated, this, [this](int, const QHostAddress)
        connect(p_.tftp, &BupTFTP::receiveTerminated, this, [this](BupTFTP*, BupTFTPID*, int, QHostAddress)
        {
            /*MyDebug.logMsg(type_log_info,
                             origin_msg_tftp,
                             STR_TFTP_RECEIVE_COMPLETED);*/
            QCoreApplication::postEvent(&p_, new MyEvent(evn_read_completed), Qt::HighEventPriority);
        });

    }
}

void FgpaProgrammer::setHistory(dbHistory *db)
{
    if (db!=p_.history)
    {
        p_.history=db;
    }
}

void FgpaProgrammer::setDriver(FpgaProgrammerDriver* driver)
{
    p_.s.driver=driver;
}

void FgpaProgrammer::start(unsigned long write_size, unsigned int sectorSize, unsigned int baseOffset)
{
    MyDebug.logMsg(type_log_info,
                     origin_msg_generic,
                     QString(STR_START_REQUEST_SIZE).arg(write_size));

    p_.initialize(write_size, sectorSize, baseOffset);
    if (write_size==0)
        QCoreApplication::postEvent(&p_, new MyEvent(evn_break), Qt::HighEventPriority);
    else
        QCoreApplication::postEvent(&p_, new MyEvent(evn_start), Qt::HighEventPriority);
}

void FgpaProgrammer::start(const FpgaFlashProfile& _profile)
{
    MyDebug.logMsg(type_log_info,
                     origin_msg_generic,
                     QString(STR_START_REQUEST_FROM).arg(_profile.user_address_start_area,0, 16));

    p_.setProfile(_profile);
    QCoreApplication::postEvent(&p_, new MyEvent(evn_start), Qt::HighEventPriority);
}

void FgpaProgrammer::erase(unsigned long erase_size, unsigned int sectorSize, unsigned int baseOffset)
{
    MyDebug.logMsg(type_log_info,
                     origin_msg_generic,
                     QString(STR_START_ERASE_SIZE).arg(erase_size));

    p_.initialize(erase_size, sectorSize, baseOffset);

    //TODO: agigungere la parte di codice per chiamare la funzione erase
    QCoreApplication::postEvent(&p_, new MyEvent(evn_erase), Qt::HighEventPriority);

}

void FgpaProgrammer::reset(const FpgaFlashProfile& _profile)
{
    MyDebug.logMsg(type_log_info,
                     origin_msg_generic,
                     STR_START_RESET);
    p_.setProfile(_profile);
    QCoreApplication::postEvent(&p_, new MyEvent(evn_reset), Qt::HighEventPriority);
}

void FgpaProgrammer::readFlashID(const FpgaFlashProfile& _profile)
{
    MyDebug.logMsg(type_log_info,
                     origin_msg_generic,
                     STR_START_READ_FLASH_ID);
    p_.setProfile(_profile);
    QCoreApplication::postEvent(&p_, new MyEvent(evn_readFlashID), Qt::HighEventPriority);
}

void FgpaProgrammer::readStatusSPI(const FpgaFlashProfile& _profile)
{
    MyDebug.logMsg(type_log_info,
                     origin_msg_generic,
                     STR_START_READ_SPI_STATUS);
    p_.setProfile(_profile);
    QCoreApplication::postEvent(&p_, new MyEvent(evn_readStatusSPI), Qt::HighEventPriority);
}


void FgpaProgrammer::terminate(const FpgaFlashProfile& _profile)
{
    MyDebug.logMsg(type_log_info,
                     origin_msg_generic,
                     STR_START_TERMINATE);
    p_.setProfile(_profile);
    QCoreApplication::postEvent(&p_, new MyEvent(evn_terminate), Qt::HighEventPriority);
}

void FgpaProgrammer::abort(const FpgaFlashProfile& _profile)
{
    p_.setProfile(_profile);
}

void FgpaProgrammer::writeCompleted(bool ok)
{
    if (!ok)
        QCoreApplication::postEvent(&p_, new MyEvent(evn_break), Qt::HighEventPriority);
    else
        QCoreApplication::postEvent(&p_, new MyEvent(evn_write_completed), Qt::HighEventPriority);
}

void FgpaProgrammer::readCompleted(bool ok)
{
    if (!ok)
        QCoreApplication::postEvent(&p_, new MyEvent(evn_break), Qt::HighEventPriority);
    else
        QCoreApplication::postEvent(&p_, new MyEvent(evn_read_completed), Qt::HighEventPriority);
}

#if 0
void FgpaProgrammer::eraseCompleted(bool ok, const void* data, unsigned int len)
{
    QCoreApplication::postEvent(&p_, new MyEvent(evn_break), Qt::HighEventPriority);
}
#endif

void FgpaProgrammer::onWriteRequest(const QString& a, const void* src, unsigned int len)
{

    p_.tftp->sendMemory(&p_.rId, (void*)src, len, a.toLatin1().constData());
}

void FgpaProgrammer::onReadRequest(const QString& a, void* dst, unsigned int len)
{
    p_.tftp->receiveMemory(&p_.rId, dst, len, a.toLatin1().constData());
}

