#include "videowin.h"
#include "gqt.h"
#include "crc.h"
#include <QTextEdit>
#include <QPushButton>
#include <QMessageBox>
#include <QDateTime>
#include <QTimerEvent>
#include <QApplication>
#include <QDesktopWidget>
#include <QScreen>
#include <QDebug>
#include <QPainter>

using namespace std;

VideoWin *VideoWin::newIns(QByteArray &name, QWidget *parent) {
    if(name.isEmpty()) return 0;
    char errbuf[PCAP_ERRBUF_SIZE]{'\0'};
    auto pcapRe = pcap_open_live(name.data(), 65536, PCAP_OPENFLAG_PROMISCUOUS, 50, errbuf);
    if(pcapRe == 0) {
        QMessageBox::critical(parent, "Error", QString("打开网卡失败")+errbuf);
        return 0;
    }
    auto pcapSend = pcap_open_live(name.data(), 65536, 0, 50, errbuf);
    if(pcapSend == 0) {
        QMessageBox::critical(parent, "Error", QString("打开网卡失败")+errbuf);
        return 0;
    }
    return new VideoWin(pcapRe, pcapSend, parent);
}

VideoWin::VideoWin(pcap_t *pcapRece, pcap_t *pcapSend, QWidget *parent) : BaseWin{parent} {
    setWindowModality(Qt::WindowModal);
    setAttribute(Qt::WA_DeleteOnClose);
    setWindowTitle("视频传输");
    resize(800, 600);

    auto vBox = new QVBoxLayout(center);
    vBox->setContentsMargins(0,0,0,0);
    vBox->setSpacing(3);
    vBox->addLayout(addBtns(new QHBoxLayout()));

    auto hBox = new HBox(vBox);

    fdWidth = new QSpinBox;
    fdWidth->setRange(1, 9999);
    fdWidth->setValue(640);
    hBox->addWidget(fdWidth);

    hBox->addWidget(new QLabel("x"));

    fdHeight = new QSpinBox;
    fdHeight->setRange(1, 9999);
    fdHeight->setValue(360);
    hBox->addWidget(fdHeight);


    hBox->addWidget(new QLabel("截图间隔(ms)"));
    fdMsecCapScr = new QSpinBox;
    fdMsecCapScr->setRange(1, 1000);
    fdMsecCapScr->setValue(17);
    hBox->addWidget(fdMsecCapScr);
    hBox->addSpacing(10);

    fdNoReview = new QCheckBox("关闭显示");
    fdNoReview->setChecked(noReview);
    hBox->addWidget(fdNoReview);

    hBox->addSpacing(10);
    auto fdUseOldProtocol = new QCheckBox("用旧协议");
    connect(fdUseOldProtocol, &QRadioButton::toggled, [=](bool checked) {
        useOldProto = checked;
        if(thdSend) thdSend->useOldProto = checked;
        if(thdRece) thdRece->useOldProto = checked;
    });
    hBox->addWidget(fdUseOldProtocol);

    hBox->addSpacing(10);

    hBox->addWidget(new QLabel("接收线程优先级"));
    fdYxj = new QSpinBox;
    fdYxj->setRange(0, 7);
    fdYxj->setValue(6);

    connect(fdYxj, (void(QSpinBox::*)(int))&QSpinBox::valueChanged, [=](int value) {
        if(thdRece) thdRece->setPriority(QThread::Priority(value));
    });
    hBox->addWidget(fdYxj);


    hBox->addSpacing(20);

    auto fdStart = new QRadioButton("发送");
    connect(fdStart, &QRadioButton::toggled, this, [this](bool checked) {
        if(! checked) return;
        screen = QGuiApplication::primaryScreen();
        if(timerId==0) timerId = startTimer(fdMsecCapScr->value());
    });
    hBox->addWidget(fdStart);
    hBox->addSpacing(20);

    fdEnd = new QRadioButton("停止");
    connect(fdEnd, &QRadioButton::toggled, this, [this](bool checked) {
        if(! checked) return;
        if(timerId) {
            killTimer(timerId);
            timerId = 0;
        }
    });
    hBox->addWidget(fdEnd);
    hBox->addSpacing(20);

    fdInfo = new QLabel;
    fdInfo->setStyleSheet("QLabel{color: #f22;}");
    hBox->addWidget(fdInfo);
    hBox->addStretch();

    hBox = new HBox(vBox);

    hBox->addWidget(new QLabel("接收:"));
    hBox->addSpacing(20);
    hBox->addWidget(new QLabel("丢包次数: "));
    hBox->addWidget(fdLostTimes = new QLabel);
    fdLostTimes->setMinimumWidth(40);
    hBox->addWidget(new QLabel("丢包数: "));
    hBox->addWidget(fdLostPkts = new QLabel);
    fdLostPkts->setMinimumWidth(40);
    hBox->addStretch();

    auto fdCanvas = new Canvas;
    vBox->addWidget(fdCanvas, 1);

    connect(fdNoReview, &QRadioButton::toggled, [=](bool checked) {
        noReview = checked;
        if(thdRece) thdRece->noReview = checked;
        if(noReview) {
            fdCanvas->img = QImage();
            fdCanvas->update();
        }
    });

    thdRece = new VideoRecThread(pcapRece);
    connect(thdRece, &QThread::finished, this, [this] {
        thdRece = 0;
    });
    connect(thdRece, &VideoRecThread::onMsg, fdCanvas, [this, fdCanvas](const QByteArray chars) {
        if(noReview) return;
        auto data = (uchar *)chars.data();
        if(data[14]==0x12) {
            imgLines.append(chars);
            int i = (data[18]<<8 | data[19]) + 1;
            if(i > imgHeight) imgHeight = i;
            i = (data[20]<<8 | data[21]) + (data[22]<<8 | data[23]);
            if(i > imgWidth) imgWidth = i;
        } else if(data[14]==0x11 && imgWidth && imgHeight) {
            //qDebug()<<"imgWidth"<<imgWidth<<"imgHeight"<<imgHeight;
            QImage img(imgWidth, imgHeight, QImage::Format_RGB888);
            imgWidth = 0;
            imgHeight = 0;
            foreach(auto line, imgLines) {
                auto data = (uchar *)line.data();
                memcpy(img.bits()+(data[18]<<8 | data[19])*img.bytesPerLine()+(data[20]<<8 | data[21])*3, data+24, (data[22]<<8 | data[23])*3);
            }
            imgLines.clear();
            fdCanvas->img = img;
            fdCanvas->update();
        }
    });
    connect(thdRece, &VideoRecThread::onImg, fdCanvas, [this, fdCanvas](QImage img, int lostTimes, int lostPkts) {
        if(noReview) return;
        fdCanvas->img = img;
        fdCanvas->update();
        if(lostPkts==0) return;
        this->lostTimes += lostTimes;
        this->lostPkts += lostPkts;
        fdLostTimes->setText(QString::number(this->lostTimes));
        fdLostPkts->setText(QString::number(this->lostPkts));
    });
    thdRece->start(QThread::Priority(fdYxj->value()));//HighestPriority //InheritPriority //TimeCriticalPriority

    thdSend = new VideoSendThread(pcapSend);
    connect(thdSend, &QThread::finished, this, [this] {
        fdEnd->setChecked(true);
        thdSend = 0;
    });
    connect(thdSend, &VideoSendThread::onErr, this, [this](QString err) {
        fdEnd->setChecked(true);
        QMessageBox::critical(this, "Error", err);
    });
    thdSend->start();
}

