//=====================================================================================================
// Headers
#include <tchar.h>
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <math.h>

//=====================================================================================================
// FlashPlayerControl
#include "../../../f_in_box/include/f_in_box.h"
#pragma comment(lib, "../../../f_in_box/lib/f_in_box.lib")

//=====================================================================================================
// OpenGl
#include <gl/gl.h>
#include <gl/glu.h>
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")

//=====================================================================================================
// Defines
#define DEF_WIDTH			(128)
#define DEF_HEIGHT			(128)

#define DEF_MAIN_WND_WIDTH  (480)
#define DEF_MAIN_WND_HEIGHT (480)

#define DEF_MAIN_WND_CLASS_NAME				(_T("{E6AA4E57-62BE-4bf3-AF8C-7EE68FA9D2A4}"))

//=====================================================================================================
// Forward declarations
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void UpdateTexture(HWND hFlashPlayerControl);
void SetupModelViewMatrix(float time);
BOOL InitPixelFormat(HDC hdc);
void RenderScene(HDC hdc);
void InitScene();

//=====================================================================================================
// Struct VERTEX
struct VERTEX
{
	FLOAT x, y, z;
	DWORD color;
	FLOAT tu, tv;
};

//=====================================================================================================
// Globals

static VERTEX g_Pyramid[3 * 4];
static BYTE g_Texture[DEF_WIDTH * DEF_HEIGHT * 4];

static HFPC g_hFPC = NULL;
static HWND g_hwndFlashPlayerControl = NULL;
static BOOL g_bTransparent;
static LPDWORD g_lpPixels = NULL;
static SIZE g_size = { 0 };
static BOOL g_bTextureUpdated = FALSE;

static void WINAPI FPCListener(HWND hwndFlashPlayerControl, LPARAM lParam, NMHDR* pNMHDR);

//=====================================================================================================
// Entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
	HWND hWnd;

	WNDCLASS wndclass = { 0 };
	wndclass.lpfnWndProc = &WndProc;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = GetSysColorBrush(COLOR_APPWORKSPACE);
	wndclass.lpszClassName = DEF_MAIN_WND_CLASS_NAME;

	if (!RegisterClass(&wndclass))
		return 0;

	hWnd = CreateWindow(DEF_MAIN_WND_CLASS_NAME, 
						_T("F-IN-BOX Example - FlashTexture (OpenGL)"), 
						WS_SYSMENU | WS_OVERLAPPED | WS_MINIMIZEBOX, 
						GetSystemMetrics(SM_CXSCREEN) / 2 - DEF_MAIN_WND_WIDTH / 2, 
						GetSystemMetrics(SM_CYSCREEN) / 2 - DEF_MAIN_WND_HEIGHT / 2, 
						DEF_MAIN_WND_WIDTH, 
						DEF_MAIN_WND_HEIGHT, 
						NULL, 
						NULL, 
						hInstance, 
						NULL);

    // Load flash ocx
    g_hFPC = FPC_LoadRegisteredOCX();

    if (0 == g_hFPC)
    {
        MessageBox(NULL, _T("FPC_LoadRegisteredOCX() failed"), _T("Error"), MB_OK | MB_ICONSTOP);
        return FALSE;
    }

    g_bTransparent = 
        IDYES == 
            MessageBox(hWnd, 
                       _T("There are two modes of rendering are supported:\n\n1. Full transparent mode. In this mode, you get pixel colors with alpha component. You can create a texture with alpha channel.\n\n2. Simple transparent mode. In this mode, real value of alpha channel is not available. But this mode faster than full transparent mode.\n\nPress Yes if you want to see how full transparent mode works.\nPress No if you want to see how simple transparent mode works."), 
                       _T("Choose mode"), 
                       MB_YESNO | MB_ICONQUESTION);

	g_hwndFlashPlayerControl = 
		FPC_CreateWindow(g_hFPC, 
                         g_bTransparent ? WS_EX_LAYERED : 0, 
                         NULL, 
                         g_bTransparent ? WS_POPUP : WS_CHILD | WS_CLIPSIBLINGS | FPCS_TRANSPARENT, 
                         0, 
                         0, 
                         DEF_WIDTH, 
                         DEF_HEIGHT, 
                         hWnd, 
                         NULL, 
                         NULL, 
                         NULL);

	// Load movie from resource
	FPCPutMovieFromResource(g_hwndFlashPlayerControl, NULL, _T("MOVIE"), _T("SWF"));
	// Play
	FPC_Play(g_hwndFlashPlayerControl);

	FPCSetEventListener(g_hwndFlashPlayerControl, &FPCListener, 0);

    FPC_PutBackgroundColor(g_hwndFlashPlayerControl, 0x00ffffd0);

	// OpenGl initialization
	HDC hDC;
	HGLRC hRC;

	hDC = GetDC(hWnd);
	InitPixelFormat(hDC);

	// Create OpenGl context
	if(hRC = wglCreateContext(hDC))
	{
		wglMakeCurrent(hDC,hRC);
	}
	else return 0;

	InitScene();

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	MSG msg = { 0 };

	while (WM_QUIT != msg.message)
	{
		static const DWORD dwStartTime = GetTickCount();

		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else 
		{
			const float time = (GetTickCount() - dwStartTime) / 1024.f;
			SetupModelViewMatrix(time);
			UpdateTexture(g_hwndFlashPlayerControl);
			RenderScene(hDC);
		}
	}

	// OpenGl cleanup
	wglMakeCurrent(hDC,0);
	wglDeleteContext(hRC);
	ReleaseDC(hWnd,hDC);

	// FlashPlayerControl cleanup
	FPC_UnloadCode(g_hFPC);

    UnregisterClass(DEF_MAIN_WND_CLASS_NAME, hInstance);

	return 0;
}

