#define DEF_USE_DIRECT_X_VERSION_9

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

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

// Direct 3D
#ifdef DEF_USE_DIRECT_X_VERSION_9
#include <d3d9.h>
#include <d3dx9math.h>
#include <d3d9types.h>
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
#else
#include <d3d8.h>
#include <d3dx8math.h>
#pragma comment(lib, "d3d8.lib")
#pragma comment(lib, "d3dx8.lib")
#endif // DEF_USE_DIRECT_X_VERSION_9

// Defines
#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 SetupMatrices();
void RenderScene();

#define D3DFVF_MYVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

struct MYVERTEX
{
	FLOAT x, y ,z;
	D3DCOLOR color;
	FLOAT tu, tv;
};

#define DEF_WIDTH			(1024)
#define DEF_HEIGHT			(768)

// 1024 x 768

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

// Globals
#ifdef DEF_USE_DIRECT_X_VERSION_9
LPDIRECT3D9 g_PDIRECT3D = NULL;
LPDIRECT3DDEVICE9 g_PDIRECT3DDEVICE = NULL;
LPDIRECT3DVERTEXBUFFER9 g_PDIRECT3DVERTEXBUFFER = NULL;
LPDIRECT3DTEXTURE9 g_pTexture = NULL;
#else
LPDIRECT3D8 g_PDIRECT3D = NULL;
LPDIRECT3DDEVICE8 g_PDIRECT3DDEVICE = NULL;
LPDIRECT3DVERTEXBUFFER8 g_PDIRECT3DVERTEXBUFFER = NULL;
LPDIRECT3DTEXTURE8 g_pTexture = NULL;
#endif // DEF_USE_DIRECT_X_VERSION_9

D3DTEXTUREFILTERTYPE CurrentFilter;

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 RECT g_rcUpdate = { 0 };

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 (DirectX)"), 
					 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);

    CurrentFilter = D3DTEXF_NONE;

	// Direct 3D Initialization
	{
		D3DPRESENT_PARAMETERS present_parameters = { 0 };

#ifdef DEF_USE_DIRECT_X_VERSION_9
		if (NULL == (g_PDIRECT3D = Direct3DCreate9(D3D_SDK_VERSION)))
			return FALSE;
#else
		if (NULL == (g_PDIRECT3D = Direct3DCreate8(D3D_SDK_VERSION)))
			return FALSE;
#endif // DEF_USE_DIRECT_X_VERSION_9

		D3DDISPLAYMODE display_mode;

		if (FAILED(g_PDIRECT3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &display_mode)))
			return FALSE;

		present_parameters.Windowed = TRUE;
		present_parameters.BackBufferFormat = display_mode.Format;
		present_parameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
		present_parameters.AutoDepthStencilFormat = D3DFMT_D16;
		present_parameters.EnableAutoDepthStencil = TRUE;

		if (FAILED(g_PDIRECT3D->CreateDevice(D3DADAPTER_DEFAULT, 
											  D3DDEVTYPE_HAL, 
											  hWnd, 
											  D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
											  &present_parameters, 
											  &g_PDIRECT3DDEVICE)))
			if (FAILED(g_PDIRECT3D->CreateDevice(D3DADAPTER_DEFAULT, 
												  D3DDEVTYPE_REF, 
												  hWnd, 
												  D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
												  &present_parameters, 
												  &g_PDIRECT3DDEVICE)))
				return FALSE;

		g_PDIRECT3DDEVICE->SetRenderState(D3DRS_ZENABLE, TRUE);
		g_PDIRECT3DDEVICE->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
		g_PDIRECT3DDEVICE->SetRenderState(D3DRS_LIGHTING, FALSE);
	}

	// Scene
	{
		const float a = 5;

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

		const MYVERTEX Vertices[] = 
		{
			E,	A,	D,
			E,	B,	A,
			E,	C,	B,
			E,	D,	C,
		};

#ifdef DEF_USE_DIRECT_X_VERSION_9
		if (FAILED(g_PDIRECT3DDEVICE->CreateVertexBuffer(sizeof(Vertices), 
														  0, 
														  D3DFVF_MYVERTEX, 
														  D3DPOOL_DEFAULT, 
														  &g_PDIRECT3DVERTEXBUFFER, 
														  NULL)))
			return FALSE;
#else
		if (FAILED(g_PDIRECT3DDEVICE->CreateVertexBuffer(sizeof(Vertices), 
														  0, 
														  D3DFVF_MYVERTEX, 
														  D3DPOOL_DEFAULT, 
														  &g_PDIRECT3DVERTEXBUFFER)))
			return FALSE;
#endif // DEF_USE_DIRECT_X_VERSION_9

		VOID* pVertices;

#ifdef DEF_USE_DIRECT_X_VERSION_9
		if (FAILED(g_PDIRECT3DVERTEXBUFFER->Lock(0, sizeof(Vertices), (VOID**)&pVertices, 0)))
			return FALSE;
#else
		if (FAILED(g_PDIRECT3DVERTEXBUFFER->Lock(0, sizeof(Vertices), (BYTE**)&pVertices, 0)))
			return FALSE;
#endif // DEF_USE_DIRECT_X_VERSION_9

		CopyMemory(pVertices, Vertices, sizeof(Vertices));

		g_PDIRECT3DVERTEXBUFFER->Unlock();
	}

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

	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("SWF1"), _T("SWF"));
	// Play
	FPC_Play(g_hwndFlashPlayerControl);

	FPCSetEventListener(g_hwndFlashPlayerControl, &FPCListener, 0);

    FPC_PutBackgroundColor(g_hwndFlashPlayerControl, 0x00ffffd0);

	MSG msg = { 0 };

	while (WM_QUIT != msg.message)
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
			RenderScene();

	// Cleanup
	if (NULL != g_PDIRECT3DVERTEXBUFFER)
		g_PDIRECT3DVERTEXBUFFER->Release();

	if (NULL != g_PDIRECT3DDEVICE)
		g_PDIRECT3DDEVICE->Release();

	if (NULL != g_PDIRECT3D)
		g_PDIRECT3D->Release();

	//
	FPC_UnloadCode(g_hFPC);

	UnregisterClass(DEF_MAIN_WND_CLASS_NAME, hInstance);

	return 0;
}

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);
}

