#include "grifodisplaydevice.h"

#include <QElapsedTimer>
#include <QUdpSocket>
#include <QNetworkDatagram>
#include <QIcon>
#include <QMainWindow>
#include <QDebug>

//#include "qgrexrtgsocket.h"
//#include "RexExternalChannelFactory.h"
//#include "RexIExternalChannel.h"
#include "qgrexnetwork.h"

#include "grifodisplaywidget.h"
#include "QGraphicImage256.h"

//#include "grifo_cmp_dbgsim.h"

#include "grifodebugmessage.h"

typedef unsigned char video_memory_t[1024][1024];
#include "gc_remote_display.h"

typedef gc_remote_display_reply_t gc_reply_t;

#if 0
struct gc_remote_display_buffer_t
{
    enum geometry_t { max_dx=608, max_dy=600, max_rows_per_message=2};

    enum gev_fmt_t
    {
        GEV_LEADER = 0x81,
        GEV_TRAILER = 0x82,
        GEV_PAYLOAD = 0x83
    };

    struct gev_header_t
    {
        uint16_t gev_status;
        uint16_t gev_flags;
        uint8_t fmt;
        uint8_t spare;
        uint16_t spare2;
        //uint64_t blockid;
        uint32_t blockid_h;
        uint32_t blockid_l;
        uint32_t packet_id;
    };

    gev_header_t gev;

    uint32_t type;
    uint32_t frag_number;
    uint32_t generated_frames;
    uint32_t sent_frames;
    uint32_t dx;
    uint32_t dy;
    uint32_t pitch;
    uint32_t row;
    uint8_t data[max_dx*max_rows_per_message];
};

#endif

//static GrifoCMpDbgSimMessage dbgSimMsg;
#define dbgSimMsg   GrifoDebugMessage::cmpDbgSim

#define GC_IP_LOCAL_PORT	51000

class GrifoDisplayDevice::Implementation
{
public:
    QgScopeDevice& qgsDevice;

    QMainWindow* mainWin;

    GrifoDisplayWidget* w;
    //RdrGraphicsScene* scene;

    QUdpSocket rcvSocket;

    QElapsedTimer timer;
    video_memory_t video_buffer;
    bool freeze;
    unsigned long rcv_frames;
    unsigned long rcv_incmplete;
    unsigned long rcv_time;

    QHostAddress displayServerAddress;
    QHostAddress displayServerBroadcastAddress;

    int frame_timeout;
    unsigned int frame_timeout_counter;

    Implementation(QgScopeDevice* d, bool as_window=false):
        qgsDevice(*d),
        w(0),
        //scene(0),
        //rcvSocket("GCDISPLAY"),
        freeze(false)
    {
      frame_timeout=0;
      frame_timeout_counter=0;
      qgsDevice.setProperty("gevWindow", 8);

      mainWin=0;

      w=new GrifoDisplayWidget;

      if (as_window)
      {
         mainWin=new QMainWindow(0);
         mainWin->setWindowTitle("GrifoVideo");
         mainWin->setCentralWidget(w);
         mainWin->show();
      }

      //scene=w->graphicsScene();

      rcvSocket.bind(QgRexNetwork().networkInterface()/*QHostAddress::Any*/, 51001); //rcvSocket.localBind(51001);
      rcvSocket.setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 256*1024);

      displayServerBroadcastAddress=QgRexNetwork().networkBroadcast();
      displayServerAddress=displayServerBroadcastAddress;
      //rcvSocket.remoteBind(QHostAddress::Any); //, 51001);
      //rcvSocket.bind(QgRexNetwork().networkInterface()/*QHostAddress("192.168.2.10")*/, 51001);
      //rcvSocket.setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 256*1024);
      //rcvSocket.qtSocket().setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 256*1024);

      //rcvSocketBroadcast.bind(QHostAddress("192.168.2.255"), 51001);
      //rcvSocket.setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 256*1024);

