#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <memory>

#include <QFile>
#include <QDebug>
#include <QThread>
#include <QtEndian>

CFDataRef createExtra(std::vector<char> &sps, std::vector<char> &pps)
{
    size_t extraSize = 6 + 2 + sps.size() + 3 + pps.size();
    uint8_t *extra = (uint8_t *)malloc(extraSize);
    uint8_t *p = extra;

    *(p + 0) = 1; // version
    *(p + 1) = sps[1]; // profile
    *(p + 2) = sps[2]; // profile compact
    *(p + 3) = sps[3]; // level
    *(p + 4) = 0xff; // 6 bits reserved (111111) + 2 bits nal size length - 3 (11)
    *(p + 5) = 0xe1; // 3 bits reserved (111) + 5bits number of sps (00001)

    *(p + 6) = (sps.size() >> 8) & 0xFF;
    *(p + 7) = (sps.size() & 0xFF);
    memcpy(p + 8, &sps[0], sps.size());

    p += 8 + sps.size();

    *(p + 0) = 1; // number of pps
    *(p + 1) = (pps.size() >> 8) & 0xFF;
    *(p + 2) = (pps.size() & 0xFF);
    memcpy(p + 3, &pps[0], pps.size());

    CFDataRef result = CFDataCreate(kCFAllocatorDefault, extra, extraSize);

    free(extra);

    return result;
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    vdaCreated(false), extraUpdated(false),
    decoder(0),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    start();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::start()
{
    QFile file("/Users/iruis/Movies/fullhd.h264");
    file.open(QFile::ReadOnly);

    QByteArray packet = file.readAll();

    file.close();

    qDebug() << "using file: " << file.fileName();

    char *bytes = packet.data();
    std::vector<char*> points;
    std::vector<int> nalSizes;

    for(int i = 0; i < packet.length() - 4; i++)
    {
        uint32_t startCode = qFromBigEndian(*(uint32_t *)(bytes + i));

        if(startCode == 1)
        {
            points.push_back(bytes + i);
            nalSizes.push_back(4);

            i += 3;
        }

        if((startCode & 0xFFFFFF00) == 0x00000100)
        {
            points.push_back(bytes + i);
            nalSizes.push_back(3);

            i += 2;
        }
    }

    // qDebug() << "point size: " << points.size() << ", data size: " << packet.size;

    for(std::vector<char*>::size_type i = 0; i < points.size(); i++)
    {
        int size = 0;

        bytes = points.at(i);

        if(i < points.size() - 1)
        {
            size = points.at(i + 1) - bytes;
        }
        else
        {
            size = packet.length() - (bytes - packet.data());
        }

        //qDebug() << "- frame size: " << size;
        QString message = QString("nal type: %1 (%2), nal size: %3, data size: %4")
                .arg(bytes[nalSizes[i]] & 0x1F)
                .arg(bytes[nalSizes[i]], 2, 16, QChar('0'))
                .arg(nalSizes[i])
                .arg(size);
        qDebug() << message;

        parse(bytes, size, nalSizes[i]);

        if(i > 100)
        {
            break;
        }
    }

    if(decoder)
    {
        VDADecoderDestroy(decoder);

        decoder = 0;
    }
}

void MainWindow::parse(char *data, int size, int nalSize)
{
    int nalType = data[nalSize] & 0x1F;

    if(nalType == 7)
    {
        sps.clear();
        sps.insert(sps.end(), data + nalSize, data + size);

        qDebug() << "extract sps size: " << sps.size();

        extraUpdated = true;
    }
    if(nalType == 8)
    {
        pps.clear();
        pps.insert(pps.end(), data + nalSize, data + size);

        qDebug() << "extract pps size: " << pps.size();

        extraUpdated = true;
    }
    if(nalType == 5)
    {
        if(extraUpdated)
        {
            if(decoder)
            {
                VDADecoderDestroy(decoder);

                decoder = 0;
            }

            int32_t width = 1920;
            int32_t height = 1080;
            int32_t format = 'avc1';

            OSType pixFmtType = 'y420'; // kCVPixelFormatType_422YpCbCr8; //'y420'; //kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;

            CFNumberRef cWidth = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &width);
            CFNumberRef cHeight = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &height);
            CFNumberRef cFormat = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &format);
            CFDataRef cDataRef = createExtra(sps, pps);

            CFMutableDictionaryRef config = CFDictionaryCreateMutable(kCFAllocatorDefault, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

            CFDictionarySetValue(config, kVDADecoderConfiguration_Width, cWidth);
            CFDictionarySetValue(config, kVDADecoderConfiguration_Height, cHeight);
            CFDictionarySetValue(config, kVDADecoderConfiguration_SourceFormat, cFormat);
            CFDictionarySetValue(config, kVDADecoderConfiguration_avcCData, cDataRef);

            CFMutableDictionaryRef properties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            CFMutableDictionaryRef bufferAttr = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

            CFNumberRef cPixFmt = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pixFmtType);
            CFDictionarySetValue(bufferAttr, kCVPixelBufferPixelFormatTypeKey, cPixFmt);
            CFDictionarySetValue(bufferAttr, kCVPixelBufferIOSurfacePropertiesKey, properties);

            OSStatus status = VDADecoderCreate(config, bufferAttr, (VDADecoderOutputCallback *)callback, this, &decoder);

            CFRelease(cWidth);
            CFRelease(cHeight);
            CFRelease(cFormat);
            CFRelease(cDataRef);

            CFRelease(cPixFmt);

            CFRelease(bufferAttr);
            CFRelease(properties);
            CFRelease(config);

            extraUpdated = false;

            qDebug() << "VDADecoderCreate status: " << status;
        }
    }
    if(nalType == 1 || nalType == 5)
    {
        static int count = 0;
        static std::vector<UInt8> frame;

        count++;

        uint32_t frameSize = qToBigEndian((uint32_t)(size - nalSize));

        frame.insert(frame.end(), (UInt8 *)&frameSize, (UInt8 *)&frameSize + 4);
        frame.insert(frame.end(), (UInt8 *)(data + nalSize), (UInt8 *)(data + size));

        if(count == 4)
        {
            count = 0;

            OSStatus status;
            uint32_t flushFlags = 1 << 0; // kVADecoderFlush_emitFrames

            CFDataRef cData = CFDataCreate(kCFAllocatorDefault, &frame[0], frame.size());

            status = VDADecoderDecode(decoder, 0, cData, nil);

            qDebug() << "VDADecoderDecode: " << status;

            if(status == noErr)
            {
                status = VDADecoderFlush(decoder, flushFlags);

                qDebug() << "VDADecoderFlush: " << status;
            }

            CFRelease(cData);
            frame.clear();
        }
    }
}