//=====================================================================================================
// Entry point
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
		case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		}
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

//=====================================================================================================
// InitPixelFormat
BOOL InitPixelFormat(HDC hdc)
{
	int pixel_format;
	PIXELFORMATDESCRIPTOR pfd = {0};

	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER|PFD_DRAW_TO_WINDOW;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 32;

	if(pixel_format = ChoosePixelFormat(hdc,&pfd))
		return SetPixelFormat(hdc,pixel_format,&pfd);

	return FALSE;
}
//=====================================================================================================
// InitScene
void InitScene()
{
	// Render options
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_DEPTH_TEST);

	// Setup projection matrix
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60, 1.f, 1.0f, 100.0);

	// Create pyramid geometry
	VERTEX* vertex = &g_Pyramid[0];
	const float a = 5.f;

	const VERTEX A = { -a / 2,  a / 2, 0, 0xffffffff, 0, 1 };
	const VERTEX B = { -a / 2, -a / 2, 0, 0xffffffff, 0, 0 };
	const VERTEX C = {  a / 2, -a / 2, 0, 0xffffffff, 1, 0 };
	const VERTEX D = {  a / 2,  a / 2, 0, 0xffffffff, 1, 1 };
	const VERTEX E = { 0, 0, a / sqrt(2.0f) , 0xffffffff, .5, .5};

	*(vertex++) = E;	*(vertex++) = A;	*(vertex++) = D;
	*(vertex++) = E;	*(vertex++) = B;	*(vertex++) = A;
	*(vertex++) = E;	*(vertex++) = C;	*(vertex++) = B;
	*(vertex++) = E;	*(vertex++) = D;	*(vertex++) = C;
}

//=====================================================================================================
// SetupModelViewMatrix
void SetupModelViewMatrix(float time)
{
	const float speed = 100;
	const float ds = speed*time;

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(5.0f, 5.0f, 6.5f,0.0f, 0.0f, 0.0f,0.0f, 0.0f, 1.0f);
	glRotatef(ds,0,0,-1);
}