      //dbgSocket.bind(QHostAddress::LocalHost);
      //dbgSocket.connectToHost(QHostAddress::LocalHost, 50001);
#if 0
      dbgMsg.marker_value='!';
      dbgMsg.fmt=0;
      dbgMsg.op=0;
      dbgMsg.mailbox=63<<2;
      dbgMsg.extsize_bytes=0;
      dbgMsg.payload_size_bytes=4*3;
      dbgMsg.payload[0]=0;
      dbgMsg.payload[1]=0;
      dbgMsg.payload[2]=0;
#endif
      //xCh=&RexExternalChannelFactory::createDebugChannel(0);
      timer.start();
    }

    void dbgSend()
    {
        //dbgSocket.write((const char*)&dbgMsg, sizeof dbgMsg);
        //xCh->sendTo(63, &dbgSimMsg, sizeof dbgSimMsg); //dbgMsg.payload, 4*4);
        GrifoDebugMessage().cmpUpdate();
    }

    void requestDisplay()
    {
        static gc_reply_t reply;
        reply.spare=0x12;
        int win_auto=qgsDevice.property("gevWindow").toInt();
        if (win_auto>=0)
           reply.max_capacity=win_auto; //qgsDevice.property("gevWindow").toUInt();
        else
            reply.max_capacity=0;
        if (frame_timeout<=0)
            rcvSocket.writeDatagram((char*)&reply, sizeof reply, displayServerBroadcastAddress, GC_IP_LOCAL_PORT);
        else
            rcvSocket.writeDatagram((char*)&reply, sizeof reply, displayServerAddress, GC_IP_LOCAL_PORT);
    }

    void receiveImage(QUdpSocket* s=0, QNetworkDatagram* pendingd=0)
    {
        static gc_reply_t reply;
        static int win_auto=-1;

        if (s==0)
            s=&rcvSocket; //.qtSocket();

        while (s->hasPendingDatagrams() || pendingd)
        {
           QNetworkDatagram datagram = pendingd ? *pendingd : s->receiveDatagram();
           pendingd=0;

           const gc_remote_display_buffer_t& sendbuffer=
             *reinterpret_cast<const gc_remote_display_buffer_t*>(datagram.data().constData());

           const gc_remote_display_header_t& header=sendbuffer.header;

           frame_timeout=4;

           if (header.type==100)
           {
               //Update palette
               w->updatePalette((uint32_t*)sendbuffer.data);
           }

           if ((header.type==0) || (header.type==3))
           {
               displayServerAddress=datagram.senderAddress();
               //qDebug()<<"GC Reply:"<<datagram.senderAddress().toString()<<datagram.senderPort();
               reply.gev=header.gev;
               reply.sent_frames=header.sent_frames;
               reply.frag_number=header.frag_number;
               reply.type=0x0001|header.type;
               if (header.type==0)
               {
                   win_auto=qgsDevice.property("gevWindow").toInt();
                   if (win_auto>=0)
                        reply.max_capacity=win_auto; //qgsDevice.property("gevWindow").toUInt();
                }
               //reply.max_capacity=64;
               //rcvSocket.writeDatagram((char*)&reply, sizeof reply, datagram.senderAddress(), datagram.senderPort());
               //rcvSocket.send(0, &reply, sizeof reply); //, datagram.senderAddress(), datagram.senderPort());
               rcvSocket.writeDatagram((char*)&reply, sizeof reply, datagram.senderAddress(), datagram.senderPort());
           }

           if ((header.type!=100) && (header.row<=512) && (header.pitch<=640))
           {
               int number_of_rows=header.number_of_rows;
               int lost=update(header.row, number_of_rows, header.dx, header.pitch, sendbuffer.data, header.type==4);
               if (lost && win_auto)
               {
                   if (reply.max_capacity>10)

                        reply.max_capacity-=reply.max_capacity>128 ? 32 : (reply.max_capacity>100 ? 10 : 2);
                   else if (reply.max_capacity<10)
                       reply.max_capacity=256;

               }
           }

           if (header.gev.fmt==gc_remote_display_header_t::GEV_TRAILER)
           {
               frame_timeout=10;
               requestDisplay();
           }
        }
    }

    int update(unsigned int row, unsigned int n_rows, unsigned int n_cols, unsigned int pitch_, const void* src, bool last_frag)
    {
        static unsigned int last_row;
        static unsigned missed;
        static float fms;

        int lost=0;

        if (last_row<row)
        {
            ++missed;
            //timer.start();
        }

        qint64 framePeriod=0;

        if (row==0)
        {
            framePeriod=timer.restart(); //start();
            fms=float(framePeriod);
        }

        if ((last_row!=row) && (row>0))
        {
          ++missed;
          //qDebug()<<"GC: l="<<last_row<<"r="<<row;
        }
        unsigned char* p=video_buffer[row];
        const unsigned int pitch=pitch_;
        const char* fb=(const char*)src;
        for(unsigned int i=0; i<n_rows; ++i, fb+=pitch)
        {
          memcpy(&p[i*1024]/*(*video_memory)[i]*/, fb, n_cols);
        }

        if (last_frag)
        {
            auto elapsed=timer.nsecsElapsed();
            if (!freeze)
            {
#if 0
                video_memory_t* video_memory=(video_memory_t*)w->videoMemory(); //scene->video_memory();
                unsigned char* p=&((*video_memory)[0][0]);
                const unsigned int pitch=512;
                const char* fb=(const char*)video_buffer[0];
                for(unsigned int i=0; i<512; ++i, fb+=1024)
                {
                  memcpy(&p[i*1024]/*(*video_memory)[i]*/, fb, pitch);
                }
#else
                const char* fb=(const char*)video_buffer[0];
                w->updateImage(fb, n_cols, 640, 1024);
#endif
                w->updateDisplay(true);
                //scene->ggUpdateImage();
                //scene->update();
            }
            ++rcv_frames;
            if (missed)
            {
                ++rcv_incmplete;
                lost=missed;
                missed=0;
            }
            //unsigned int us=elapsed/1000;
            float ms=float(elapsed)/(1000.0*1000.0);
            //QString fcnt=QString("%1/%2: %3ms (%4ms)").arg(rcv_frames).arg(rcv_incmplete).arg(fms).arg(ms);
            QString fcnt=QString("%1 (%2) %3ms (%4ms)").arg(rcv_frames).arg(frame_timeout_counter).arg(fms).arg(ms);
            //QString fcnt=QString("%1").arg(rcv_frames);
            //lblStatus->setText(fcnt);
            w->showShortMessage(fcnt);
            qgsDevice.setStatusString(fcnt);
            //timer.start();
            last_row=0;
        }
        else
            last_row=row+n_rows;
        return lost;
    }

};

