// displayLatestBuffer.cpp : Defines the entry point for the application.

#include "stdafx.h"
#include "displayLatestBuffer.h"
#include <string>
#include <EGrabber.h>

using namespace Euresys;

typedef EGrabber<CallbackOnDemand> MyGrabber;

static const size_t     NB_BUFFERS     = 50;
static const size_t     MAX_LOADSTRING = 100;

// Global Variables:
static HINSTANCE        hInst;                          // Current instance
static TCHAR            szTitle[MAX_LOADSTRING];        // The title bar text
static TCHAR            szWindowClass[MAX_LOADSTRING];  // The main window class name

// Forward declarations of functions included in this code module:
struct AppState;
static ATOM             MyRegisterClass(HINSTANCE hInstance);
static VOID             InitInstance(HINSTANCE, int, AppState *);
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static void             OnPaint(HWND hWnd, AppState *);
static DWORD WINAPI     RefreshThread(LPVOID context);

// Application State
struct AppState {
    EGenTL                  genTL;
    MyGrabber               grabber;
    BITMAPINFO              *bitmapInfo;
    Buffer                  *currentBuffer;
    HWND                    window;
    UINT_PTR                refreshTimer;
    HANDLE                  refreshThread;
    volatile bool           refreshThreadRunning;
    size_t                  width;
    size_t                  height;
    bool                    imageConvertFailed;

    AppState()
    : grabber(genTL), bitmapInfo(NULL), currentBuffer(NULL)
    , window(NULL), refreshTimer(0), refreshThread(NULL)
    , refreshThreadRunning(false), width(0), height(0)
    , imageConvertFailed(false)
    {
        grabber.runScript("./config.js");
        grabber.reallocBuffers(NB_BUFFERS);
        width = grabber.getWidth();
        height = grabber.getHeight();
        bitmapInfo = (LPBITMAPINFO)malloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD));
        bitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        bitmapInfo->bmiHeader.biPlanes = 1;
        bitmapInfo->bmiHeader.biBitCount = 24;
        bitmapInfo->bmiHeader.biCompression = BI_RGB;
        bitmapInfo->bmiHeader.biSizeImage = 0;
        bitmapInfo->bmiHeader.biXPelsPerMeter = 0;
        bitmapInfo->bmiHeader.biYPelsPerMeter = 0;
        bitmapInfo->bmiHeader.biClrUsed = 0;
        bitmapInfo->bmiHeader.biClrImportant = 0;
        bitmapInfo->bmiHeader.biWidth = (LONG)width;
        bitmapInfo->bmiHeader.biHeight = -(LONG)height;
        RGBQUAD* bmiColors = (RGBQUAD*)(bitmapInfo->bmiColors);
        for (size_t index = 0; index < 1; ++index)
        {
            bmiColors[index].rgbBlue = (BYTE)index;
            bmiColors[index].rgbGreen = (BYTE)index;
            bmiColors[index].rgbRed = (BYTE)index;
            bmiColors[index].rgbReserved = 0;
        }
    }

    virtual ~AppState()
    {
        stopRefreshThread();
        if (currentBuffer)
        {
            currentBuffer->push(grabber);
            delete currentBuffer;
        }
        free(bitmapInfo);
    }

    void startRefreshThread()
    {
        refreshThreadRunning = true;
        refreshThread = CreateThread(NULL, 0, RefreshThread, this, 0, NULL);
        refreshTimer = SetTimer(window, 0, 50, (TIMERPROC) NULL);
    }

    void stopRefreshThread()
    {
        if (refreshTimer)
        {
            KillTimer(window, refreshTimer);
            refreshTimer = 0;
        }
        refreshThreadRunning = false;
        if (refreshThread)
        {
            try
            {
                grabber.stop();
                grabber.cancelPop();
            }
            catch (...)
            {
            }
            WaitForSingleObject(refreshThread, INFINITE);
            CloseHandle(refreshThread);
            refreshThread = NULL;
        }
    }
};

int APIENTRY _tWinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPTSTR    lpCmdLine,
                       int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    try
    {
        AppState appState;

        // TODO: Place code here.
        MSG msg;
        HACCEL hAccelTable;

        // Initialize global strings
        LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
        LoadString(hInstance, IDC_DISPLAYLATESTBUFFER, szWindowClass, MAX_LOADSTRING);
        MyRegisterClass(hInstance);

        // Perform application initialization:
        InitInstance(hInstance, nCmdShow, &appState);
        hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DISPLAYLATESTBUFFER));

        // Main message loop:
        BOOL bRet;
        while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
        {
            if (bRet == -1)
            {
                throw std::runtime_error("Handle error in GetMessage");
            }
            if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        return (int) msg.wParam;
    }
    catch (const std::exception &e)
    {
        MessageBoxA(NULL, e.what(), NULL, MB_OK);
        return FALSE;
    }
}