//=====================================================================================================
// UpdateTexture
void UpdateTexture(HWND hFlashPlayerControl)
{
    if (!g_bTextureUpdated)
    {
        if (NULL != g_lpPixels)
        {
	        BYTE* pTextureBits = &g_Texture[0];
	        DWORD* pBitmapBits = g_lpPixels;

	        for (int j = 0; j < DEF_HEIGHT; j++)
	        {
                memcpy(pTextureBits, pBitmapBits, g_size.cx * sizeof(DWORD));
		        pTextureBits += DEF_WIDTH * 4;
		        pBitmapBits += DEF_WIDTH;
	        }
        }

        g_bTextureUpdated = TRUE;
    }
}

//=====================================================================================================
// RenderScene
void RenderScene(HDC hdc)
{
	const GLsizei stride = sizeof(VERTEX);
	const GLsizei color_offset = sizeof(FLOAT)*3;
	const GLsizei texcoord_offset = color_offset+sizeof(DWORD);

	glClearColor(0.4f,0.4f,0.4f,1.f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);

	glTexCoordPointer(2, GL_FLOAT, stride, (char*)(&g_Pyramid[0]) + texcoord_offset);
	glVertexPointer(3, GL_FLOAT, stride, &g_Pyramid[0]);
	glColorPointer(4, GL_UNSIGNED_BYTE, stride, (LPBYTE)(&g_Pyramid[0]) + color_offset);

	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexImage2D(GL_TEXTURE_2D, 0, 4, DEF_WIDTH, DEF_HEIGHT,
				 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, &g_Texture[0]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	glDrawArrays(GL_TRIANGLES, 0, 12);
	SwapBuffers(hdc);
}

static
void WINAPI FPCListener(HWND hwndFlashPlayerControl, LPARAM lParam, NMHDR* pNMHDR)
{
	if (FPCN_UPDATE_RECT == pNMHDR->code)
	{
        if (g_bTransparent)
        {
		    SFPCGetFrameBitmap FPCGetFrameBitmap = { 0 };
		    ::SendMessage(g_hwndFlashPlayerControl, FPCM_GET_FRAME_BITMAP, 0, (LPARAM)&FPCGetFrameBitmap);
		    HBITMAP hBitmap = FPCGetFrameBitmap.hBitmap;
		    {
			    BITMAP bmp_info;
			    GetObject(hBitmap, sizeof(bmp_info), &bmp_info);

		        RECT rc;
		        GetClientRect(hwndFlashPlayerControl, &rc);

		        if (g_size.cx != rc.right || g_size.cy != rc.bottom)
		        {
			        if (NULL != g_lpPixels)
				        delete[] g_lpPixels;

			        g_size.cx = rc.right;
			        g_size.cy = rc.bottom;

			        g_lpPixels = new DWORD[g_size.cx * g_size.cy];
		        }

		        CopyMemory(g_lpPixels, bmp_info.bmBits, g_size.cx * g_size.cy * sizeof(DWORD));
		    }
		    DeleteObject(hBitmap);

            g_bTextureUpdated = FALSE;
        }
        else
        {
            // FPCN_PAINT will be sent
		    SendMessage(hwndFlashPlayerControl, WM_PAINT, 0, 0);
        }
	}
	else if (FPCN_PAINT == pNMHDR->code)
	{
		SFPCNPaint* pFPCNPaint = (SFPCNPaint*)pNMHDR;
		LPDWORD lpPixels = pFPCNPaint->lpPixels; // <-- pixels buffer

		RECT rc;
		GetClientRect(hwndFlashPlayerControl, &rc);

		if (g_size.cx != rc.right || g_size.cy != rc.bottom)
		{
			if (NULL != g_lpPixels)
				delete[] g_lpPixels;

			g_size.cx = rc.right;
			g_size.cy = rc.bottom;

			g_lpPixels = new DWORD[g_size.cx * g_size.cy];
		}

		CopyMemory(g_lpPixels, lpPixels, g_size.cx * g_size.cy * sizeof(DWORD));

        g_bTextureUpdated = FALSE;

		// You can copy lpPixels into internal buffer or create what you want from this buffer (bitmaps, textures, etc.)
		// Note that lpPixels is temporary and you shouldn't save this pointer for futher using
	}
}
