#include "StdAfx.h"
#include "resource.h"
#include "Application.h"

#include "DXVersion.h"
#include "AppWindow.h"
#include "AppTimer.h"

#include "RenderSystem.h"
#include "InputSystem.h"
#include "FilePackSystem.h"
#include "SoundSystem.h"

#include "OptionManager.h"
#include "ResourceManager.h"
#include "CameraManager.h"
#include "SceneManager.h"
#include "ObjectManager.h"
#include "UIManager.h"
#include "StageManager.h"

#include "UICursor.h"

#ifdef _GMTOOL
#include "GMTool.h"
#endif


#ifdef _DEVSYS
#include "DevSystem.h"
#endif

cApplication* cApplication::mpSingleton = 0;

/// directx version data size
const unsigned int BUFSIZE = 80;

cApplication::cApplication()
: mpFileSystem( 0 )
, mpOptionManager( 0 )
, mpRenderSystem( 0 )
, mpSoundSystem( 0 )
, mpInputSystem( 0 )
, mpResourceManager( 0 )
, mpCameraManager( 0 )
, mpSceneManager( 0 )
, mpInstance( 0 )
, mpStageManager( 0 )
, mpCursor(0)
#ifdef _GMTOOL
, mGMTool( 0 )
#endif
#ifdef _DEVSYS
, mpDevSystem( 0 )
#endif
, mSaveScreenShot( false )
, mResetFlag( false )
{

	assert( mpSingleton == 0 && "bad singleton!" );
	mpSingleton = this;

	mIsActive = true;
	mIsResolution = false;

	mpWindow = new cAppWindow;		
	
	mpTimer = NULL;
	/// 070110 PKH 동기화용 타이머
	mAccumTime = 0;
	mDeltaTime = 0;
}

cApplication::~cApplication()
{
	/// 윈도우의 제거는 임의로 먼저 한다.
	SAFE_DELETE( mpWindow );

#ifdef _GMTOOL
	SAFE_DELETE( mGMTool );
#endif
#ifdef _DEVSYS
	SAFE_DELETE( mpDevSystem );
#endif

	SAFE_DELETE( mpCursor );
	if( mpStageManager )
	{
		mpStageManager->Exit();
		delete mpStageManager;
		mpStageManager = 0;
	}
	SAFE_DELETE( mpSceneManager );
	SAFE_DELETE( mpCameraManager );
	SAFE_DELETE( mpTimer );
	SAFE_DELETE( mpInputSystem );
	SAFE_DELETE( mpOptionManager );
	SAFE_DELETE( mpResourceManager );
	SAFE_DELETE( mpSoundSystem );
	SAFE_DELETE( mpRenderSystem );
	SAFE_DELETE( mpFileSystem );

	mpSingleton = 0;
}

HWND cApplication::GetHWND()
{ 
	return (mpWindow)? mpWindow->GetHWND():NULL; 
}

HINSTANCE cApplication::GetWindowInstance()
{
	return mpInstance;
}

cApplication* cApplication::Create()
{
	return new cApplication;
}

