/**
 * Live2D SDK for DirectX 
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */

#include <windows.h>
#include <d3d9.h>
#include "Live2D.h"
#include "Live2DModelD3D.h"
#include "motion/MotionQueueManager.h"
#include "motion/Live2DMotion.h"
#include "util/UtSystem.h"
#include <vector>
#include <string>
#include <sstream>
#include <time.h>

using namespace live2d;
using namespace std;

/***********************************************************
	必要なライブラリをリンクする
************************************************************/
#pragma comment( lib, "d3d9.lib" )
#pragma comment( lib, "d3dx9d.lib" )

/***********************************************************
	グローバル変数(アプリケーション関連)
************************************************************/
#define		ENABLE_CLIPPING_FEATURE			1		// Cubism SDK2.1クリッピング機能の有効化フラグ

const int NUM_MODELS = 10;	// 表示されるモデルの数
const int INTERVAL = 1000;

LPDIRECT3D9				g_pDirect3D = NULL;
LPDIRECT3DDEVICE9		g_pDirect3D_Device = NULL;
D3DPRESENT_PARAMETERS	g_D3DPP;				//  D3DDeviceの設定
LPDIRECT3DTEXTURE9		g_pMaskTexture       = NULL; //  クリッピングマスク用のテクスチャ
LPDIRECT3DSURFACE9		g_pMaskBufferSurface = NULL; //  クリッピングマスク用のサーフェース
LPD3DXFONT				g_pFont      = NULL;
#define FAILED(hr) (((HRESULT)(hr)) < 0)
#define SAFE_RELEASE(x)  { if(x) { (x)->Release(); (x)=NULL; } }

// Live2D
vector<Live2DModelD3D*> models;
vector<int> modelPositions;
MotionQueueManager* motionMgr;
Live2DMotion* motion;

// FPSの計算用
int frame;
float frameRate = 0.0f;
long oldTime;
char strFPS[100];

HDC hdc;
HWND _hwnd;
HDC     hMemDC;
HBITMAP hMemBMP;

LRESULT WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
VOID CalcFPS();
VOID RenderLive2D();
HRESULT SetupClippingMask();
VOID CleanupClippingMask();

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine,
                   int nShow)
{
	MSG msg;
	
	WNDCLASSEX wc = {sizeof(WNDCLASSEX), CS_VREDRAW|CS_HREDRAW|CS_OWNDC, 
					WndProc, 0, 0, hInstance, NULL, NULL, (HBRUSH)(COLOR_WINDOW+1), 
					NULL, "DX9_TUTORIAL1_CLASS", NULL};

	RegisterClassEx(&wc);
	
	// ウィンドウ作成
	HWND hMainWnd = CreateWindow("DX9_TUTORIAL1_CLASS",
								"Live2D SDK Benchmark", 
								WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 800,
								NULL, NULL, hInstance, NULL);
	
	// DirectXの初期化
	g_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);
	memset(&g_D3DPP, 0, sizeof(D3DPRESENT_PARAMETERS));
	
	g_D3DPP.Windowed = TRUE;
	g_D3DPP.SwapEffect = D3DSWAPEFFECT_DISCARD;
	
	g_pDirect3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hMainWnd,
							D3DCREATE_SOFTWARE_VERTEXPROCESSING, &g_D3DPP,
							&g_pDirect3D_Device);

	// クリッピングモデル向けに設定
	SetupClippingMask();

	//  ビューポートの設定
	D3DVIEWPORT9 vp;
	vp.X		= 0;
	vp.Y		= 0;
	vp.Width	= 800;
	vp.Height	= 800;
	vp.MinZ		= 0.0f;
	vp.MaxZ		= 1.0f;
	g_pDirect3D_Device->SetViewport(&vp);


	// ウィンドウの表示
	ShowWindow(hMainWnd, nShow);
	UpdateWindow(hMainWnd);


	D3DXCreateFont( g_pDirect3D_Device , 20 , 10 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , NULL , &g_pFont );
	
	// Live2Dの初期化
	Live2D::init();

	// デバイスの設定(モデル間で共有される)
	live2d::Live2DModelD3D::setDevice(g_pDirect3D_Device);

