#include "stdafx.h"

#include "Mix/IO.h"
#include "Mix/HID.h"
#include "Mix/Graphics.h"
#include "Mix/Scene.h"

#include "Utility/Common.h"
#include "Utility/UniversalCameraHelper.h"

#include <vector>

#ifdef _DEBUG
	#include <crtdbg.h>
#endif //_DEBUG

#define SCREEN_WIDTH 1024
#define SCREEN_HEIGHT 768

static const Mix::Vector3 SUN_POS( 0.5f, 1.0f, -0.5f );

static const Float32 CIRCUIT_RADIUS = 9.0f;
static const Float32 BASE_HEIGHT = 1.0f;

static const Float32 MOVE = 0.01f;
static const Float32 HORIZ_RANGE = 0.25f;
static const Float32 VERT_RANGE = 0.75f;

static const Float32 MIN_SPEED = 0.1f;
static const Float32 MAX_SPEED = 10.0f;
static const Float32 VAL_SPEED = 0.1f;

static const Float32 RUDDER_SPEED = 0.01f;
static const Float32 RUDDER_RANGE = 0.4f;
static const Float32 RUDDER_DAMPING = 0.05f;

static const UInt32 NUM_BALL = 16;

struct BALL
{
	Mix::Scene::IActorModel* pModel;
	Mix::Vector2 offset;
	Float32 pos;
	Float32 speed;
};

