#include "grifotimerdockwidget.h"
#include "ui_grifotimerdockwidget.h"

#include "qgpropertywidget.h"

#include "qgscopechrono.h"
#include "qgrexsocket.h"
#include "qgrexnetwork.h"
#include "qglogger.h"

#include <QCheckBox>

#include <QUdpSocket>
#include <QHostAddress>
#include <QNetworkDatagram>

#include <QMessageBox>

#include <QFile>
#include <QFileInfo>

#include <QtEndian>

#include "testdatagenerator.h"

#include "scsimulator.h"

//#include "rexrtgsocket.h"

#include <string.h>

#include "rep_fpga_interface.h"

#define TV_HEADER_SPARE_DATA   5

#define DSP_BUFF_SIZE   0x1001000 //(524288*2)

struct DspOpaqueData
{
    RexMessageHeader hdr;
    uint8_t data[(4096*64*4)*4+1024*8];//4096rbin*64pri*4ch
};

static DspOpaqueData dspData;

class GrifoTimerDevice::Implementation
{
public:
    GrifoTimerDevice* td;

    QgScopeChronoTimer ttsynchTimer;

    QUdpSocket s;
    QHostAddress tgtHost;

    GrifoTimerDevice::BatchMode batchMode;
    iemul_info_t iemul_info;

    unsigned bc_counter;
    unsigned int m_batchcounter;
    int scPattern;

    ScSimulator scSim;

    QgRexSocket* rexS;
    QHostAddress fpgaHost;

    typedef RexUdpMsgHeaderT2<FakeFpgaMessage> sw_t;
    sw_t sw;

    typedef RexUdpMsgHeaderT2<FakeFpgaTargetsMessage> tgt_t;
    tgt_t tgt;

    QByteArray sarVector;
    const uint8_t* sar_tv_data;
    unsigned int sar_tv_batches;
    unsigned int sar_tv_size;
    int sar_tv_total_size;

    int fragsPerBatch;
    int sendFastMode;

    //TvFile dbstv;
    DbsTestDataGenerator dbstv; //QFile dbstv;
    SarTestDataGenerator sartv;
    FakeRbmTestDataGenerator rbmtv;
    TgtTestDataGenerator tgttv;
    AdTestDataGenerator adtv;

    PlaybackDataGenerator playback;

    TestDataGenerator* tvgen;

    bool message_completed;

    struct FakeSignalMessage
    {
        FakeMessageHeader msgHeader;
        RepFpgaSignalHeaderData fpgaHeader;

        //FpgaSweep sweep;
        uint8_t data[2048];
    };

    Implementation(GrifoTimerDevice* d):
        td(d),
        rbmtv(&dbstv)
    {
        m_batchcounter=0;
    }

    void initialize()
    {
        scPattern=0;
        bc_counter=0;

        fpgaHost=QHostAddress::Any;

        fragsPerBatch=4;
        sendFastMode=1;
        batchMode=GrifoTimerDevice::BatchSlave;

        rexS=new QgRexSocket;//("XX");
        rexS->setId(IidNodeId::TIMER+1, "FPGA");
        rexS->localBind(grifo_inet::rex_ext_tgt_sim);
        {
            dbstv.initialize();
            sartv.initialize();
            rbmtv.initialize();
            tgttv.initialize();
            playback.initialize();
            tvgen=0;
        }

        message_completed=true;
    }

    void batchReset()
    {
        if (tvgen)
        {
            tvgen->reinit();
        }
        message_completed=true;
    }


    bool messageReceived(const IRexNetListener::msg_descriptor_t& d)
    {
       if (batchMode==GrifoTimerDevice::BatchAuto)
       {
          ++m_batchcounter;
          bool ok=batchExec(d.ba);
          return ok;
       }
       return false;
    }