void MainWindow::callback(void *decompressionOutputRefCon, CFDictionaryRef frameInfo, OSStatus status, uint32_t infoFlags, CVImageBufferRef imageBuffer)
{
    Q_UNUSED(decompressionOutputRefCon)
    Q_UNUSED(frameInfo)
    Q_UNUSED(infoFlags)

    static int saveCount;

    qDebug() << "callback status: " << status;

    if(status == 0)
    {
        if(saveCount++ < 10)
        {
            CGSize size = CVImageBufferGetDisplaySize(imageBuffer);

            qDebug() << "is planar: " << CVPixelBufferIsPlanar(imageBuffer);
            qDebug() << "cgsize: " << size.width << ", " << size.height;

            QFile file(QString("frame_%1.raw").arg(saveCount, 3, 10, QChar('0')));
            file.open(QFile::WriteOnly);

            CVPixelBufferLockBaseAddress(imageBuffer, 0);

            if(CVPixelBufferIsPlanar(imageBuffer))
            {
                void *bufferY = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
                void *bufferU = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
                void *bufferV = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 2);

                file.write((const char *)bufferY, 1920 * 1080);
                file.write((const char *)bufferU, 1920 * 1080 / 4);
                file.write((const char *)bufferV, 1920 * 1080 / 4);
            }
            else
            {
                // ...
                void *buffer = CVPixelBufferGetBaseAddress(imageBuffer);

                file.write((const char *)buffer, 1920 * 1080);
            }

            CVPixelBufferUnlockBaseAddress(imageBuffer, 0);

            file.close();
        }
    }
}