bool cApplication::Init( NiInstanceRef hi, cString ip )
{
	mpInstance = hi;
	mIP = ip;

	/// 파일 팩 시스템을 생성
	mpFileSystem = new cFilePackSystem;
	if( !mpFileSystem || !mpFileSystem->Init() )
	{
		return false;
	}

	/// 사운드 시스템 생성 및 초기화
	mpSoundSystem = new cSoundSystem;
	if( !mpSoundSystem || !mpSoundSystem->Init() )
	{
		assert( 0 && "failed to init sound system" );
		NiMessageBox( "Failed to Init SoundSystem", "Error" );
		return false;
	}

	/// 윈도우를 생성
	if( CreateMainWindow() == false )
	{
		return false;
	}

	mpSoundSystem->SetHWND( mpWindow->GetHWND() );

	/// option file load
	mpOptionManager = new cOptionManager;
	if( !mpOptionManager || !mpOptionManager->Init() )
	{
		return false;
	}

	/// 렌더링 시스템을 생성 및 초기화
	mpRenderSystem = new cRenderSystem( mpWindow );
	if( !mpRenderSystem || !mpRenderSystem->Init() )
	{
		assert( 0 && "failed to init render system" );
		NiMessageBox( "Failed to Init RenderSystem", "Error" );
		return false;
	}

	/// 자원 관리자를 생성 및 초기화
	mpResourceManager = new cResourceManager;
	if( !mpResourceManager || !mpResourceManager->Init() )
	{
		return false;
	}

	/// 커서
	mpCursor = new cUICursor;
	if( !mpCursor || !mpCursor->Init() )
	{
		assert( 0 && "failed to Init mouse cursor" );
		return false;
	}

	::ShowWindow( GetHWND(), SW_SHOW );
	NiTexture* tex = mpResourceManager->LoadMapTexture( "./Data/2DData/LoadingMap_Image00.dds", false );
	mpRenderSystem->RenderOnce( tex );

	if( mpRenderSystem->IsFullScreenMode() )
		Sleep(20);

	/// 입력 시스템을 생성 및 초기화
	mpInputSystem = new cInputSystem;
	if( !mpInputSystem || !mpInputSystem->Init( mpInstance, mpWindow->GetHWND() ) )
	{
		assert( 0 && "failed to init input system" );
		NiMessageBox( "Failed to Init InputSystem", "Error" );
		return false;
	}

	/// 타이머를 생성 및 초기화
	mpTimer = new cAppTimer;
	if( !mpTimer )
	{
		return false;
	}
	mpTimer->Reset();

	/// 카메라 관리자를 생성 및 초기화
	mpCameraManager = new cCameraManager;
	if( !mpCameraManager || !mpCameraManager->Init() )
	{
		assert( 0 && "failed to init camera manager" );
		return false;
	}

	/// 장면 관리자를 생성
	mpSceneManager = new cSceneManager;
	if( !mpSceneManager || !mpSceneManager->Init() )
	{
		return false;
	}

	/// stage manager 생성 및 초기화
	mpStageManager = new cStageManager;
	if( !mpStageManager || !mpStageManager->Init( mpWindow->GetHWND(), mpRenderSystem->GetScreenWidth(), mpRenderSystem->GetScreenHeight() ) )
	{
		return false;
	}

#ifdef _DEVSYS
	mpDevSystem = new cDevSystem();
	if( !mpDevSystem || !mpDevSystem->Init() )
	{
		return false;
	}
#endif

	/// System Timer Start..
	mpTimer->Start();

	return true;
}

