/*
    Copyright 2016-2019 Arisotura

    This file is part of melonDS.

    melonDS is free software: you can redistribute it and/or modify it under
    the terms of the GNU General Public License as published by the Free
    Software Foundation, either version 3 of the License, or (at your option)
    any later version.

    melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with melonDS. If not, see http://www.gnu.org/licenses/.
*/

#include <stdio.h>
#include <string.h>
#include "MelonCap.h"
#include "libui/ui.h"
#include "../NDS.h"
#include "../GPU.h"

#include <windows.h>
#include <setupapi.h>
#include <guiddef.h>
#include <winusb.h>


namespace MelonCap
{

uiWindow* Window;
uiArea* Area;
uiAreaHandler AreaHandler;
uiDrawBitmap* WinBitmap;
bool WinBitmapInited;

u32* WinBitmapData;

// this crap was built from the reverse-engineering of ds_capture.exe
// mixed in with their Linux capture sample code

GUID InterfaceClass = {0xA0B880F6, 0xD6A5, 0x4700, {0xA8, 0xEA, 0x22, 0x28, 0x2A, 0xCA, 0x55, 0x87}};
HANDLE CapHandle;
WINUSB_INTERFACE_HANDLE CapUSBHandle;


void OnAreaDraw(uiAreaHandler* handler, uiArea* area, uiAreaDrawParams* params)
{
    if (!WinBitmapInited)
    {
        if (WinBitmap) uiDrawFreeBitmap(WinBitmap);

        WinBitmapInited = true;
        WinBitmap = uiDrawNewBitmap(params->Context, 768, 384, 0);
    }

    if (!WinBitmap) return;
    if (!WinBitmapData) return;

    uiRect rc = {0, 0, 768, 384};

    uiDrawBitmapUpdate(WinBitmap, WinBitmapData);
    uiDrawBitmapDraw(params->Context, WinBitmap, &rc, &rc, 0);
}

void OnAreaMouseEvent(uiAreaHandler* handler, uiArea* area, uiAreaMouseEvent* evt)
{
}

void OnAreaMouseCrossed(uiAreaHandler* handler, uiArea* area, int left)
{
}

void OnAreaDragBroken(uiAreaHandler* handler, uiArea* area)
{
}

int OnAreaKeyEvent(uiAreaHandler* handler, uiArea* area, uiAreaKeyEvent* evt)
{
    return 1;
}

void OnAreaResize(uiAreaHandler* handler, uiArea* area, int width, int height)
{
}


void Init()
{
    printf("MelonCap init\n");

    HDEVINFO devinfo = SetupDiGetClassDevsW(&InterfaceClass, NULL, NULL, DIGCF_DEVICEINTERFACE|DIGCF_PRESENT);
    if (devinfo == INVALID_HANDLE_VALUE) return;

    int member = 0;
    bool good = false;
    for (;;)
    {
        SP_DEVICE_INTERFACE_DATA interfacedata;
        memset(&interfacedata, 0, sizeof(interfacedata));
        interfacedata.cbSize = sizeof(interfacedata);

        BOOL ret = SetupDiEnumDeviceInterfaces(devinfo, NULL, &InterfaceClass, member, &interfacedata);
        if (!ret)
        {
            printf("found %d interfaces\n", member);
            break;
        }

        DWORD requiredsize = 0;
        SetupDiGetDeviceInterfaceDetailW(devinfo, &interfacedata, NULL, NULL, &requiredsize, NULL);
        printf("%d: required size %d\n", member, requiredsize);

        PSP_DEVICE_INTERFACE_DETAIL_DATA_W interfacedetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA_W)new u8[requiredsize];
        interfacedetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA_W);
        ret = SetupDiGetDeviceInterfaceDetailW(devinfo, &interfacedata, interfacedetail, requiredsize, NULL, NULL);
        if (ret)
        {
            printf("got interface detail: path=%S\n", interfacedetail->DevicePath);
            HANDLE file = CreateFileW(interfacedetail->DevicePath, GENERIC_READ|GENERIC_WRITE,
                                      FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
                                      FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
            if (file != INVALID_HANDLE_VALUE)
            {
                WINUSB_INTERFACE_HANDLE usbhandle;
                ret = WinUsb_Initialize(file, &usbhandle);
                if (ret)
                {
                    int val;
                    val = 0x1E;
                    WinUsb_SetPipePolicy(usbhandle, 0x00, PIPE_TRANSFER_TIMEOUT, 4, &val);
                    val = 0x32;
                    WinUsb_SetPipePolicy(usbhandle, 0x82, PIPE_TRANSFER_TIMEOUT, 4, &val);
                    val = 0x01;
                    WinUsb_SetPipePolicy(usbhandle, 0x82, RAW_IO, 1, &val);

                    printf("looking good\n");
                    good = true;

                    CapHandle = file;
                    CapUSBHandle = usbhandle;
                }
                else
                    CloseHandle(file);
            }
        }

        delete[] (u8*)interfacedetail;

        if (good) break;

        member++;
    }

    SetupDiDestroyDeviceInfoList(devinfo);