#if ENABLE_CLIPPING_FEATURE
	// クリッピングマスクの設定(モデル間で共有される)
	live2d::Live2DModelD3D::setMaskTexture(g_pMaskTexture);
	live2d::Live2DModelD3D::setMaskSurface(g_pMaskBufferSurface);
#endif	
	
	oldTime= UtSystem::getTimeMSec();
    frame = 0;

	const char* MODEL="res\\epsilon\\Epsilon2.1.moc";
	const char* MOTION="res\\epsilon\\Epsilon2.1_idle_01.mtn";
	const LPCWSTR TEXTURES[] = { 
		L"res\\epsilon\\Epsilon2.1.2048\\texture_00.png",
		NULL ,
	} ;

	// テクスチャのロード
	vector<LPDIRECT3DTEXTURE9> textures;
	for( int i = 0 ; i < 1000 ; i++ ){
		if( ! TEXTURES[i] ) break ;

		LPDIRECT3DTEXTURE9 tex ;
		
		// テクスチャ画像をDirextXでの表示用に変換
		if( FAILED( D3DXCreateTextureFromFileExW( g_pDirect3D_Device
					, TEXTURES[i]
					, 0	//width 
					, 0	//height
					, 0	//mipmap //( 0なら完全なミップマップチェーン）
					, 0	//Usage
					, D3DFMT_A8R8G8B8                
					, D3DPOOL_MANAGED
					, D3DX_FILTER_LINEAR
					, D3DX_FILTER_BOX
					, 0			  
					, NULL
					, NULL
					, &tex ) ) )
		{
			live2d::UtDebug::print( "Failed create texture" );
			return FALSE ;
		}
		else
		{
			textures.push_back(tex);
		}
	}

	srand((unsigned)time(NULL));

	for (int i = 0; i < NUM_MODELS; i++)
	{
		Live2DModelD3D* live2DModel = Live2DModelD3D::loadModel(MODEL) ;

		
		models.push_back(live2DModel);
		
		for( int i = 0 ; TEXTURES[i] != NULL ; i++ ){
			live2DModel->setTexture( i , textures[i] ) ;// テクスチャとモデルを結びつける
		}

		// モデルの描画位置
		modelPositions.push_back(rand()%(2001)-1000);
	}

	motionMgr=new MotionQueueManager();

	motion=Live2DMotion::loadMotion(MOTION);

	// メッセージ・ループ
	do
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			// アイドル処理
			CalcFPS();
			RenderLive2D();
		}
	} while (msg.message != WM_QUIT);

	CleanupClippingMask();
	g_pDirect3D_Device->Release();
	g_pDirect3D->Release();
	delete motion;
	delete motionMgr;
	for (int i = 0; i < NUM_MODELS; i++)
	{
		delete models[i];
	}
	live2d::Live2D::dispose();
	return(0);
}
 

LRESULT WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_CREATE:
		hdc = GetDC(hwnd);
		if ( hdc != NULL )
		{
			hMemDC = CreateCompatibleDC( hdc );
			hMemBMP = CreateCompatibleBitmap( hdc, 300, 200);
			SelectObject( hMemDC, hMemBMP );

			SelectObject(hMemDC, GetStockObject(NULL_PEN));
			SelectObject(hMemDC, CreateSolidBrush(RGB(0xFF,0xFF,0xFF)));
			Rectangle(hMemDC, 0,0,300,200);
			DeleteObject(SelectObject(hMemDC,GetStockObject(WHITE_BRUSH)));
		}
		
        ReleaseDC(hwnd, hdc);


		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		
		if ( g_pFont != NULL ) g_pFont->Release();
		break;
		
	case WM_PAINT:
		_hwnd = hwnd;
		hdc = GetDC(_hwnd);
		break;
	}
	
	return(DefWindowProc(hwnd, msg, wParam, lParam));
}