    bool batchExec(QByteArray d) //const QNetworkDatagram& d)
    {
        static unsigned int evn_counter;

        static bool p_stby;
        static unsigned int p_mode;

        bool stby=false;
        unsigned int mode=0;

        TestDataGenerator* ng=tvgen;

        const RepFpgaProgramData* cfg=0;

        if (d.size())
        {
            const FakeRepPgmMessage& msg=*reinterpret_cast<const FakeRepPgmMessage*>(d.constData());

            //qDebug()<<"FPGA:"<<msg.header.marker<<msg.payload.batch_mode;

            stby=msg.payload.dspHeader.basicMode.stby;
            mode=msg.payload.dspHeader.basicMode.mode;


            if (mode!=p_mode)
            {
                switch(mode)
                {
                default:
                    ng=&tgttv;
                    break;
                case 2:
                    ng=&rbmtv;
                    break;
                case 24:
                    ng=&adtv;
                    break;

                case 21:
                case 100:
                    ng=&sartv;
                    break;
                case 3:
                case 200:
                    ng=&dbstv;
                    break;
                }
            }

            cfg=&msg.payload;
        }
        else
            ng=&playback;

        if (ng!=tvgen)
        {
            if (tvgen)
                tvgen->stop();
            ng->reinit();
            tvgen=ng;
            message_completed=true;
        }
        else if (stby!=p_stby)
        {
            if (tvgen)
                tvgen->reinit();
        }

        if (td->m_batchBreak)
        {
            if (td->m_batchBreak==tvgen->batchCounter())
                return false;
        }

        p_stby=stby;
        p_mode=mode;

        if (stby)
        {
            message_completed=true;
            return false;
        }

        if (!tvgen)
            return false;

        if (!message_completed)
            return false;

        unsigned int out_size=tvgen->generateNextBatch(dspData.data, sizeof dspData.data, cfg);
        if (out_size==0)
        {
            //tv_completed=true;
            //sat.reinit();
            return false;
        }
        unsigned int fmt_marker=tvgen->formatMarker();
        //if (fmt_marker==0)
        //    dspData.hdr.marker=0xA5A5A5A5UL;
        dspData.hdr.marker=fmt_marker;

        dspData.hdr.len=out_size;

        message_completed=false;

        //unsigned int evn_code=(evn_counter & 1)+1;
        //unsigned int mailbox=DSP_PUBLIC_MEMORY;
        //mailbox+=(evn_counter & 1)*DSP_BUFF_SIZE; //(4*1024*4);
        //unsigned int evn_code=msg.payload.dstAddresses.doorbell_code;
        //unsigned int mailbox=msg.payload.dstAddresses.header_address;

        unsigned int evn_code=1;
        unsigned int mailbox=0x80000000U;

        if ((evn_counter & 1))
        {
            evn_code=32+2;
            mailbox+=0x1000000;
        }
        else
            evn_code=32+1;

        //rexS->memoryWrite(IidNodeId::DSP0, mailbox, &dspData, out_size+sizeof(dspData.hdr), evn_code);
        rexS->memoryWrite(IidNodeId::DSP0, mailbox, &dspData.data, out_size/*+sizeof(dspData.hdr)*/, evn_code);

        //rexS->memoryWrite(IidNodeId::DSP0, mailbox, &dspData.data[0], 4*1024, 0);
        //rexS->memoryWrite(IidNodeId::DSP0, mailbox+0x00100000, &dspData.data[4*1024], out_size-4*1024, evn_code);


        ++evn_counter;

        return true;
    }
};

GrifoTimerDevice::GrifoTimerDevice():
    QgScopeDevice("timer.grifo", "batchtimer"),
    m_connected(false),
    //m_batchCounter(0),
    m_batchLen(10000),
    p_(*new Implementation(this))
{
    m_batchBreak=0;

    p_.s.bind(QgRexNetwork().networkInterface(), 0);
    p_.tgtHost=QgRexNetwork().networkBroadcast();
    memset(&p_.iemul_info, 0, sizeof p_.iemul_info);

    //p_.ttsynchTimer.start(640000);
    this->chronoTimer().start(m_batchLen);

    p_.initialize();

    connect(&p_.ttsynchTimer, &QgScopeChronoTimer::periodExpired, this,
            [this]()
            {
                QHostAddress ha=m_connected ? p_.tgtHost : QgRexNetwork().networkBroadcast();
                p_.iemul_info.intr_mask=1<<1;
                p_.s.writeDatagram((char*)&p_.iemul_info, sizeof p_.iemul_info, ha, grifo_inet::rex_interrupts_emulation);
            }
       );
    connect(&this->chronoTimer(), QgScopeChronoTimer::periodExpired,
            [this]()
            {
#if 1
                if (p_.batchMode==GrifoTimerDevice::BatchAuto)
                {                    
#if 0
                    ++m_batchCounter;
                    QHostAddress ha=m_connected ? p_.tgtHost : QgRexNetwork().networkBroadcast();
                    p_.iemul_info.intr_mask=1<<2;
                    p_.s.writeDatagram((char*)&p_.iemul_info, sizeof p_.iemul_info, ha, grifo_inet::rex_interrupts_emulation);
#endif
                    //++m_batchCounter;
                    ++p_.m_batchcounter;

                    bool ok=p_.batchExec(QByteArray()); //d.ba);
                    if (ok)
                        Q_EMIT batchGenerated(p_.tvgen->batchCounter());

                    if (m_batchLen!=this->chronoTimer().period())
                    {
                        this->chronoTimer().start(m_batchLen);
                    }

                }
#endif
            }
            );

    connect(&p_.s, QUdpSocket::readyRead,
            [this]()
            {
                while (p_.s.hasPendingDatagrams())
                {
                    QNetworkDatagram d=p_.s.receiveDatagram();
                    if (p_.tgtHost!=d.senderAddress())
                    {
                        p_.tgtHost=d.senderAddress();
                        this->logger()->info(QString("Attaching to remote: %1:%2").arg(p_.tgtHost.toString()).arg(d.senderPort()));
                        m_connected=true;
                    }
                }
            }
            );

    connect(p_.rexS, &QgRexSocket::messageReceived,
            [this](const IRexNetListener::msg_descriptor_t d)
            {
                if (p_.messageReceived(d))
                    Q_EMIT batchGenerated(p_.tvgen->batchCounter());

#if 0
                if (p_.batchMode==GrifoTimerDevice::BatchAuto)
                {
                    ++m_batchCounter;
                    bool ok=p_.batchExec(d.ba);
                    if (ok)
                        Q_EMIT batchGenerated(p_.tvgen->batchCounter());
                }
#endif
            });

    connect(p_.rexS, &QgRexSocket::messageCompleted,
            [this]()
            {
                p_.message_completed=true;
            });

}