void SetupMatrices()
{
	static DWORD g_dwStartTime = 0;

	if (0 == g_dwStartTime)
		g_dwStartTime = GetTickCount();

	D3DXMATRIX WorldMatrix;
	D3DXMatrixIdentity(&WorldMatrix);
	D3DXMatrixRotationZ(&WorldMatrix, (GetTickCount() - g_dwStartTime) / 1024.0f);
	g_PDIRECT3DDEVICE->SetTransform(D3DTS_WORLD, &WorldMatrix);

	D3DXMATRIX ViewMatrix;
	D3DXMatrixLookAtLH(&ViewMatrix, 
					   &D3DXVECTOR3(5.0f, 5.0f, 6.5f), 
					   &D3DXVECTOR3(0.0f, 0.0f, 1.0f), 
					   &D3DXVECTOR3(0.0f, 0.0f, 1.0f));
	g_PDIRECT3DDEVICE->SetTransform(D3DTS_VIEW, &ViewMatrix);

	D3DXMATRIX ProjectionMatrix;
	D3DXMatrixPerspectiveFovLH(&ProjectionMatrix, D3DX_PI / 3, 1.0f, 1.0f, 100.0f);
	g_PDIRECT3DDEVICE->SetTransform(D3DTS_PROJECTION, &ProjectionMatrix );
}

