#include "StdAfx.h"
#include "RenderSystem.h"

#include "AppWindow.h"
#include "CameraManager.h"
#include "FontAgent.h"
#include "LightAgent.h"
#include "FogAgent.h"
#include "SceneManager.h"
#include "WorldManager.h"
#include "UIManager.h"
#include "UICursor.h"
#include "OptionManager.h"

#include "Application.h"

#include "Shader.h"

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

cRenderSystem* cRenderSystem::mpSingleton = 0;

bool g_Invalidate( void* pvData )
{
	THEAPP->Invalidate();
	return true;
}

bool g_Restore( bool bBeforeReset, void* pvData )
{
	if( bBeforeReset )
	{
		NiDX9Renderer* r = (NiDX9Renderer*)NiRenderer::GetRenderer();
		if( r->IsDeviceLost() )
		{
			THEAPP->Invalidate();
		}
	}
	else
	{
		THEAPP->Restore();
		RENDERSYS->mReCreateContinue = false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
cPostProcess::cPostProcess()
: mpRenderedTexture(0)
, mPosCoordBuffer( 0 )
, mTexCoordBuffer( 0 )
, mVertexDeclaration( 0 )
{
	mShader = new cBrightShader;
}

cPostProcess::~cPostProcess()
{
	if( mVertexDeclaration )
	{
		mVertexDeclaration->Release();
		mVertexDeclaration = 0;
	}
	if( mPosCoordBuffer )
	{
		mPosCoordBuffer->Release();
		mPosCoordBuffer = 0;
	}
	if( mTexCoordBuffer )
	{
		mTexCoordBuffer->Release();
		mTexCoordBuffer = 0;
	}

	SAFE_DELETE(mShader);
}

bool cPostProcess::Init( cRenderer* r )
{
	///
	NiTexture::FormatPrefs prefs;
	const NiRenderTargetGroup* pkRTGroup = r->GetDefaultRenderTargetGroup();
	const NiPixelFormat* pkPixelFormat = pkRTGroup->GetPixelFormat(0);
	if (pkPixelFormat->GetBitsPerPixel() == 16)
		prefs.m_ePixelLayout = NiTexture::FormatPrefs::HIGH_COLOR_16;
	else
		prefs.m_ePixelLayout = NiTexture::FormatPrefs::TRUE_COLOR_32;

	prefs.m_eAlphaFmt = NiTexture::FormatPrefs::NONE;
	prefs.m_eMipMapped = NiTexture::FormatPrefs::NO;

	mpRenderedTexture = NiRenderedTexture::Create( pkRTGroup->GetWidth(0), pkRTGroup->GetHeight(0), r, prefs );
	if( mpRenderedTexture == 0 )
		return false;

	mpRenderTargetGroup = NiRenderTargetGroup::Create( mpRenderedTexture->GetBuffer(), r, true, false );

	/// create Quad point
	mShader->Init( r->GetBrightEffect() );

	/// 정점 선언을 생성
	D3DVERTEXELEMENT9 decl[] =
	{
		{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
		{ 1, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
		D3DDECL_END()
	};

	LPDIRECT3DDEVICE9 device = r->GetD3DDevice();
	device->CreateVertexDeclaration( decl, &mVertexDeclaration );

	/// 정점 버퍼를 생성

	NiPoint3 vBuffer[4] = { NiPoint3(0.0f, 0.0f, 1.0f),NiPoint3(1.0f, 0.0f, 1.0f), NiPoint3(0.0f, 1.0f, 1.0f), NiPoint3(1.0f, 1.0f, 1.0f) };
	mPosCoordBuffer = r->CreateVertexBuffer( 4, sizeof(NiPoint3), vBuffer );
	if( mPosCoordBuffer == 0 )
		return false;

	NiPoint2 tBuffer[4] = { NiPoint2(0.0f, 0.0f), NiPoint2(1.0f, 0.0f), NiPoint2(0.0f, 1.0f), NiPoint2(1.0f, 1.0f) };
	mTexCoordBuffer = r->CreateVertexBuffer( 4, sizeof(NiPoint2), tBuffer );
	if( mTexCoordBuffer == 0 )
		return false;

	return true;
}

void cPostProcess::Recreate( cRenderer* r, unsigned int width, unsigned int height, unsigned char bpp )
{
	mpRenderedTexture = 0;
	mpRenderTargetGroup = 0;

	///
	NiTexture::FormatPrefs prefs;
	if (bpp == 16)
		prefs.m_ePixelLayout = NiTexture::FormatPrefs::HIGH_COLOR_16;
	else
		prefs.m_ePixelLayout = NiTexture::FormatPrefs::TRUE_COLOR_32;

	prefs.m_eAlphaFmt = NiTexture::FormatPrefs::NONE;
	prefs.m_eMipMapped = NiTexture::FormatPrefs::NO;

	mpRenderedTexture = NiRenderedTexture::Create( width, height, r, prefs );
	mpRenderTargetGroup = NiRenderTargetGroup::Create( mpRenderedTexture->GetBuffer(), r, true, false );
}

void cPostProcess::RenderImmediate( cRenderer* r )
{
	if( r->IsDeviceLost() )
		return;

	LPDIRECT3DDEVICE9 device = r->GetD3DDevice();
	IDirect3DVertexDeclaration9* oldVertDecl = 0;
	device->GetVertexDeclaration( &oldVertDecl );

	/// 셰이더를 시작
	if( mShader->Begin( r->GetD3DViewProj() ) == true )
	{
		mShader->CommitChanges();

		bool bS0Mipmap, bNonPow2, bChanged; 
		D3DBaseTexturePtr pkD3DTexture = r->GetTextureManager()->PrepareTextureForRendering( mpRenderedTexture, bChanged, bS0Mipmap, bNonPow2 );

		///
		device->SetTexture( 0, pkD3DTexture );
		device->SetVertexDeclaration( mVertexDeclaration );
		device->SetStreamSource( 0, mPosCoordBuffer, 0, sizeof(NiPoint3) );
		device->SetStreamSource( 1, mTexCoordBuffer, 0, sizeof(NiPoint2) );

		device->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );

		mShader->End();
	}

	/// 장치 설정을 복원
	if( oldVertDecl )
		device->SetVertexDeclaration( oldVertDecl );
}

void cPostProcess::Render( cRenderer* r )
{
	if( r->BeginUsingDefaultRenderTargetGroup( NiRenderer::CLEAR_ALL ) == true )
	{
		r->SetScreenSpaceCameraData();

		RenderImmediate( r );

		UIMAN->Render();

#ifdef _DEVSYS
		DEVSYSTEM->DrawDevInfo();
#endif
		r->EndUsingRenderTargetGroup();
	}
}


//////////////////////////////////////////////////////////////////////////
cRenderSystem::cRenderSystem( cAppWindow* pwindow )
: mpWindowRef(pwindow)
//, mShadowShader( 0 )
{
	assert( mpSingleton == 0 && "bad singleton!" );
	mpSingleton = this;

	/// 중개자들을 생성
	mpFontAgent = new cFontAgent;
	mpLightAgent = new cLightAgent;
	mpFogAgent = new cFogAgent;

	mPostProcess = new cPostProcess;

	mClearColor = NiColor(0.2f,0.1f,0.1f);

	// 셰이더 도우미를 생성
	//mpShaderHelper = new cShaderHelper;
	//mpSceneManager = new cSceneManager;

	/// It can filter flat images into mipmap pyramids, and can convert 
	/// from standard Gamebryo pixel formats (PAL8, PALA8, RGB24, RGBA32, etc)
	/// to non-standard formats, and from non-standard formats to a few standard formats
	NiImageConverter::SetImageConverter( NiNew NiDevImageConverter );
	NiTexture::SetMipmapByDefault( true );

	/// 장치 설정
	mCurrentAdapter = 0;
	mCurrentDevice = 0;
	mCurrentMode = 0;

	mReCreateContinue = false;
	mWorldRendering = true;
}

cRenderSystem::~cRenderSystem()
{
	Exit();

//	NiImageConverter::SetImageConverter(NiNew NiDevImageConverter);

	//SAFE_DELETE( mpSceneManager )
	/// 셰이더 도우미를 제거
	//SAFE_DELETE(mpShaderHelper);

	/// 중개자들을 제거
	SAFE_DELETE( mPostProcess );
	SAFE_DELETE( mpFogAgent );
	SAFE_DELETE( mpLightAgent );
	SAFE_DELETE( mpFontAgent );

	mpSingleton = 0;
}

HWND cRenderSystem::GetHWND()
{ 
	return (mpWindowRef)? mpWindowRef->GetHWND():NULL; 
}

bool cRenderSystem::Init()
{
	/// DirectX 인스턴스 생성..
	/// Rendering을 주관해줄 Renderer 생성..
	/// 엔진에서 제공하는 NiRenderer를 활용
	if( CreateRenderer() == false )
	{
		return false;
	}

	if( mRenderer->Init() == false )
	{
		assert( 0 );
		return false;
	}

//	unsigned int verVS = mRenderer->GetShaderLibraryVersion()->GetSystemVertexShaderVersion();
//	unsigned int verPS = mRenderer->GetShaderLibraryVersion()->GetSystemPixelShaderVersion();

	if( mPostProcess->Init( mRenderer ) == false )
	{
		assert(0);
		return false;
	}

	/// FontAgent 초기화
	if( mpFontAgent->Init() == false )
	{
		return false;
	}

	/// LIghtAgent 초기화
	if( mpLightAgent->Init() == false )
	{
		return false;
	}

	mRenderer->AddLostDeviceNotificationFunc( g_Invalidate, 0 );
	mRenderer->AddResetNotificationFunc( g_Restore, 0 );

	SetBrightness( OPTIONMAN->GetGammaPer() );

	return true;
}

void cRenderSystem::Exit()
{
	/// 1. 종료시에 현재 적용된 최종 Render 옵션을 저장한다.
	if( mpFontAgent )
		mpFontAgent->Exit();
}

/// 주의 사항 : 불필요한 코드의 삽입을 금지한다.
void cRenderSystem::Render()
{
	mRenderer->BeginFrame();
	{
		if( mReCreateContinue == true )
			return;

		/// 버퍼 클리어 컬러 지정
		mRenderer->SetBackgroundColor( mClearColor );

		if( mRenderer->BeginUsingRenderTargetGroup( mPostProcess->GetRenderTarget(), NiRenderer::CLEAR_ALL ) == true )
		{
			if( mWorldRendering )
			{
				/// D3DTS_VIEW & D3DTS_PROJECTION 셋팅
				mRenderer->SetCameraData( CAMERAMAN->GetCurrentNi() );

				WORLDMAN->RenderSkyDome();

				WORLDMAN->Render();

				SCENEMAN->Render();
			}

			mRenderer->SetScreenSpaceCameraData();
			SCENEMAN->Render2D();

			mRenderer->SetCameraData( CAMERAMAN->GetCurrentNi() );
			SCENEMAN->RenderZFalseDamage();
/*
			mRenderer->SetScreenSpaceCameraData();
			UIMAN->Render();

#ifdef _DEVSYS
			DEVSYSTEM->DrawDevInfo();
#endif
*/
			mRenderer->EndUsingRenderTargetGroup();

			mPostProcess->Render( mRenderer );
		}
	}
	mRenderer->EndFrame();

	mRenderer->DisplayFrame();
}

void cRenderSystem::Invalidate()
{
	mpFontAgent->Invalidate();
	mRenderer->OnLostDevice();
}

void cRenderSystem::Restore()
{
	mRenderer->OnResetDevice();
	mpFontAgent->Restore();
}

bool cRenderSystem::Reset( unsigned int width, unsigned int height, unsigned char bpp, bool winmode )
{
	///
	NiWindowRef handle = mpWindowRef->GetHWND();

	if( winmode == false )
		mRenderOption.Flags = mRenderOption.Flags | NiDX9Renderer::USE_FULLSCREEN;
	else
		mRenderOption.Flags = mRenderOption.Flags & ~(NiDX9Renderer::USE_FULLSCREEN);

	mpWindowRef->AdjustWindowForChange( IsFullScreenMode() );

	EnumResolutions( width, height, bpp, winmode );

	if( winmode == false )
	{
		if( mCurrentMode )
		{
			NiDX9Renderer::DeviceDesc eDesc = NiDX9Renderer::DEVDESC_PURE;
			if( mCurrentDevice->GetDeviceType() == D3DDEVTYPE_REF )
				eDesc = NiDX9Renderer::DEVDESC_REF;

			/// Renderer Create
			NiDX9Renderer::RecreateStatus rval = NiDX9Renderer::GetRenderer()->Recreate( width, height,
				(NiDX9Renderer::FlagType)mRenderOption.Flags,
				handle,
				NiDX9Renderer::GetNiFBFormat( mCurrentMode->m_eD3DFormat ),
				NiDX9Renderer::DSFMT_UNKNOWN,
				NiDX9Renderer::PRESENT_INTERVAL_IMMEDIATE,
				NiDX9Renderer::SWAPEFFECT_DEFAULT,
				mRenderOption.FBmode );

			if( rval == NiDX9Renderer::RECREATESTATUS_RESTORED )
			{
				goto _ERROR;
			} 
			else if( rval == NiDX9Renderer::RECREATESTATUS_FAILED ) 
			{
				goto _ERROR;
			}
		}
		else
		{
			/// message box 띄우자!! Default값으로 생성한다!!
			goto _ERROR;
		}
	}
	else
	{
		NiDX9Renderer::DeviceDesc eDesc = NiDX9Renderer::DEVDESC_PURE;
		if( mCurrentDevice->GetDeviceType() == D3DDEVTYPE_REF )
			eDesc = NiDX9Renderer::DEVDESC_REF;

		/// Renderer Create
		NiDX9Renderer::RecreateStatus rval = NiDX9Renderer::GetRenderer()->Recreate( width, height,
			(NiDX9Renderer::FlagType)mRenderOption.Flags,
			handle,
			NiDX9Renderer::FBFMT_UNKNOWN,
			NiDX9Renderer::DSFMT_UNKNOWN,
			NiDX9Renderer::PRESENT_INTERVAL_IMMEDIATE,
			NiDX9Renderer::SWAPEFFECT_DEFAULT,
			mRenderOption.FBmode );

		if( rval == NiDX9Renderer::RECREATESTATUS_RESTORED )
		{
			goto _ERROR;
		} 
		else if( rval == NiDX9Renderer::RECREATESTATUS_FAILED ) 
		{
			goto _ERROR;
		}
	}

	mpWindowRef->AdjustWindowRect( width, height );

	mRenderOption.Width = width;
	mRenderOption.Height = height;
	mRenderOption.Bpp = bpp;

	mPostProcess->Recreate( mRenderer, width, height, bpp );

	///
	return true;

_ERROR:
	mRenderOption.Width = width;
	mRenderOption.Height = height;
	mRenderOption.Bpp = bpp;

	mReCreateContinue = true;
	return false;
}

bool cRenderSystem::CreateRenderer()
{
	/// 랜더러 초기화를 위한 옵션정보..
	/// 다이렉트 엑스 초기화
	/// 게임브리오 랜더러 초기화

	NiWindowRef handle = mpWindowRef->GetHWND();

	/// 생성 옵션 적용
	if( OPTIONMAN->IsWindowed() == false )
		mRenderOption.Flags = mRenderOption.Flags | NiDX9Renderer::USE_FULLSCREEN;
	else
		mRenderOption.Flags = mRenderOption.Flags & ~(NiDX9Renderer::USE_FULLSCREEN);

	mRenderOption.Width = OPTIONMAN->GetWindowWidth();
	mRenderOption.Height = OPTIONMAN->GetWindowHeight();
	mRenderOption.Bpp = OPTIONMAN->GetWindowBpp();

	/// main window update
	mpWindowRef->AdjustWindowForChange( IsFullScreenMode() );

	EnumAdapters();
	EnumResolutions( mRenderOption.Width, mRenderOption.Height, mRenderOption.Bpp, OPTIONMAN->IsWindowed() );

	if( IsFullScreenMode() )
	{
		if( mCurrentAdapter && mCurrentDevice && mCurrentMode )
		{
			NiDX9Renderer::DeviceDesc eDesc = NiDX9Renderer::DEVDESC_PURE;
			if( mCurrentDevice->GetDeviceType() == D3DDEVTYPE_REF )
				eDesc = NiDX9Renderer::DEVDESC_REF;

			/// Renderer Create
			mRenderer = cRenderer::Create( mRenderOption.Width, mRenderOption.Height,
				(NiDX9Renderer::FlagType)mRenderOption.Flags,
				handle,
				handle,
				mCurrentAdapter->GetAdapterIndex(),
				eDesc,
				NiDX9Renderer::GetNiFBFormat( mCurrentMode->m_eD3DFormat ),
				NiDX9Renderer::DSFMT_UNKNOWN,
				NiDX9Renderer::PRESENT_INTERVAL_IMMEDIATE,
				NiDX9Renderer::SWAPEFFECT_DEFAULT,
				mRenderOption.FBmode );
		}
		else
		{
			/// message box 띄우자!! Default값으로 생성한다!!
			NiMessageBox( "Failed Device create!! Default option create!!", "Error Info" );

			/// 기본 해상도로 생성해 본다.
			mRenderOption.Width = 1024;
			mRenderOption.Height = 768;
			mRenderOption.Bpp = 32;

			/// main window update
			EnumAdapters();
			EnumResolutions( mRenderOption.Width, mRenderOption.Height, mRenderOption.Bpp, OPTIONMAN->IsWindowed() );
			if( mCurrentAdapter == 0 || mCurrentDevice == 0  || mCurrentMode == 0 )
			{
				NiMessageBox( "Failed Device create!! ShotDown Application.", "Error Info" );
				return false;
			}

			NiDX9Renderer::DeviceDesc eDesc = NiDX9Renderer::DEVDESC_PURE;
			if( mCurrentDevice->GetDeviceType() == D3DDEVTYPE_REF )
				eDesc = NiDX9Renderer::DEVDESC_REF;

			/// Renderer Create
			mRenderer = cRenderer::Create( mRenderOption.Width, mRenderOption.Height,
				(NiDX9Renderer::FlagType)mRenderOption.Flags,
				handle,
				handle,
				mCurrentAdapter->GetAdapterIndex(),
				eDesc,
				NiDX9Renderer::GetNiFBFormat( mCurrentMode->m_eD3DFormat ),
				NiDX9Renderer::DSFMT_UNKNOWN,
				NiDX9Renderer::PRESENT_INTERVAL_IMMEDIATE,
				NiDX9Renderer::SWAPEFFECT_DEFAULT,
				mRenderOption.FBmode );
		}
	}
	else
	{
		NiDX9Renderer::DeviceDesc eDesc = NiDX9Renderer::DEVDESC_PURE;
		if( mCurrentDevice->GetDeviceType() == D3DDEVTYPE_REF )
			eDesc = NiDX9Renderer::DEVDESC_REF;

		// create and return the renderer
		mRenderer = cRenderer::Create( mRenderOption.Width, mRenderOption.Height,
			(NiDX9Renderer::FlagType)mRenderOption.Flags,
			handle,
			handle,
			mCurrentAdapter->GetAdapterIndex(),
			eDesc,
			NiDX9Renderer::FBFMT_UNKNOWN, 
			NiDX9Renderer::DSFMT_UNKNOWN,
			NiDX9Renderer::PRESENT_INTERVAL_IMMEDIATE,
			NiDX9Renderer::SWAPEFFECT_DEFAULT,
			mRenderOption.FBmode );
	}

	if( mRenderer == NULL )
	{
		NiMessageBox( "DX9 Renderer Creation Failed", "Error Info" );
		return false;
	}

	mpWindowRef->AdjustWindowRect( mRenderOption.Width, mRenderOption.Height, true );

	/// Setting dx background color
	mRenderer->SetBackgroundColor( mClearColor );

	return true;
}

void cRenderSystem::ToggleFullScreenMode()
{
	if( IsFullScreenMode() )
		mRenderOption.Flags = mRenderOption.Flags & ~(NiDX9Renderer::USE_FULLSCREEN);
	else
		mRenderOption.Flags = mRenderOption.Flags | NiDX9Renderer::USE_FULLSCREEN;
}

//////////////////////////////////////////////////////////////////////////
bool cRenderSystem::EnumAdapters()
{
	mCurrentAdapter = 0;
	mCurrentDevice = 0;

	const NiDX9SystemDesc* pSystem = NiDX9Renderer::GetSystemDesc();
	unsigned int adapterCount = pSystem->GetAdapterCount();

	bool checkAdapter = false;
	for ( unsigned int i = 0; i < adapterCount; i++ )
	{
		const NiDX9AdapterDesc* pAdapter = pSystem->GetAdapter(i);
		assert(pAdapter);

		const NiDX9DeviceDesc* pDevice = pAdapter->GetDevice( D3DDEVTYPE_HAL );
		if( !pDevice )
			continue;

		/// 윈도우 모드 지원 체크.
		if( pAdapter->CanDeviceRenderWindowed( D3DDEVTYPE_HAL ) == false )
			continue;

		/// 배열에 담아 두자
		mAdapterArr.PushBack( (void*)pAdapter );

		checkAdapter = true;
	}

	if( checkAdapter )
	{
		mCurrentAdapter = (NiDX9AdapterDesc*)mAdapterArr[0];
		if( mCurrentAdapter == 0 )
			return false;

		mCurrentDevice = (NiDX9DeviceDesc*)mCurrentAdapter->GetDevice( D3DDEVTYPE_HAL );
		assert( mCurrentDevice );
	}

	return checkAdapter;
}

void cRenderSystem::EnumResolutions( unsigned int curWidth, unsigned int curheight, unsigned int curBpp, bool windowed )
{
	if( mCurrentAdapter == 0 )
		return;

	mCurrentMode = 0;
	mResolutionArr.Clear();

	unsigned int modeCount = mCurrentAdapter->GetModeCount();

	for( unsigned int i=0; i<modeCount; i++ )
	{
		const NiDX9AdapterDesc::ModeDesc* pMode = mCurrentAdapter->GetMode( i );

		if( pMode == 0 )
			continue;

		/// 추가로 불필요한 모드는 제외시킨다.
		if( pMode->m_uiWidth <= 800 )
			continue;
		if( pMode->m_uiHeight <= 600 )
			continue;

		/// 배열에 담아 두자
		mResolutionArr.PushBack( (void*)pMode );

		/// 현재 모드 체크
		if( pMode->m_uiWidth == curWidth && pMode->m_uiHeight == curheight )
		{
			if( pMode->m_uiBPP == curBpp )
				mCurrentMode = (NiDX9AdapterDesc::ModeDesc*)pMode;
		}
	}

	::Sort( mResolutionArr.Begin(), mResolutionArr.End(), cResolutionCompare() );
}

void cRenderSystem::EnumMultisamples()
{
/*
	if( mCurrentDevice == 0 )
		return;

	D3DFORMAT eformat;
	if( mCurrentMode )
	{
		NiDX9AdapterDesc::ModeDesc* pMode = (NiDX9AdapterDesc::ModeDesc*)mCurrentMode;
		eformat = pMode->m_eD3DFormat;
	}
	else
	{
		unsigned int bitdepth = 

	}
*/
}

#include "PlaneObject.h"
void cRenderSystem::RenderOnce( NiTexture* tex )
{
	cPlaneObject* pPlane = new cPlaneObject;
	if( pPlane->Create( tex, 0, 0, (unsigned short)GetScreenWidth(), (unsigned short)GetScreenHeight(),
		0, 0, tex->GetWidth(), tex->GetHeight() ) == false )
	{
		return;
	}

	mRenderer->BeginFrame();
	{
		mRenderer->SetBackgroundColor( mClearColor );
		if( mRenderer->BeginUsingDefaultRenderTargetGroup( NiRenderer::CLEAR_ALL ) == true )
		{
			mRenderer->SetScreenSpaceCameraData();
			pPlane->Draw();

			mRenderer->EndUsingRenderTargetGroup();
		}
	}
	mRenderer->EndFrame();

	mRenderer->DisplayFrame();

	SAFE_DELETE(pPlane);
}

void cRenderSystem::SetBrightness( float br )
{
	if( mPostProcess )
		mPostProcess->mShader->SetBright( br );
}