void GrifoTimerDevice::simulationReset()
{
    p_.scSim.reset();
}

void GrifoTimerDevice::scSetTestImageCenter(int x, int y)
{
    p_.scSim.setCenter(x, y);
}

GrifoTimerDevice::BatchMode GrifoTimerDevice::batchMode() const
{
    return p_.batchMode;
}

void GrifoTimerDevice::setBatchMode(BatchMode m)
{
    p_.batchMode=m;
}

void GrifoTimerDevice::setFragsPerBatch(int n)
{
    p_.fragsPerBatch=n;
    p_.rexS->setDefaultWindow(n);
}

int GrifoTimerDevice::fragsPerBatch() const
{
    return p_.fragsPerBatch;
}
void GrifoTimerDevice::setSendFastMode(int n)
{
    p_.sendFastMode=n;
}

unsigned int GrifoTimerDevice::batchCounter() const
{
    return p_.m_batchcounter;
}

void GrifoTimerDevice::setBatchCounter(unsigned int b)
{
    p_.m_batchcounter=b;
}

int GrifoTimerDevice::sendFastMode() const
{
    return p_.sendFastMode;
}

GrifoTimerDockWidget::GrifoTimerDockWidget(QWidget *parent) :
    QFrame(parent),
    ui(new Ui::GrifoTimerDockWidget),
    p_(*new GrifoTimerDevice)
{
    ui->setupUi(this);

    ui->mapSim->addItem(p_.p_.playback.name(), QVariant((unsigned long long)&p_.p_.playback));
    ui->mapSim->addItem(p_.p_.dbstv.name(), QVariant((unsigned long long)&p_.p_.dbstv));
    ui->mapSim->addItem(p_.p_.sartv.name(), QVariant((unsigned long long)&p_.p_.sartv));
    ui->mapSim->addItem(p_.p_.rbmtv.name(), QVariant((unsigned long long)&p_.p_.rbmtv));
    ui->mapSim->addItem(p_.p_.tgttv.name(), QVariant((unsigned long long)&p_.p_.tgttv));
    ui->mapSim->addItem(p_.p_.adtv.name(), QVariant((unsigned long long)&p_.p_.adtv));

    connect(ui->tvProps, &QToolButton::released, this,
            [this]()
            {
                QObject* obj=reinterpret_cast<QObject*>(ui->mapSim->currentData().toULongLong());
                QgPropertyDialog dlg(obj, this);
                dlg.exec();
            }
    );

    connect(ui->cbTTReset, &QCheckBox::toggled, this,
            [this](bool active)
    {
       if (active)
           p_.p_.ttsynchTimer.start(1000*ui->nominalPeriod->value());
       else
           p_.p_.ttsynchTimer.stop();

    });
    connect(ui->nominalPeriod, static_cast<void (QSpinBox::*)(int index)>(&QSpinBox::valueChanged), this,
            [this](int value)
            {
                if (ui->cbTTReset->isChecked())
                    p_.p_.ttsynchTimer.start(value*1000);
            }
    );

    connect(&p_.p_.ttsynchTimer, &QgScopeChronoTimer::periodExpired, this,
            [this]()
            {
               ui->timeNow->setText(QString("%1").arg(p_.p_.ttsynchTimer.timeNow()/1000));
            }
    );
    connect(ui->cbMode, QCheckBox::stateChanged,
            [this]()
            {
                Qt::CheckState cs=ui->cbMode->checkState();
                p_.p_.batchMode=cs==Qt::Checked ? GrifoTimerDevice::BatchAuto : GrifoTimerDevice::BatchSlave;
            }
    );

    connect(&p_.chronoTimer(), QgScopeChronoTimer::periodExpired,
            [this]()
            {
                p_.p_.bc_counter=1;
                ui->generatedBatches->setValue(p_.p_.m_batchcounter);
            }
    );

    connect(&p_, &GrifoTimerDevice::batchGenerated,
            [this](int n)
            {
                ui->tvBatch->setValue(n);
            }
    );

    connect(ui->tgtReset, &QToolButton::clicked,
            [this](bool)
    {
        p_.p_.batchReset();
    });
}

GrifoTimerDockWidget::~GrifoTimerDockWidget()
{
    delete &p_;
    delete ui;
}

QgScopeDevice* GrifoTimerDockWidget::scopeDevice() const
{
    return &p_;
}

void GrifoTimerDockWidget::on_tbStep_clicked()
{
    p_.p_.batchExec(QByteArray());
}