// FPSの計算
VOID CalcFPS()
{
	frame++;
	long time = UtSystem::getTimeMSec() - oldTime;
	if (time >= INTERVAL) {
		frameRate = ((float)(frame) / time)*1000;
		sprintf(strFPS, "FPS:%f", frameRate);

		frame = 0;
		oldTime = UtSystem::getTimeMSec();
	}
}


// モデルの描画
VOID RenderLive2D()
{
	// モデルが初期化されていなければ何もしない
	if(models.size() == 0) return;

	g_pDirect3D_Device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 255, 255), 1.0f, 0);
	g_pDirect3D_Device->BeginScene();
	
	// Set up world matrix
	D3DXMATRIXA16 matWorld;
	D3DXMatrixIdentity( &matWorld );

	D3DXMATRIX Ortho2D;     
	D3DXMATRIX Identity;
	
	int w , h ;
	w = 800 ;
	h = 800 ;
	
	if (motionMgr->isFinished())
	{
		motionMgr->startMotion(motion,false);
	}

	
	for (int i = 0; i < NUM_MODELS; i++)
	{
		Live2DModelD3D* live2DModel=models[i];

		float modelWidth = live2DModel->getModelImpl()->getCanvasWidth() ;
		float modelHeight = live2DModel->getModelImpl()->getCanvasHeight() ;

		D3DXMatrixOrthoLH(&Ortho2D, modelHeight ,- modelHeight *h/w, -1.0f, 1.0f);
		D3DXMatrixIdentity(&Identity);
		
		g_pDirect3D_Device->SetTransform(D3DTS_PROJECTION, &Ortho2D);
		g_pDirect3D_Device->SetTransform(D3DTS_WORLD, &Identity);
		g_pDirect3D_Device->SetTransform(D3DTS_VIEW , &Identity);
		
		
		// --- 座標変換 ---
		// センタリングと上下逆転が必要
		D3DXMATRIXA16 world, scale, trans ;
		g_pDirect3D_Device->GetTransform(D3DTS_WORLD, &world);
		D3DXMatrixTranslation(&trans , -modelWidth/2+modelPositions[i], -modelHeight/2, 0) ;
		world = trans *world;

		g_pDirect3D_Device->SetTransform(D3DTS_WORLD, &world);


		live2DModel->setDevice(g_pDirect3D_Device) ;

		motionMgr->updateParam(live2DModel);
		live2DModel->update() ;
		live2DModel->draw() ;
	}

	// FPSの描画
	RECT rc;
	rc.left   = 10;
	rc.top    = 10;
	rc.right  = 640;
	rc.bottom = 320;
	g_pFont->DrawText( NULL , strFPS , -1 , &rc , NULL , 0xFF333333);

	g_pDirect3D_Device->EndScene();
	g_pDirect3D_Device->Present(NULL, NULL, NULL, NULL);

	return;
}

/************************************************************
	Setup Clipping Mask
************************************************************/
HRESULT SetupClippingMask()
{
#if ENABLE_CLIPPING_FEATURE
	// バックバッファと同じサイズでかつアルファ値のみのテクスチャを作成する
	HRESULT hr = D3DXCreateTexture(g_pDirect3D_Device, g_D3DPP.BackBufferWidth, g_D3DPP.BackBufferHeight, 
		D3DX_DEFAULT, D3DUSAGE_RENDERTARGET, D3DFMT_A8, D3DPOOL_DEFAULT, &g_pMaskTexture);
	if (FAILED(hr))
	{
		live2d::UtDebug::print( "SetupClippingMask CreateMaskTexture failed" );
		return hr;
	}

	hr = g_pMaskTexture->GetSurfaceLevel(0, &g_pMaskBufferSurface);
	if(FAILED(hr))
	{
		live2d::UtDebug::print( "SetupClippingMask CreateMaskTextureSurface failed" );
		return hr;
	}
#endif
	return S_OK;
}

/************************************************************
	Cleanup Clipping Mask
************************************************************/
VOID CleanupClippingMask()
{
#if ENABLE_CLIPPING_FEATURE
	SAFE_RELEASE(g_pMaskBufferSurface);
	SAFE_RELEASE(g_pMaskTexture);	
#endif
}