void VideoWin::timerEvent(QTimerEvent *event) {
    if(event->timerId()!=timerId) BaseWin::timerEvent(event);
    else {
        int width = fdWidth->value();
        int height = fdHeight->value();
        if(width < 32 || height < 32) return;
        auto devicePixelRatio = screen->devicePixelRatio();
        auto pixmap = screen->grabWindow(0, 0, 0, width/devicePixelRatio, height/devicePixelRatio);
        auto img = pixmap.toImage();
        img.convertTo(QImage::Format_RGB888);
        {
            std::lock_guard<std::mutex> lock(thdSend->mtx);
            if(thdSend->imgs.size()>2) return;
            thdSend->imgs.append(img);
        }
        auto now_epoch = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
        int dur = now_epoch - last_epoch;
        last_epoch = now_epoch;
        fdInfo->setText(info = "帧间隔: "+QString::number(dur)+"  帧缓存: "+QString::number(thdSend ? thdSend->imgs.size() : 0));
    }
}

VideoSendThread::VideoSendThread(pcap_t *pcap) : pcap(pcap) {
    connect(this, &QThread::finished, this, &QThread::deleteLater);
}
#define MAX_ONCE 1482
void VideoSendThread::run() {
    QImage img;
    quint32 idx = 0;
    while(status!=2) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            img = imgs.isEmpty() ? QImage() : imgs.takeFirst();
        }
        if(img.isNull()) {
            QThread::msleep(15);
            continue;
        }
        auto queue = pcap_sendqueue_alloc(img.width()*img.height()*4);
        if(useOldProto) {
            QByteArray bytes;
            bytes.append("\x00\x11\x22\x33\x44\x55", 6); //目的地址
            bytes.append("\x00\x11\x22\x33\x44\x55", 6); //源地址
            bytes.append("\x10\x5A"); //协议标识
            bytes.append("\x11\0\0\0", 4); //包类型标识 保留 网口标识 接收卡标识
            bytes.append(frameIdx>>8).append(frameIdx);//帧循环计数
            frameIdx++;
            bytes.append(2, 0); //帧保持计数
            bytes.append("\x0\xff\x0\x0\x0", 5); //自动亮度模式 整屏亮度 实时参数模式 测试模式 保留
            auto crc32 = crc32_calc((uint8_t*)bytes.data(), bytes.length());
            bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32);
            //发送帧开始指令包
            struct pcap_pkthdr pktheader;
            pktheader.len = pktheader.caplen = bytes.size();
            if(pcap_sendqueue_queue(queue, &pktheader, (u_char*)bytes.data()) == -1) {
                onErr(QString("添加开始失败: ")+pcap_geterr(pcap));
                goto end;
            }
            auto lineLen = img.width() * 3;
            int once;
            if(lineLen > MAX_ONCE) {
                int cnt = (lineLen + MAX_ONCE - 1) / MAX_ONCE;
                once = (lineLen + cnt - 1) / cnt;
            } else once = lineLen;
            auto bytesPerLine = img.bytesPerLine();
            auto bits = img.constBits();
            //按行发送图像数据包
            for(int i=0; i<img.height(); i++) for(int j=0; j<lineLen; j+=once) {
                int dataLen = lineLen - j;
                if(dataLen > once) dataLen = once;
                bytes.clear();
                bytes.append("\x00\x11\x22\x33\x44\x55", 6); //目的地址
                bytes.append("\x00\x11\x22\x33\x44\x55", 6); //源地址
                bytes.append("\x10\x5A"); //协议标识
                bytes.append("\x12\0\0\0", 4); //包类型标识 保留 网口标识 接收卡标识
                bytes.append(i>>8).append(i).append(j/3>>8).append(j/3); //行列起始位置
                bytes.append(dataLen/3>>8).append(dataLen/3); //包像素计数
                bytes.append((const char*)bits + i * bytesPerLine + j, dataLen); //图像数据
                crc32 = crc32_calc((uint8_t*)bytes.data(), bytes.length());
                bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32);
                pktheader.len = pktheader.caplen = bytes.size();
                if(pcap_sendqueue_queue(queue, &pktheader, (u_char*)bytes.data()) == -1) {
                    onErr(QString("添加数据失败: ")+pcap_geterr(pcap));
                    goto end;
                }
            }
            u_int res;
            if((res = pcap_sendqueue_transmit(pcap, queue, 0)) < queue->len) {
                onErr(QString::asprintf("发送包出错: %s. 仅发了 %d / %d bytes", pcap_geterr(pcap), res, queue->len));
                goto end;
            }
        } else {
            QByteArray bytes;
            bytes.append("\x55\x55\x9a\x3d"); //前导, 版本号, 服务类型
            bytes.append("\0\x4", 2); //数据长度
            bytes.append(4, '\xff'); //目的地址
            bytes.append(4, 0); //源地址
            bytes.append("\x35\0\xc\xbb", 4); //内存指针
            bytes.append(2, 0); //应答填充项
            auto crc32 = crc32_calc((uint8_t*)bytes.data()+2, bytes.length()-2);
            bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32);
            bytes.append(img.width()>>8).append(img.width()).append(img.height()>>8).append(img.height());
            crc32 = crc32_calc((uint8_t*)bytes.data()+bytes.length()-4, 4);
            bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32);
            //发送帧开始指令包
            struct pcap_pkthdr pktheader;
            pktheader.len = pktheader.caplen = bytes.size();
            if(pcap_sendqueue_queue(queue, &pktheader, (u_char*)bytes.data()) == -1) {
                onErr(QString("添加开始失败: ")+pcap_geterr(pcap));
                goto end;
            }
            auto lineLen = img.width() * 3;
            int once;
            if(lineLen > MAX_ONCE) {
                int cnt = (lineLen + MAX_ONCE - 1) / MAX_ONCE;
                once = (lineLen + cnt - 1) / cnt;
            } else once = lineLen;
            auto bytesPerLine = img.bytesPerLine();
            auto bits = img.constBits();
            idx = 0;
            //按行发送图像数据包
            for(int i=0; i<img.height(); i++) for(int j=0; j<lineLen; j+=once) {
                int dataLen = lineLen - j;
                if(dataLen > once) dataLen = once;
                dataLen += 4;
                bytes.clear();
                bytes.append("\x55\x55\x1\x33"); //前导, 版本号, 服务类型
                bytes.append(dataLen>>8).append(dataLen); //数据长度
                bytes.append(4, '\xff'); //目的地址
                bytes.append(4, 0); //源地址
                bytes.append(i>>8).append(i).append(j>>8).append(j); //内存指针
                bytes.append(2, 0); //应答填充项
                crc32 = crc32_calc((uint8_t*)bytes.data()+2, bytes.length()-2);
                bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32);
                bytes.append(idx>>24).append(idx>>16).append(idx>>8).append(idx);//包序号
                idx++;
                bytes.append((const char*)bits + i * bytesPerLine + j, dataLen - 4);
                crc32 = crc32_calc((uint8_t*)bytes.data()+bytes.length()-dataLen, dataLen);
                bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32);
                pktheader.len = pktheader.caplen = bytes.size();
                if(pcap_sendqueue_queue(queue, &pktheader, (u_char*)bytes.data()) == -1) {
                    onErr(QString("添加数据失败: ")+pcap_geterr(pcap));
                    goto end;
                }
            }
            bytes.clear();
            bytes.append("\x55\x55\x9a\x3d"); //前导, 版本号, 服务类型
            bytes.append(2, 0); //数据长度
            bytes.append(4, '\xff'); //目的地址
            bytes.append(4, 0); //源地址
            bytes.append("\x35\0\xc\xcc", 4); //内存指针
            bytes.append(2, 0); //应答填充项
            crc32 = crc32_calc((uint8_t*)bytes.data()+2, bytes.length()-2);
            bytes.append(crc32>>24).append(crc32>>16).append(crc32>>8).append(crc32);
            pktheader.len = pktheader.caplen = bytes.size();
            //发送帧结束指令包
            if(pcap_sendqueue_queue(queue, &pktheader, (u_char*)bytes.data()) == -1) {
                onErr(QString("添加结束失败: ")+pcap_geterr(pcap));
                goto end;
            }
            u_int res;
            if((res = pcap_sendqueue_transmit(pcap, queue, 0)) < queue->len) {
                onErr(QString::asprintf("发送包出错: %s. 仅发了 %d / %d bytes", pcap_geterr(pcap), res, queue->len));
                goto end;
            }
        }
        end:
        pcap_sendqueue_destroy(queue);
    }
}
/* Callback function invoked by libpcap for every incoming packet */
//void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) {