//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;

    wcex.cbSize         = sizeof(WNDCLASSEX);
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_DISPLAYLATESTBUFFER));
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCE(IDC_DISPLAYLATESTBUFFER);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassEx(&wcex);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int, AppState *)
//
//   PURPOSE: Creates main window
//
//   COMMENTS:
//
//        In this function, we create and display the main program window.
//
VOID InitInstance(HINSTANCE hInstance, int nCmdShow, AppState *appState)
{
    std::wstringstream sstitle;
    sstitle << szTitle << " " << appState->width << "x" << appState->height;

    appState->window = CreateWindow(szWindowClass, sstitle.str().c_str(), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, (int)appState->width, (int)appState->height, NULL, NULL, hInstance, appState);

    if (!appState->window)
    {
        throw std::runtime_error("CreateWindow failed");
    }

    appState->grabber.start();
    appState->startRefreshThread();

    ShowWindow(appState->window, nCmdShow);
    UpdateWindow(appState->window);
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND    - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY    - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    AppState *pappState;
    if (message == WM_NCCREATE)
    {
        CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
        pappState = reinterpret_cast<AppState*>(pCreate->lpCreateParams);
        SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pappState); 
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    else
    {
        pappState = reinterpret_cast<AppState*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
        if (pappState == NULL)
        {
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    AppState &appState = *pappState;

    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId    = LOWORD(wParam);
            int wmEvent = HIWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            OnPaint(hWnd, pappState);
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_GETMINMAXINFO:
        if (0 == DefWindowProc(hWnd, message, wParam, lParam))
        {
            MINMAXINFO* pmmi = (MINMAXINFO*)lParam;
            pmmi->ptMaxTrackSize.x = (LONG)appState.width;
            pmmi->ptMaxTrackSize.y = (LONG)appState.height;
            pmmi->ptMinTrackSize.x = (LONG)appState.width;
            pmmi->ptMinTrackSize.y = (LONG)appState.height;
        }
        break;
    case WM_TIMER:
        PostMessage(hWnd, WM_PAINT, 0, 0);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

size_t GetBitmapRowSize(size_t width, int bpp)
{
    // row size must be a multiple of 4 bytes
    size_t rowSize = width * bpp;
    if (rowSize & 3) {
        rowSize |= 3;
        ++rowSize;
    }
    return rowSize;
}

void OnPaint(HWND hWnd, AppState *pappState)
{
    AppState &appState = *pappState;
    if (!appState.currentBuffer || appState.imageConvertFailed)
    {
        return;
    }
    try
    {
        ConvertedBuffer bgr = appState.currentBuffer->convert(appState.grabber, "BGR8");
        unsigned char *imgPtr = reinterpret_cast<unsigned char *>(bgr.getPixels());
        size_t lineDataSize = appState.width * 3;
        size_t rowSize = GetBitmapRowSize(appState.width, 3);
        HDC dc = GetDC(hWnd);
        if (rowSize /= lineDataSize)
        {
            for (size_t h = 0; h < appState.height; ++h) {
                ::SetDIBitsToDevice(dc, 0, (int)h, (DWORD)appState.width, 1, 0, 0, 0, 1, imgPtr, appState.bitmapInfo, DIB_RGB_COLORS);
                imgPtr += lineDataSize;
            }
        }
        else
        {
            ::SetDIBitsToDevice(dc, 0, 0, (DWORD)appState.width, (DWORD)appState.height, 0, 0, 0, (UINT)appState.height, imgPtr, appState.bitmapInfo, DIB_RGB_COLORS);
        }
        ReleaseDC(hWnd, dc);
    }
    catch (const std::exception &e)
    {
        appState.imageConvertFailed = true;
        MessageBoxA(NULL, e.what(), NULL, MB_ICONSTOP | MB_OK | MB_TASKMODAL);
        PostQuitMessage(0);
        return;
    }
    Buffer *p = appState.currentBuffer;
    appState.currentBuffer = NULL;
    p->push(appState.grabber);
    delete p;
}

DWORD WINAPI RefreshThread(LPVOID context)
{
    AppState &appState = *(AppState*)context;
    while (appState.refreshThreadRunning)
    {
        try
        {
            Buffer buffer(appState.grabber.pop());
            if (appState.currentBuffer)
            {
                buffer.push(appState.grabber);
            }
            else
            {
                appState.currentBuffer = new Buffer(buffer);
            }
        }
        catch (const gentl_error &e)
        {
            if (e.gc_err != gc::GC_ERR_ABORT)
            {
                MessageBoxA(NULL, e.what(), NULL, MB_OK);
            }
        }
        catch (const std::exception &e)
        {
            MessageBoxA(NULL, e.what(), NULL, MB_OK);
        }
    }
    return 1;
}