int APIENTRY _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow )
{
#ifdef _DEBUG
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif //_DEBUG

	Mix::ENGINE_CONFIG engineConfig;

	engineConfig.flags = Mix::EC_IO_NARROW | Mix::EC_KEYBOARD | Mix::EC_MOUSE | Mix::EC_GRAPHICS | Mix::EC_SCENE;
	engineConfig.pRootDirectoryPath = Utility::GetRootDirectoryPath();
	engineConfig.pPluginDirectoryPath = Utility::GetPluginsDirectoryPath();
	engineConfig.pUserDirectoryPath = Utility::GetUserDirectoryPath( L"Scene\\PostEffect\\CameraMotionBlur" );
	engineConfig.pCaption = L"Scene - PostEffect ( CameraMotionBlur )";
	engineConfig.targetSize = Mix::Point( SCREEN_WIDTH, SCREEN_HEIGHT );
	engineConfig.shaderModel = Mix::Graphics::SHADER_MODEL_3;

	if( Mix::Initialize( engineConfig ) == True )
	{
		Mix::IEngine* pEngine = Mix::GetEnginePtr();

		Mix::IO::IManager* pIOMgr = Mix::IO::GetManagerPtr();
		Mix::HID::IManager* pInputMgr = Mix::HID::GetManagerPtr();
		Mix::Graphics::IManager* pGraphicsMgr = Mix::Graphics::GetManagerPtr();
		Mix::Scene::IManager* pSceneMgr = Mix::Scene::GetManagerPtr();

		Mix::HID::IMouse* pMouse = NULL;
		Mix::HID::IKeyboard* pKeyboard = NULL;

		Mix::Graphics::IDevice* pGraphicsDev = NULL;
		Mix::Graphics::Utility::IFont* pFontS = NULL;
		Mix::Graphics::Utility::IFont* pFontL = NULL;
		Mix::Graphics::Utility::ICanvasRenderer* pCanvasRenderer = NULL;

		Mix::Scene::IEffectPackage* pSceneEffectPackage = NULL;
		Mix::Scene::IRenderer* pSceneRenderer = NULL;
		Mix::Scene::IUniversalCamera* pCamera = NULL;
		Mix::Scene::ISpotLight* pSpotLight = NULL;

		std::vector<BALL> balls;

		Mix::StringW tempStr;

		////////////////////////////////////////////////////////////////////////////////////////////////////

		pIOMgr->MountDirectory( L"Data" );

		////////////////////////////////////////////////////////////////////////////////////////////////////

		pInputMgr->GetMouse( &pMouse );
		pInputMgr->GetKeyboard( &pKeyboard );

		////////////////////////////////////////////////////////////////////////////////////////////////////

		pGraphicsMgr->GetDevice( &pGraphicsDev );

		pGraphicsMgr->CreateFontFromFile( L"Data\\Font\\mspg_12_200.fnt", &pFontS );
		pGraphicsMgr->CreateFontFromFile( L"Data\\Font\\mspg_16_400.fnt", &pFontL );

		if( pGraphicsMgr->CreateCanvasRenderer( &pCanvasRenderer, L"Sample" ) == True )
		{
			pCanvasRenderer->SetFont( pFontL );
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////

		if( pSceneMgr->CreateEffectPackage( L"Data\\", L"effects", &pSceneEffectPackage ) == True )
		{
			Mix::Scene::RENDERER_CONFIG config;

			config.caps = Mix::Scene::RCAP_SHADOW_MAPPING | Mix::Scene::RCAP_CHROMATISM;

			if( pSceneMgr->CreateRenderer( pSceneEffectPackage, config, &pSceneRenderer, L"Sample" ) == True )
			{
				Mix::Scene::CAMERA_CONFIG camConfig;
				Mix::Scene::ITerrainModel* pRoomModel = NULL;
				Mix::Scene::IActorModel* pBallModel = NULL;
				Mix::Scene::IDirectionalLight* pDirLight = NULL;

				camConfig.caps = Mix::Scene::RCAP_SHADOW_MAPPING | Mix::Scene::RCAP_CHROMATISM;
				camConfig.targetSize.Set( SCREEN_WIDTH, SCREEN_HEIGHT );
				camConfig.colorTexFormat = Mix::Graphics::FMT_R8G8B8A8;
				camConfig.smDepthTexSize = 2048;
				camConfig.smSmplRes = Mix::Scene::RSMPL_DEFAULT;

				if( pSceneMgr->CreateUniversalCamera( camConfig, &pCamera ) == True )
				{
					Mix::Scene::ICamera::VISIBILITY_SETTINGS visiSettings = pCamera->GetVisiblitySettings();
					Mix::Scene::ICamera::SHADOW_MAPPING_SETTINGS smSettings = pCamera->GetShadowMappingSettings();
					Mix::Scene::ICamera::MOTION_BLUR_SETTINGS mbSettings = pCamera->GetMotionBlurSettings();

					pCamera->SetBackgroundColor( Mix::Vector4( 0.2f, 0.5f, 0.8f, 1.0f ) );
					pCamera->SetProjection( MIX_TO_RAD( 60.0f ), ( Float32 )SCREEN_WIDTH / ( Float32 )SCREEN_HEIGHT, 0.01f, 2000.0f );

					visiSettings.sdwLimitDistance = 10.0f;
					visiSettings.sdwFalloffAmount = 5.0f;
					pCamera->SetVisiblitySettings( visiSettings );

					smSettings.depthBias = -0.89997506f;
					pCamera->SetShadowMappingSettings( smSettings );

					pCamera->SetShadowMappingEnabled( True );

					mbSettings.minLinearVelocity = 0.0f;
					mbSettings.maxLinearVelocity = 0.01f;
					mbSettings.minAngularVelocity = 0.0f;
					mbSettings.maxAngularVelocity = 0.01f;
					mbSettings.amount = 4;
					mbSettings.scale = 1.0f;
					pCamera->SetMotionBlurSettings( mbSettings );
					pCamera->SetMotionBlurEnabled( True );

					pSceneRenderer->AddCamera( pCamera );
				}

				if( pSceneMgr->CreateTerrainModelFromFile( pSceneEffectPackage, L"Data\\Model\\circuit\\land.mtm", &pRoomModel ) == True )
				{
					pSceneRenderer->AddTerrainModel( pRoomModel );
					MIX_RELEASE( pRoomModel );
				}

				if( pSceneMgr->CreateActorModelFromFile( pSceneEffectPackage, L"Data\\Model\\circuit\\ball.mam", &pBallModel ) == True )
				{
					for( UInt32 i = 0; i < NUM_BALL; i++ )
					{
						BALL ball;

						if( pBallModel->Clone( &( ball.pModel ) ) == True )
						{
							Mix::Matrix4x4 rotMat;
							Mix::Matrix4x4 worldMat;

							ball.offset.x = ( Mix::RandF() * 2.0f - 1.0f ) * HORIZ_RANGE;
							ball.offset.y = ( Mix::RandF() * 2.0f - 1.0f ) * VERT_RANGE;
							ball.pos = Mix::RandF() * MIX_2PI;
							ball.speed = Mix::LerpF( MIN_SPEED, MAX_SPEED, Mix::RandF() );
							balls.push_back( ball );

							rotMat.SetRotationY( ball.pos );
							worldMat.SetTranslation( 0.0f, BASE_HEIGHT + ball.offset.y, CIRCUIT_RADIUS + ball.offset.x );
							worldMat *= rotMat;

							ball.pModel->SetWorldMatrix( worldMat );
							ball.pModel->Reset();

							pSceneRenderer->AddActorModel( ball.pModel );
						}
					}

					MIX_RELEASE( pBallModel );
				}

				if( pSceneMgr->CreateDirectionalLight( &pDirLight ) == True )
				{
					pDirLight->SetEnabled( True );
					pDirLight->SetDirection( -SUN_POS.ToNormalize() );

					pSceneRenderer->SetDirectionalLight( pDirLight );

					MIX_RELEASE( pDirLight );
				}

				if( pSceneMgr->CreateSpotLight( &pSpotLight ) == True )
				{
					pSpotLight->SetColor( Mix::Vector4( 1.0f, 0.9f, 0.6f, 1.0f ) );
					pSceneRenderer->AddSpotLight( pSpotLight );
				}

#ifdef _DEBUG
				pSceneRenderer->Debug_SetCanvasRenderer( pCanvasRenderer );
#endif //_DEBUG
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////

		pEngine->ResetFPS();

		Float32 speed = 4.0f;
		Float32 pos = 0.0f;
		Mix::Vector2 bodyOffset = Mix::Vector2::Zero();
		Mix::Vector2 headOffset = Mix::Vector2::Zero();

		while( pEngine->Update() == True )
		{
			Float32 dt = pEngine->GetDT();

			////////////////////////////////////////////////////////////////////////////////////////////////////
			// エフェクト
			////////////////////////////////////////////////////////////////////////////////////////////////////

			if( pKeyboard->GetKeyState( Mix::HID::KEY_SPACE ) & Mix::HID::PRESSED )
			{
				pCamera->SetMotionBlurEnabled( !pCamera->IsMotionBlurEnabled() );
			}

			////////////////////////////////////////////////////////////////////////////////////////////////////
			// 自機
			////////////////////////////////////////////////////////////////////////////////////////////////////
/*
			if( pKeyboard->GetKeyState( Mix::HID::KEY_Z ) & Mix::HID::DOWN )
			{
				Mix::Scene::ICamera::SHADOW_MAPPING_SETTINGS smSettings = pCamera->GetShadowMappingSettings();
				smSettings.depthBias = MIX_CLAMP( smSettings.depthBias - 0.1f, -100.0f, +100.0f );
				pCamera->SetShadowMappingSettings( smSettings );
			}
			else if( pKeyboard->GetKeyState( Mix::HID::KEY_X ) & Mix::HID::DOWN )
			{
				Mix::Scene::ICamera::SHADOW_MAPPING_SETTINGS smSettings = pCamera->GetShadowMappingSettings();
				smSettings.depthBias = MIX_CLAMP( smSettings.depthBias + 0.1f, -100.0f, +100.0f );
				pCamera->SetShadowMappingSettings( smSettings );
			}
*/
			if( pKeyboard->GetKeyState( Mix::HID::KEY_A ) & Mix::HID::DOWN )
			{
				bodyOffset.x = MIX_CLAMP( bodyOffset.x + MOVE, -HORIZ_RANGE, +HORIZ_RANGE );
				headOffset.x = MIX_CLAMP( headOffset.x + RUDDER_SPEED, -RUDDER_RANGE, +RUDDER_RANGE );
			}
			else if( pKeyboard->GetKeyState( Mix::HID::KEY_D ) & Mix::HID::DOWN )
			{
				bodyOffset.x = MIX_CLAMP( bodyOffset.x - MOVE, -HORIZ_RANGE, +HORIZ_RANGE );
				headOffset.x = MIX_CLAMP( headOffset.x - RUDDER_SPEED, -RUDDER_RANGE, +RUDDER_RANGE );
			}

			if( pKeyboard->GetKeyState( Mix::HID::KEY_W ) & Mix::HID::DOWN )
			{
				bodyOffset.y = MIX_CLAMP( bodyOffset.y + MOVE, -VERT_RANGE, +VERT_RANGE );
				headOffset.y = MIX_CLAMP( headOffset.y + RUDDER_SPEED, -RUDDER_RANGE, +RUDDER_RANGE );
			}
			else if( pKeyboard->GetKeyState( Mix::HID::KEY_S ) & Mix::HID::DOWN )
			{
				bodyOffset.y = MIX_CLAMP( bodyOffset.y - MOVE, -VERT_RANGE, +VERT_RANGE );
				headOffset.y = MIX_CLAMP( headOffset.y - RUDDER_SPEED, -RUDDER_RANGE, +RUDDER_RANGE );
			}

			if( pKeyboard->GetKeyState( Mix::HID::KEY_UP ) & Mix::HID::DOWN )
			{
				speed = min( speed + VAL_SPEED, MAX_SPEED );
			}
			else if( pKeyboard->GetKeyState( Mix::HID::KEY_DOWN ) & Mix::HID::DOWN )
			{
				speed = max( MIN_SPEED, speed - VAL_SPEED );
			}

			headOffset -= headOffset * RUDDER_DAMPING;

			pos += speed * dt;
			while( pos >= MIX_2PI )
			{
				pos -= MIX_2PI;
			}

			////////////////////////////////////////////////////////////////////////////////////////////////////
			// カメラ
			////////////////////////////////////////////////////////////////////////////////////////////////////

			if( pCamera != NULL )
			{
				Mix::Matrix4x4 mat;
				Mix::Vector3 eye;
				Mix::Vector3 at;

				mat.SetRotationY( pos );

				eye.Set( 0.0f, BASE_HEIGHT + bodyOffset.y, CIRCUIT_RADIUS + bodyOffset.x );

				at = eye + Mix::Vector3::Cross( -eye.ToNormalize(), Mix::Vector3::YAxis() );
				at.z += headOffset.x;
				at.y += headOffset.y;

				eye = mat * eye;
				at = mat * at;

				pCamera->SetView( eye, at, Mix::Vector3::YAxis() );
			}

			////////////////////////////////////////////////////////////////////////////////////////////////////
			// 更新
			////////////////////////////////////////////////////////////////////////////////////////////////////

			if( pCamera != NULL )
			{
				pCamera->Update();
			}

			for( std::vector<BALL>::iterator it = balls.begin(); it != balls.end(); ++it )
			{
				Mix::Matrix4x4 rotMat;
				Mix::Matrix4x4 worldMat;

				( *it ).pos += ( *it ).speed * dt;
				while( ( *it ).pos >= MIX_2PI )
				{
					( *it ).pos -= MIX_2PI;
				}

				rotMat.SetRotationY( ( *it ).pos );
				worldMat.SetTranslation( 0.0f, BASE_HEIGHT + ( *it ).offset.y, CIRCUIT_RADIUS + ( *it ).offset.x );
				worldMat *= rotMat;

				( *it ).pModel->SetWorldMatrix( worldMat );
				( *it ).pModel->Update();
			}

			if( pSceneRenderer != NULL )
			{
				pSceneRenderer->Update( pEngine->GetDT() );
			}

			////////////////////////////////////////////////////////////////////////////////////////////////////
			// リフレッシュ
			////////////////////////////////////////////////////////////////////////////////////////////////////

			if( pSceneRenderer != NULL )
			{
				pSceneRenderer->Refresh();
			}

			for( std::vector<BALL>::iterator it = balls.begin(); it != balls.end(); ++it )
			{
				( *it ).pModel->Refresh();
			}

			if( ( pCamera != NULL ) && ( pSpotLight != NULL ) )
			{
				pSpotLight->SetPosition( pCamera->GetEye() );
				pSpotLight->SetDirection( pCamera->GetViewForward() );
				pSpotLight->Refresh();
			}

			////////////////////////////////////////////////////////////////////////////////////////////////////
			// 描画
			////////////////////////////////////////////////////////////////////////////////////////////////////

			pGraphicsDev->SetViewBounds( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT );

			if( pGraphicsDev->Begin() == True )
			{
				if( pCamera != NULL )
				{
					pCamera->Draw();
				}

				if( pCanvasRenderer != NULL )
				{
					const Mix::Point& screenSize = pGraphicsDev->GetScreenSize();
					const Mix::Vector3& eye = pCamera->GetEye();

					pCanvasRenderer->SetColor( Mix::Vector4( 1.0f, 1.0f, 1.0f, 1.0f ) );
					pCanvasRenderer->SetBlendType( Mix::Graphics::BLEND_NORMAL );

					tempStr.Sprintf(	L"FPS  [%f]\n"
										L"STPS [%f]\n"
										L"IF   [%d/%d]",	pEngine->GetFPS(),
															pEngine->GetSleepTimePerSec(),
															pEngine->GetInsomniaFrameCountPerSec(), pEngine->GetInsomniaFrameMax() );
					pCanvasRenderer->AddString(	16.0f, 16.0f, tempStr.GetConstPtr() );

					tempStr.Sprintf(	L"Position [%f,%f,%f]\n"
										L"Offset   [%f,%f]\n"
										L"Speed    [%.1f]", eye.x, eye.y, eye.z,
															bodyOffset.x, bodyOffset.y,
															speed );
					pCanvasRenderer->AddString(	16.0f, 80.0f, tempStr.GetConstPtr() );

					pCanvasRenderer->AddString(	16.0f, screenSize.y - 80.0f,
										L"Controls :\n"
										L"  Move   : W S A D Key\n"
										L"  Speed  : Up Down Key\n"
										L"  Effect : Space Key" );

					Utility::DrawMouseCursor( pCanvasRenderer, pMouse->GetPos() );

					pCanvasRenderer->Update();
					pCanvasRenderer->Draw();
				}

				pGraphicsDev->End();
			}

			pGraphicsDev->Present();
		}

		for( std::vector<BALL>::iterator it = balls.begin(); it != balls.end(); ++it )
		{
			MIX_RELEASE( ( *it ).pModel );
		}

		MIX_RELEASE( pSpotLight );
		MIX_RELEASE( pCamera );
		MIX_RELEASE( pSceneRenderer );
		MIX_RELEASE( pSceneEffectPackage );
		MIX_RELEASE( pCanvasRenderer );
		MIX_RELEASE( pFontL );
		MIX_RELEASE( pFontS );
		MIX_RELEASE( pGraphicsDev );
		MIX_RELEASE( pKeyboard );
		MIX_RELEASE( pMouse );

		Mix::Finalize();
	}

	return 0;
}