//}

VideoRecThread::VideoRecThread(pcap_t *pcap) : pcap(pcap) {
    connect(this, &QThread::finished, this, &QThread::deleteLater);
}
void VideoRecThread::run() {
    pcap_pkthdr *header;
    const u_char *data;
    QImage img;
    int res;
    int lastIdx = -1;
    int lostTimes{0}, lostPkts{0};
    while((res = pcap_next_ex(pcap, &header, &data)) >= 0) {
        if(status==2) return;
        if(status==1 || res == 0 || noReview) continue; //超时
        if(header->caplen<24) continue;
        if(useOldProto) {
            if(data[0]!=0 || data[1]!=0x11 || data[2]!=0x22 || data[3]!=0x33 || data[12]!=0x10 || data[13]!=0x5A) continue;
            emit onMsg(QByteArray((char*)data, header->caplen));
        } else {
            if(data[0]!=0x55 || data[1]!=0x55) continue;
            if(data[2]==0x9a && data[3]==0x3d) {
                if(data[17]==0xBB) {
                    img = QImage(((int)data[24] << 8) + data[25], ((int)data[26] << 8) + data[27], QImage::Format_RGB888);
                    lastIdx = -1;
                    lostTimes = 0;
                    lostPkts = 0;
                } else if(data[17]==0xCC) emit onImg(img, lostTimes, lostPkts);
                continue;
            }
            if(data[2]!=0x1 || data[3]!=0x33) continue;
            int len = ((int)data[4] << 8) + data[5];
            int row = ((int)data[14] << 8) + data[15];
            int col = ((int)data[16] << 8) + data[17];
            quint32 idx = ((quint32)data[24] << 24) + ((quint32)data[25] << 16) + ((quint32)data[26] << 8) + data[27];
            //auto epoch = chrono::duration_cast<chrono::microseconds>(chrono::steady_clock::now().time_since_epoch()).count();
            memcpy(img.bits()+row*img.bytesPerLine()+col, &data[28], len-4);
            int diff = idx - lastIdx - 1;
            lastIdx = idx;
            if(diff > 0) {
                //qDebug()<<"丢包"<<diff;
                lostTimes++;
                lostPkts += diff;
            }
        }
    }
    emit onErr(pcap_geterr(pcap));
}

void Canvas::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.drawImage(0, 0, img);
}