void cApplication::Run()
{
	MSG  msg;
	ZeroMemory( &msg, sizeof(msg) );

//	static char buff[256] = {0,};

	while( msg.message != WM_QUIT )
	{
//		XPG_SET( "Performance Test", 100 );

		if( ::PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
		{
			::TranslateMessage( &msg );
			::DispatchMessage( &msg );

//			sprintf(buff, "Message : %d", msg.message );
		}
		else
		{
			MainLoop();
//			strcpy( buff, "MainLoop");
		}
//		XPG_DO(buff);
	}

/*
	XPG_SET( "Performance Test", 100 );

	while( !::PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE ) )
	{
		MainLoop();
	}
	XPG_DO( "MainLoop" );

	do 
	{
		/// 사운드 모듈부분 릴리즈 하는부분에서 윈도우 핸들이 임의로 소실되어
		/// 메세지큐를 비우지 못하고 무한 반복되는 경우를 회피한다.
		/// 최종 사운드 모듈을 붙히면서 수정후 제거한다.
		ret = ::GetMessage( &msg, NULL, 0U, 0U );
		if( ret == 0 || ret == -1 )
		{
			break;
		}

		::TranslateMessage( &msg );
		::DispatchMessage( &msg );

	} while( ::PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE ) );

	XPG_DO( "Message Process" );
*/
}

/// 목적: 각종 시스템 및 관리자들을 제거
/// 주의: 생성의 역순으로 제거하는 규칙을 지켜주세요.
void cApplication::Exit()
{
#ifdef _GMTOOL
	if( mGMTool )
		mGMTool->Exit();
#endif

	if(mpStageManager)
	{
		mpStageManager->Exit();
		delete mpStageManager;
		mpStageManager = 0;
	}

	if( mpSceneManager )
		mpSceneManager->Exit();

	if( mpResourceManager )
		mpResourceManager->Exit();

#ifdef _DEVSYS
	if( mpDevSystem )
		mpDevSystem->Exit();
#endif
	if( mpInputSystem )
		mpInputSystem->Exit();

	if( mpRenderSystem )
		mpRenderSystem->Exit();

	/// 타이머를 정지
	if( mpTimer )
		mpTimer->Stop();
}

/// 목적: 모드 전환시 그래픽 메모리의 초기화
/// 주의: 게임브리오 내부 루틴에서 지원하는 부분이지만,
/// 문제 발생시 루틴수정없이 제어하기 위한 함수
void cApplication::Invalidate()
{
//	NiDX9Renderer* r = NiDX9Renderer::GetRenderer();
//	if( r->IsDeviceLost() == false )
//		return;

//	if( mResetFlag )
//		return;

	//OutputDebugString( _T("Invalidate\n") );
//	mResetFlag = true;

	if( mpRenderSystem )
		mpRenderSystem->Invalidate();

#ifdef _DEVSYS
	if(mpDevSystem)
		mpDevSystem->Invalidate();
#endif
}

/// 목적: 모드 전환시 그래픽 메모리의 재생산
/// 주의: 게임브리오 내부 루틴에서 지원하는 부분이지만,
/// 문제 발생시 루틴수정없이 제어하기 위한 함수
void cApplication::Restore()
{
//	if( mResetFlag == false )
//		return;

	//OutputDebugString( _T("Restore\n") );
//	mResetFlag = false;

#ifdef _DEVSYS
	if(mpDevSystem)
		mpDevSystem->Restore();
#endif

	if( mpRenderSystem )
		mpRenderSystem->Restore();
}

void cApplication::ChangeMode( )
{
/*
	cRenderSystem::sRenderOption* pOption = mpRenderSystem->GetOption();
	if( !pOption )
		return;

	Invalidate();
	mpRenderSystem->Reset();
	Restore();
*/
}

/*
void cApplication::ToggleScreen()
{	
	Invalidate();

	mpRenderSystem->ToggleFullScreenMode();
	if( mpRenderSystem->Reset( RENDERSYS->GetScreenWidth(), RENDERSYS->GetScreenHeight() ) == false )
	{
		mpRenderSystem->ToggleFullScreenMode();
	}

	Restore();
}
*/

void cApplication::UpdateScreenSize( unsigned int width, unsigned int height, unsigned char bpp, bool windowed )
{
	Invalidate();

	bool check = mpRenderSystem->Reset( width, height, bpp, windowed );

//	Restore();

	if( check == false )
	{
		/// 프로그램 종료...
		::SendMessage( mpWindow->GetHWND(), WM_CLOSE, 0, 0 );
		return;
	}

	CAMERAMAN->Reset( width, height );

	/// 옵션 내용은 윈도우 모드일때 GetClientRect에 의해 변할 수 잇다.
	UIMAN->UpdateScreenSize( width, height );
}

void cApplication::MainLoop()
{
	/// 윈도우가 비활성화 되었을때 다른 Application에 CPU 타임을 할당
	if( mIsActive == false )
		NiSleep( 10 );

	/// 경과 시간 및 누적 시간을 계산
	mDeltaTime = mpTimer->GetDeltaTime();
	mAccumTime += mDeltaTime;

	/// 프로세싱
	mpInputSystem->Process( mDeltaTime );

	mpResourceManager->Process();

	mpStageManager->Process( mDeltaTime, mAccumTime );

	mpCameraManager->Process( mDeltaTime, mAccumTime );

	mpSceneManager->Process( mDeltaTime, mAccumTime );

	mpSoundSystem->Process( mDeltaTime, mAccumTime );

	mpStageManager->ProcessWorld( mDeltaTime, mAccumTime );

#ifdef _DEVSYS
	mpDevSystem->Process( mAccumTime );
#endif

	/// 렌더링
	mpRenderSystem->Render();

	/// 스크린샷
	if( mSaveScreenShot )
	{
		mSaveScreenShot = false;
		SaveScreenShot();
	}
}

void cApplication::Process()
{

}

void cApplication::Render()
{

}

bool cApplication::CreateMainWindow()
{
	/// 윈도우를 등록
	WNDCLASS wndclass;
	wndclass.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
	wndclass.lpfnWndProc   = cApplication::WinProc;
	wndclass.cbClsExtra    = 0;
	wndclass.cbWndExtra    = 0;
	wndclass.hInstance     = mpInstance;
	wndclass.hIcon         = ::LoadIcon(NULL, MAKEINTRESOURCE(IDR_MAINFRAME));
	wndclass.hCursor       = ::LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)::GetStockObject(BLACK_BRUSH);
	wndclass.lpszClassName = cAppWindow::GetWindowClassName();
	wndclass.lpszMenuName  = 0;

	if( ::RegisterClass(&wndclass) == 0 )
	{
		NiMessageBox( "Can't Create Window!", "Error!!" );
		return false;
	}

	mpWindow->Create( mpInstance, 0, 0 );

	return true;
}