    AreaHandler.Draw = OnAreaDraw;
    AreaHandler.MouseEvent = OnAreaMouseEvent;
    AreaHandler.MouseCrossed = OnAreaMouseCrossed;
    AreaHandler.DragBroken = OnAreaDragBroken;
    AreaHandler.KeyEvent = OnAreaKeyEvent;
    AreaHandler.Resize = OnAreaResize;

    WinBitmapInited = false;
    WinBitmapData = new u32[768*384];

    Window = uiNewWindow("melonDS - topnotch pixel checker", 768, 384, 0, 0, 0);
    Area = uiNewArea(&AreaHandler);
    uiWindowSetChild(Window, uiControl(Area));

    uiControlShow(uiControl(Window));
}

void DeInit()
{
    uiControlDestroy(uiControl(Window));
    uiDrawFreeBitmap(WinBitmap);
    WinBitmapInited = false;
    delete[] WinBitmapData;

    WinUsb_Free(CapUSBHandle);
    CloseHandle(CapHandle);
}


int VendorIn(u8 req, u16 len, u8* buf)
{
    WINUSB_SETUP_PACKET pkt;
    pkt.RequestType = 0xC0; // device to host
    pkt.Request = req;
    pkt.Value = 0; // ?????
    pkt.Index = 0;
    pkt.Length = len;

    ULONG ret = 0;
    BOOL res = WinUsb_ControlTransfer(CapUSBHandle, pkt, buf, len, &ret, NULL);
    if (!res) return -1;
    return ret;
}

int VendorOut(u8 req, u16 val, u16 len, u8* buf)
{
    WINUSB_SETUP_PACKET pkt;
    pkt.RequestType = 0x40; // host to device
    pkt.Request = req;
    pkt.Value = val;
    pkt.Index = 0;
    pkt.Length = len;

    ULONG ret = 0;
    BOOL res = WinUsb_ControlTransfer(CapUSBHandle, pkt, buf, len, &ret, NULL);
    if (!res) return -1;
    return ret;
}

int BulkIn(u8* buf, u32 len)
{
    ULONG ret = 0;
    BOOL res = WinUsb_ReadPipe(CapUSBHandle, 0x82, buf, len, &ret, NULL);
    if (!res) return -1;
    return ret;
}


u32 ConvertColor(u16 col)
{
    u32 b = col & 0x001F;
    u32 g = (col & 0x07E0) >> 5;
    u32 r = (col & 0xF800) >> 11;

    u32 ret = 0xFF000000;
    ret |= ((r << 3) | (r >> 2)) << 16;
    ret |= ((g << 2) | (g >> 4)) << 8;
    ret |= (b << 3) | (b >> 2);
    return ret;
}

void CaptureFrame()
{
    u32 ret;
    u8 derp;
    u32 framelen = 256*384*2;
    u16 frame[framelen/2];
    u32 framepos = 0;
    u8 frameinfo[64];

    ret = VendorOut(0x30, 0, 0, &derp);
    if (ret < 0) return;

    int tries = 0;
    while (framepos < framelen)
    {
        ret = BulkIn((u8*)&frame[framepos/2], framelen-framepos);
        if (ret < 0) break;
        if (ret == 0)
        {
            tries++;
            if (tries >= 100) break;
            continue;
        }
        framepos += ret;
    }

    ret = VendorIn(0x30, 64, frameinfo);
    if (ret < 0) return;
    if ((frameinfo[0] & 0x03) != 0x03) return;
    if (!frameinfo[52]) return;

    u16* in = &frame[0];
    u32* out = &WinBitmapData[256];

    for (int y = 0; y < 384; y++)
    {
        u32* out = &WinBitmapData[((y/2)*768) + ((y&1)*128) + 256];

        if (!(frameinfo[y>>3] & (1<<(y&7))))
        {
            continue;
        }

        for (int x = 0; x < 256/2; x++)
        {
            out[0] = ConvertColor(in[1]);
            out[768*192] = ConvertColor(in[0]);
            out++;
            in += 2;
        }
    }
}

void Update()
{
    // melonDS output

    int frontbuf = GPU::FrontBuffer;

    u32* topbuf = GPU::Framebuffer[frontbuf][0];
    if (topbuf)
    {
        for (int y = 0; y < 192; y++)
        {
            memcpy(&WinBitmapData[y*768], &topbuf[y*256], 256*4);
        }
    }

    u32* botbuf = GPU::Framebuffer[frontbuf][1];
    if (botbuf)
    {
        for (int y = 0; y < 192; y++)
        {
            memcpy(&WinBitmapData[(y+192)*768], &botbuf[y*256], 256*4);
        }
    }

    // DS capture

    CaptureFrame();

    // compare

    for (int y = 0; y < 384; y++)
    {
        for (int x = 0; x < 256; x++)
        {
            u32 colA = WinBitmapData[(y*768) + x + 0];
            u32 colB = WinBitmapData[(y*768) + x + 256];

            // best we get from the capture card is RGB565
            // so we'll ignore the lower bits
            const u32 mask = 0x00F8FCF8;
            colA &= mask;
            colB &= mask;

            if (colA == colB) WinBitmapData[(y*768) + x + 512] = 0xFF00FF00;
            else              WinBitmapData[(y*768) + x + 512] = 0xFFFF0000;
        }
    }

    uiAreaQueueRedrawAll(Area);
}

}