GrifoDisplayDevice::GrifoDisplayDevice(bool asWindow, QObject */*parent*/):
    QgScopeDevice("display.gc.grifo", "rdrdisplay"),
    p_(*new Implementation(this,asWindow))
{

    QIcon icon1;
    icon1.addFile(QStringLiteral(":/icons/icons/pause44.png"), QSize(), QIcon::Normal, QIcon::Off);
    p_.w->setIconFreeze(icon1);
    icon1.addFile(QStringLiteral(":/icons/icons/photo212.png"), QSize(), QIcon::Normal, QIcon::Off);
    p_.w->setIconOcclusion(icon1);
    //QIcon icon1;
    icon1.addFile(QStringLiteral(":/icons/icons/list89.png"), QSize(), QIcon::Normal, QIcon::Off);
    p_.w->setIconDbgMenu(icon1);

    //localFreeze->setIcon(icon1);    
    connect(&p_.rcvSocket, &QUdpSocket::readyRead, this,
            [this]()
            {
                QUdpSocket* s=qobject_cast<QUdpSocket*>(sender());
                if (deviceActive())
                    p_.receiveImage(s);
                else
                {
                    while (s->hasPendingDatagrams())
                    {
                       s->receiveDatagram();
                    }
                }
            }
            );

    connect(this, &QgScopeDevice::deviceActivated, this,
            [this](bool /*active*/)
            {
                //setEnabled(active);
            }
            );

    connect(p_.w, &GrifoDisplayWidget::dbgCursorSlew, this,
            [this](bool /*active*/, int x, int y) //, Qt::KeyboardModifiers mod)
            {
                //p_.dbgMsg.payload[1]=x;
                //p_.dbgMsg.payload[2]=y;
                dbgSimMsg.dbg_crs_flags|=GrifoCMpDbgSimMessage::gc_flag_cursor_show;
                dbgSimMsg.dbg_crs_x=x;
                dbgSimMsg.dbg_crs_y=y;
                p_.dbgSend();
            }
            );
    connect(p_.w, &GrifoDisplayWidget::freezeChanged, this,
            [this](bool frozen)
    {
       p_.freeze=frozen;
    });
    connect(p_.w, &GrifoDisplayWidget::dbgCrsFlagsChanged, this,
            [this](GrifoDisplayWidget::DbgCrsFlags flags)
    {
        //p_.dbgMsg.payload[0]=flags;
        dbgSimMsg.dbg_crs_flags=flags;
        p_.dbgSend();
    });

    connect(p_.w, &GrifoDisplayWidget::dbgSymFlagsChanged, this,
            [this](GrifoDisplayWidget::DbgSymFlags flags)
    {
        //p_.dbgMsg.payload[0]=flags;
        //dbgSimMsg.dbg_crs_flags=flags;
        dbgSimMsg.gc_declutter_global=flags & 0x1F;
        dbgSimMsg.gc_declutter=flags;

        p_.dbgSend();
    });

    connect(&this->chronoTimer(), &QgScopeChronoTimer::periodExpired, this,
            [this]()
            {
                if (!deviceActive())
                    return;
                if (p_.frame_timeout>0)
                {
                    --p_.frame_timeout;
                    if (p_.frame_timeout==0)
                    {
                        //qDebug()<<"Remote Display: timeout!"<<p_.frame_timeout_counter;
                    }
                }
                else
                {
                    ++p_.frame_timeout_counter;
                    p_.requestDisplay();
                }
            }
        );
    chronoTimer().start(500000);
}

GrifoDisplayDevice::~GrifoDisplayDevice()
{
    delete &p_;
}

QWidget* GrifoDisplayDevice::displayWidget()
{
    return p_.w;
}