void RenderScene()
{
	g_PDIRECT3DDEVICE->Clear(0, 
							  NULL, 
							  D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 
							  D3DCOLOR_RGBA(100, 100, 100, 50), 
							  1.0, 
							  0);

	g_PDIRECT3DDEVICE->BeginScene();

	SetupMatrices();

#ifdef DEF_USE_DIRECT_X_VERSION_9
	LPDIRECT3DTEXTURE9 pTexture = g_pTexture;
#else
	LPDIRECT3DTEXTURE8 pTexture = g_pTexture;
#endif // DEF_USE_DIRECT_X_VERSION_9

	g_PDIRECT3DDEVICE->SetTexture(0, pTexture);

#ifdef DEF_USE_DIRECT_X_VERSION_9
#else
	g_PDIRECT3DDEVICE->SetTextureStageState(0, D3DTSS_MAGFILTER, CurrentFilter);
	g_PDIRECT3DDEVICE->SetTextureStageState(0, D3DTSS_MINFILTER, CurrentFilter);
	g_PDIRECT3DDEVICE->SetTextureStageState(0, D3DTSS_MIPFILTER, CurrentFilter);
#endif // DEF_USE_DIRECT_X_VERSION_9

#ifdef DEF_USE_DIRECT_X_VERSION_9
	g_PDIRECT3DDEVICE->SetStreamSource(0, g_PDIRECT3DVERTEXBUFFER, 0, sizeof(MYVERTEX));
#else
	g_PDIRECT3DDEVICE->SetStreamSource(0, g_PDIRECT3DVERTEXBUFFER, sizeof(MYVERTEX));
#endif // DEF_USE_DIRECT_X_VERSION_9

#ifdef DEF_USE_DIRECT_X_VERSION_9
	g_PDIRECT3DDEVICE->SetFVF(D3DFVF_MYVERTEX);
#else
	g_PDIRECT3DDEVICE->SetVertexShader(D3DFVF_MYVERTEX);
#endif // DEF_USE_DIRECT_X_VERSION_9

	//Нижнюю грань не рендерим
	g_PDIRECT3DDEVICE->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 4);

	g_PDIRECT3DDEVICE->EndScene();

	g_PDIRECT3DDEVICE->Present(NULL, NULL, NULL, NULL);

	//if (NULL != pTexture)
	//	pTexture->Release();
}

void UpdateTexture()
{
	if (NULL == g_pTexture)
		D3DXCreateTexture(g_PDIRECT3DDEVICE, 
						DEF_WIDTH, 
						DEF_HEIGHT, 
						1, 
						0, 
						D3DFMT_A8R8G8B8, 
						D3DPOOL_MANAGED, 
						&g_pTexture);

	D3DSURFACE_DESC d3dsd;
	g_pTexture->GetLevelDesc(0, &d3dsd);

	//// If size is changed...
	//MoveWindow(g_hwndFlashPlayerControl, 0, 0, d3dsd.Width, d3dsd.Height, TRUE);

	// Size
	SIZE size = { d3dsd.Width, d3dsd.Height };

	D3DLOCKED_RECT rcLockedRect = { 0 };
	RECT rc = g_rcUpdate;

	g_pTexture->LockRect(0, &rcLockedRect, &rc, D3DLOCK_DISCARD);
	{
        if (NULL != g_lpPixels)
        {
            BYTE* pTextureBits = (BYTE*)rcLockedRect.pBits;

			DWORD dwSizeOfLineInDWORDs = DEF_WIDTH;

            DWORD* pBitmapBitsStart = g_lpPixels + (DEF_HEIGHT - rc.bottom) * dwSizeOfLineInDWORDs + rc.left;

            BYTE* pLineTextureBits = pTextureBits + rcLockedRect.Pitch * (rc.bottom - rc.top - 1);

			for (int j = rc.top; j < rc.bottom; j++)
            {
	            DWORD* pPixels = (DWORD*)pLineTextureBits;

				DWORD* pBitmapBits = pBitmapBitsStart;

/*
				for (int i = rc.left; i < rc.right; i++)
	            {
		            *pPixels = *pBitmapBits;

		            pPixels++;
		            pBitmapBits++;
	            }
*/
				CopyMemory(pPixels, pBitmapBits, (rc.right - rc.left) * sizeof(DWORD));

	            pLineTextureBits -= rcLockedRect.Pitch;
				pBitmapBitsStart += dwSizeOfLineInDWORDs;
            }
        }
	}
	g_pTexture->UnlockRect(0);
}

static
void WINAPI FPCListener(HWND hwndFlashPlayerControl, LPARAM lParam, NMHDR* pNMHDR)
{
	if (FPCN_UPDATE_RECT == pNMHDR->code)
	{
		g_rcUpdate = ((SFPCNUpdateRect*)pNMHDR)->rc;

		if (g_rcUpdate.right > DEF_WIDTH)
			g_rcUpdate.right = DEF_WIDTH;
		if (g_rcUpdate.bottom > DEF_HEIGHT)
			g_rcUpdate.bottom = DEF_HEIGHT;

        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);

			UpdateTexture();

			RenderScene();
		}
        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));

		// 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

		UpdateTexture();

		RenderScene();
	}
}