bool cApplication::CheckEnoughMemory()
{
	return true;
}

bool cApplication::CheckEnoughHardDisk()
{
	return true;
}

bool cApplication::CheckDirectXVersion()
{
	/// 실제 GetDXVersion() 함수의 속도가 느린부분 때문에, 레지스트리 정보를 믿어본다.
	/// 이부분은 처음 실행시 한번 검사를 하고, 그 값을 파일에 저장시키고,
	/// 다음 실행시는 레지스트리를 먼저 검사하는 방식으로 변경한다.
	HKEY hkey;
	char producttype[BUFSIZE];
	unsigned long buflen = BUFSIZE;
	long ret;

	/// 레지스트리에 있는 정보를 신뢰한다.
	ret = ::RegOpenKeyEx( HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\DirectX"), 0, KEY_QUERY_VALUE, &hkey );
	if( ret == ERROR_SUCCESS )
	{
		ret = ::RegQueryValueEx( hkey, _T("Version"), NULL, NULL, (LPBYTE) producttype, &buflen);
		if( (ret == ERROR_SUCCESS) && (buflen <= BUFSIZE) )
		{
			if( producttype[2] == '0' && producttype[3] < '9' )
			{
				/// Version Check Fail!
				NiMessageBox( "최신의 DirectX 버젼을 설치하세요.\n\n문제가 계속될 경우 재설치 하시기 바랍니다.", "Error Info" );
				::RegCloseKey( hkey );
				return false;
			}
			else
			{
				/// Version Check Succeed!
				::RegCloseKey( hkey );
				return true;
			}
		}

		::RegCloseKey( hkey );
	}

	/// DxDialog와 File버젼을 체크한다.
	HRESULT hr = E_FAIL;
	unsigned long dxver = 0;
	TCHAR dxvertext[10];

	CDXVersion kVer;
	hr = kVer.GetDXVersion( &dxver, dxvertext, 10 );

	if( SUCCEEDED(hr) )
	{
		/// 9.0~ 9.0c까지 통과.
		if( HIWORD(dxver) < 9 )
		{
			NiMessageBox( "최신의 DirectX 버젼을 설치하세요.", "Error Info" );
			return false;
		}
	}
	else
	{
		/// Dx 버젼에 대한 체크 실패.
		NiMessageBox( "DirectX설치에 문제가 있습니다.\n\n문제가 계속될 경우 재설치 하시기 바랍니다.", "Error Check" );
		return false;
	}

	return true;
}

/* ------------------------------------------------------------------
* 함수이름 :	CheckExecutionApplication()
* 목    적 :	뮤텍스를 통해서 Application의 중복 실행을 제한한다.
* 주의사항 :	
* ------------------------------------------------------------------ */
bool cApplication::CheckExecutionApplication()
{
	cAppWindow::mMutexHandle = ::CreateMutex( NULL, TRUE, cAppWindow::GetWindowClassName() );
	unsigned long err = ::GetLastError();
	if( err != ERROR_SUCCESS )
	{
		NiMessageBox( "Application already running...", "Error Info" );
		return false;
	}
	return true;
}

void cApplication::SetActive( bool active )
{
	mIsActive = active;
	if( SOUNDSYS )
		SOUNDSYS->ActiveSound( active );
}

/* ------------------------------------------------------------------
* 함수이름 :	WinProc()
* 목    적 :	생성된 System및 함수를 일괄 제거한다.
* 주의사항 :	생성의 역순으로 제거하는 규칙을 지켜주세요.
* ------------------------------------------------------------------ */
LRESULT CALLBACK cApplication::WinProc( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
	/// window message parsing !!
	if( INPUTSYS && INPUTSYS->WindowMessageParser( wnd, msg, wparam, lparam ) )
	{
		/// 포커스객체에서 사용하는 경우
		/// DX_INPUT 키보드 정보 비운다..
		INPUTSYS->KeyInfoClear();
		return 0;
	}

	switch( msg ) 
	{
	case WM_CREATE:
		{
			::SetTimer( wnd, 0, 60000, NULL );
		}
		break;
	case WM_TIMER:
		{
			///
			NETWORK->SendHeartbeat();
		}
		break;
	case WM_SOCKEVENTMSG2:
		NETWORK->Process( wparam, lparam );
		break;
	case WM_ACTIVATE:
		{
			if( THEAPP )
			{
				if( wparam == WA_INACTIVE )
				{
					THEAPP->SetActive( false );
				}
				else 
				{
					THEAPP->SetActive( true );
				}
			}
		}
		break;
	case WM_SETCURSOR:
		{ 
			if( CURSOR != NULL )
			{
				CURSOR->Process(0);
			}
		}
		break;
	case WM_IME_SETCONTEXT:
		{
			if( wparam == TRUE )
			{
				lparam &= ~( ISC_SHOWUICOMPOSITIONWINDOW /*| ISC_SHOWUIALLCANDIDATEWINDOW*/ );
				return 0;
			}
		}
		break;
	case WM_SYSCOMMAND:
		switch( wparam )
		{
		case SC_MOVE:
		case SC_SIZE:
		case SC_MAXIMIZE:
		case SC_MONITORPOWER:
			{
				if( RENDERSYS && RENDERSYS->IsFullScreenMode() )
				{
					return 1;
				}
			}
			return 0;
		case SC_KEYMENU:
			{
				return 1;
			}
		}
		break;
		/// 휴대용 PC에서 게임을 하는 경우에 대한 처리
	case WM_POWERBROADCAST:	
		{
			switch( wparam )
			{
#ifndef	PBT_APMQUERYSUSPEND
#define	PBT_APMQUERYSUSPEND 0x0000
#endif
	case PBT_APMQUERYSUSPEND:
		return true;
#ifndef	PBT_APMRESUMESUSPEND
#define PBT_APMRESUMESUSPEND 0x0007
#endif
	case PBT_APMRESUMESUSPEND:
		return true;
			}
		}
		break;
//	case WM_SIZE:
//		{
//		}
//		return 0;
	case WM_MOVE:
		{
#ifdef _GMTOOL
			if( GMTOOL )
				GMTOOL->OnMove();
#endif
		}
		break;

	case WM_CLOSE:
	case WM_DESTROY:
		{
//			SOUNDSYSTEM->SoundAllStop();
			::PostQuitMessage( 0 );
		}
		return 0;
	case WM_DISPLAYCHANGE:
		{
			THEAPP->mIsResolution = true;
		}
		break;
	}

	return ::DefWindowProc( wnd, msg, wparam, lparam );
}

bool cApplication::SaveScreenShot()
{
	NiDX9Renderer* renderer = NiDX9Renderer::GetRenderer();
	if( renderer == 0 )
		return false;

	/// 백버퍼를 파일로 저장
	LPDIRECT3DDEVICE9 device = renderer->GetD3DDevice();
	if( device == 0 )
		return false;

	IDirect3DSurface9* buffer = 0;
	if( device->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &buffer ) != D3D_OK )
		return false;

	if( buffer == 0 )
		return false;

	///
	if( ::FileExist( "ScreenShot" ) == false )
	{
		::CreateDirectory( L"ScreenShot", 0 );
	}

	///
	SYSTEMTIME tm;
	::GetLocalTime( &tm );

	cStringT pathName;
	pathName.Format( L"ScreenShot/iris_screenshot_%d_%02d_%02d_%02d_%02d_%02d.jpg", tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond );

	HRESULT ret = D3DXSaveSurfaceToFile( pathName.Cstr(), D3DXIFF_JPG, buffer, 0, 0 );
	buffer->Release();

	return ret == D3D_OK;
}

void cApplication::TestTest()
{
	UIMAN->UpdateScreenSize( mpRenderSystem->GetScreenWidth(), mpRenderSystem->GetScreenHeight() );
}

#ifdef _GMTOOL
bool cApplication::InitGMTool()
{
	mGMTool = new cGMTool();
	if( mGMTool == 0 || mGMTool->Init( mpInstance, mpWindow->GetHWND() ) == false )
		return false;

	return true;
}
#endif