#include "stdafx.h"
#include "cacheagent.h"

#include "BinSearchAE.h"
#include "RenderSystem.h"

cCacheAgent::cCacheAgent() :
mhDC(0), mhMemDC(0), mhBrush(0), mpImageBuffer(0),
mpBlockDescPool(0), mpLineDescPool(0), mpLineDescSearch(0),
mpCacheHashBuffer(0), mpTextureBufferDesc(0), mpPrimaryBufferDesc(0)
{
	mTexWidth = 0;
	mTexHeight = 0;

	mMaxLineDescNum = 0;
	mMaxTextureBlockDescNum = 0;
}

cCacheAgent::~cCacheAgent()
{
	SAFE_DELETE(mpCacheHashBuffer);
	SAFE_DELETE(mpLineDescSearch);

	SAFE_DELETE(mpLineDescPool);
	SAFE_DELETE(mpBlockDescPool);

	sTextureBufferDesc*	pBufferDesc = mpTextureBufferDesc;
	sTextureBufferDesc*	pNext;
	for( unsigned long i=0; i<mTextureBufferNum; i++)
	{
		pNext = pBufferDesc->pNext;

		pBufferDesc->pLineDesc = NULL;
		pBufferDesc->pBlockDesc = NULL;

		SAFE_NIDELETE( pBufferDesc->pNiTexture );

		delete pBufferDesc;
		pBufferDesc = pNext;
	}

	SAFE_DELETE_ARRAY(mpImageBuffer);

	if( mhBrush )
	{
		::DeleteObject( mhBrush );
		mhBrush = NULL;
	}

	if( mhMemDC )
	{
		::DeleteDC( mhMemDC );
		mhMemDC = NULL;
	}

	if( mhDC )
	{
		::ReleaseDC( NULL, mhDC );
		mhDC = NULL;
	}
}

/* ------------------------------------------------------------------
 * 함수이름 :	Initialize( NiDX9Renderer* pRenderer, unsigned int maxCacheItemNum, unsigned int texWidth, unsigned int texHeight, unsigned int texNum )
 * 목    적 :	
 * 주의사항 :	
 * ------------------------------------------------------------------ */
bool cCacheAgent::Initialize( unsigned int maxCacheItemNum, unsigned int texWidth, unsigned int texHeight, unsigned int texNum )
{
	NiDX9Renderer* pRenderer = NiDX9Renderer::GetRenderer();
	if( !pRenderer )
		return false;

	/// 텍스쳐를 생성하기 위한 기초 정보를 생성.
	const NiRenderTargetGroup* pkRTGroup = pRenderer->GetDefaultRenderTargetGroup();
	const NiPixelFormat* pkPixelFormat = pkRTGroup->GetPixelFormat(0);

	NiPixelFormat format = NiPixelFormat::RGBA5551;
	NiTexture::FormatPrefs textureFormat;
	if (pkPixelFormat->GetBitsPerPixel() == 16)
	{
		mImageDepth = 2;
		textureFormat.m_eAlphaFmt = NiTexture::FormatPrefs::BINARY;
		textureFormat.m_ePixelLayout = NiTexture::FormatPrefs::HIGH_COLOR_16;
	}
	else
	{
		mImageDepth = 4;
		textureFormat.m_eAlphaFmt = NiTexture::FormatPrefs::SMOOTH;
		textureFormat.m_ePixelLayout = NiTexture::FormatPrefs::TRUE_COLOR_32;
		format = NiPixelFormat::BGRA8888;
	}
	textureFormat.m_eMipMapped = NiTexture::FormatPrefs::NO;

	/// 문자를 출력한 이미지를 생성하기 위한 기본 변수 생성/초기화
	mhDC = ::GetDC( NULL );
	mhMemDC = ::CreateCompatibleDC(mhDC);
	mhBrush = ::CreateSolidBrush( 0xff000000 );

	mTexWidth = texWidth;
	mTexHeight = texHeight;

	mpImageBuffer = new unsigned char[mTexWidth*mTexHeight*mImageDepth];
	::memset( mpImageBuffer,0, mTexWidth*mTexHeight*mImageDepth );

	mTextureBufferNum = texNum;
	mMaxCacheItemNum = maxCacheItemNum;


	/// 텍스쳐 버퍼 원형 리스트 생성 및 초기화
	mpTextureBufferDesc = new sTextureBufferDesc;

	sTextureBufferDesc*	pBufferDesc = mpTextureBufferDesc;
	::memset( pBufferDesc, 0, sizeof(sTextureBufferDesc) );

	for( unsigned long i=0; i<mTextureBufferNum-1; i++ )
	{
		pBufferDesc->pNext = new sTextureBufferDesc;
		pBufferDesc = pBufferDesc->pNext;
		::memset( pBufferDesc, 0,sizeof(sTextureBufferDesc) );

		pBufferDesc->testnum = i+1;
	}
	pBufferDesc->pNext = mpTextureBufferDesc;

	/// 텍스쳐 버퍼에 텍스쳐를 생성한다.
	for ( unsigned long i=0; i<mTextureBufferNum; i++ )
	{
		/// Screen Texture를 생성한다.
		NiPixelData* pPixelData = NiNew NiPixelData( mTexWidth, mTexHeight, format );
		NiSourceTexture* pTemp = NiSourceTexture::Create( pPixelData, textureFormat );
		if( !pTemp )
		{
			assert(0);
			return false;
		}

		pBufferDesc->pNiTexture = NiNew NiScreenTexture( pTemp );
		pBufferDesc->pNiTexture->SetApplyMode( NiTexturingProperty::APPLY_REPLACE );

		/// Lock하기위한 DX텍스쳐를 참조 해 놓는다.
		NiDX9TextureData* pRenderData = (NiDX9TextureData*)pTemp->GetRendererData();
		pBufferDesc->pTexture = (D3DTexturePtr)pRenderData->GetD3DTexture();
        
		pBufferDesc = pBufferDesc->pNext;
	}

	/// 사용 버퍼의 할당
	mpPrimaryBufferDesc = mpTextureBufferDesc;
	mpPrimaryBufferDesc->availableHeight= mTexHeight;
	mpPrimaryBufferDesc->posY = 0;
	mpPrimaryBufferDesc->pBlockDesc = NULL;
	mpPrimaryBufferDesc->pLineDesc = NULL;

	mMaxLineDescNum = mTexHeight / MIN_FONT_HEIGHT * mTextureBufferNum;
	mMaxTextureBlockDescNum = mMaxCacheItemNum;

	mpBlockDescPool = new cBlockDescPool( mMaxTextureBlockDescNum );
	mpLineDescPool = new cLineDescPool( mMaxLineDescNum, mTexHeight / MIN_FONT_HEIGHT );

	mpLineDescSearch = new cBinSearchAE;
	mpLineDescSearch->Initialize( mMaxLineDescNum );

	mpCacheHashBuffer = new cCacheBuffer;
	mpCacheHashBuffer->Initialize( (mTexWidth/MIN_FONT_HEIGHT) * mMaxLineDescNum * mTextureBufferNum, MAX_STRING_BYTES_NUM );

	return true;
}

bool cCacheAgent::GetCache( RECT* pRect, sFontPropertyDesc* pDesc, NiObject** ppRvalue, SIZE* /*size*/ )
{
	NiDX9Renderer* pRenderer = NiDX9Renderer::GetRenderer();
	if( !pRenderer )
		return false;

	sTextureBlockDesc* pBlockDesc = AddCache( pRect, pDesc );
	if( !pBlockDesc )
		return false;

	(pBlockDesc->pNiTexture)->AddNewScreenRect( (short)pRect->top, 
												(short)pRect->left, 
												(unsigned short)pBlockDesc->width, 
												(unsigned short)pBlockDesc->height, 
												(unsigned short)pBlockDesc->rect.top, 
												(unsigned short)pBlockDesc->rect.left );
	*ppRvalue = pBlockDesc->pNiTexture;

	return true;
}

sTextureBlockDesc* cCacheAgent::AddCache( RECT* pRect, sFontPropertyDesc* pDesc )
{
	NiDX9Renderer* pRenderer = NiDX9Renderer::GetRenderer();
	if( !pRenderer )
	{
		assert(0);
		return 0;
	}

	/// 기존 폰트 정보를 찾는다.
	sTextureBlockDesc* pBlockDesc = (sTextureBlockDesc*)mpCacheHashBuffer->SearchValue( pDesc->pszString, pDesc->strLen, pDesc->hFont, pDesc->color );
	if( !pBlockDesc )
	{
        /// 블럭 정보 생성..
		unsigned int width;
		unsigned int height;

		HBITMAP hOldBitmap;
		HBITMAP	hBitmap;

		/// 이미지 생성
		hBitmap = CreateBitmapFromText( width, height, pDesc->hFont, pDesc->pszString, pDesc->strLen, 
			pDesc->color, pRect, pDesc->height, &hOldBitmap );

		DDBToDIB( mhMemDC, hBitmap, mpImageBuffer );

		::SelectObject( mhMemDC, hOldBitmap );
		::DeleteObject( hBitmap );

		if( width > mTexWidth )
			width = mTexWidth;

		if( height > mTexHeight )
			height = mTexHeight;

		/// 텍스쳐블럭을 할당받는다..
		pBlockDesc = AllocTextureBlock( width, height );
		pBlockDesc->pHashHandle = mpCacheHashBuffer->InsertValue( pDesc->pszString, pDesc->strLen, pDesc->hFont, pDesc->color, pBlockDesc );

		/// 텍스쳐상에서의 Block위치
		pBlockDesc->rect.left = pBlockDesc->posX;
		pBlockDesc->rect.right = pBlockDesc->posX + pBlockDesc->width;
		pBlockDesc->rect.top = pBlockDesc->posY;
		pBlockDesc->rect.bottom = pBlockDesc->posY + pBlockDesc->height;


		/// 글자 색상을 알아 오기 위해서
		DWORD fontColor = 0;
		DWORD alphaColor = 0xff000000;
		if( mImageDepth == 2 )
		{
			WORD* p = (WORD*)mpImageBuffer;
			for( WORD y=0; y<height; y++ )
			{
				for( WORD x=0; x<width; x++ )
				{
					if( p[ (width*y)+x ] & 0x7fff )
					{
						fontColor = (DWORD)(p[ (width*y)+x ] & 0x7fff);
						break;
					}
				}
			}
		}
		else
		{
			fontColor = pDesc->color & 0x00ffffff;
			alphaColor = pDesc->color & 0xff000000;
		}

		/// 이미지 복사
		D3DLOCKED_RECT lockRect;
		D3DTexturePtr pD3DTexture = pBlockDesc->pTexture;
		if( pD3DTexture->LockRect( 0, &lockRect, &pBlockDesc->rect, 0 ) == D3D_OK )
		{
			int	dPitch = lockRect.Pitch / mImageDepth;

			if( mImageDepth == 2 )
			{ /// 16bit
				WORD* pDst = (WORD*)lockRect.pBits;
				WORD* pSrc = (WORD*)mpImageBuffer;

				for( WORD y=0; y<height; y++ )
				{
					for( WORD x=0; x<width; x++ )
					{
						if( pSrc[ (width*y)+x ] & 0x7fff )
							pDst[ (dPitch*y)+x ] = pSrc[ (width*y)+x ] | 0x8000;
						else
							pDst[ (dPitch*y)+x ] = 0x0000;
					}
				}
			}
			else
			{ /// 32bit
				DWORD* pDst = (DWORD*)lockRect.pBits;
				DWORD* pSrc = (DWORD*)mpImageBuffer;

				for( DWORD y=0; y<height; y++ )
				{
					for( DWORD x=0; x<width; x++ )
					{
						if( pSrc[ (width*y)+x ] & 0x00ffffff )
							pDst[ (dPitch*y)+x ] = pSrc[ (width*y)+x ] | alphaColor;//0xff000000;
						else
							pDst[ (dPitch*y)+x ] = 0x00000000;
					}
				}
			}
			pD3DTexture->UnlockRect(0);
		}
	}

	return pBlockDesc;
}

/* ------------------------------------------------------------------
 * 함수이름 :	CreateBitmapFromText(...)
 * 목    적 :	글자를 찍은 DDB 비트맵을 생성한다.
 * 주의사항 :	
 * ------------------------------------------------------------------ */
HBITMAP cCacheAgent::CreateBitmapFromText( unsigned int& width, unsigned int& height, HFONT hFont, LPTSTR string, unsigned long strLen,
										  unsigned long color, RECT* pRect, unsigned long fontHeight, HBITMAP* pOldBitmap )
{
	/// 색깔값 분리.
	unsigned char red	= (unsigned char)((color & 0xFF0000) >> 16);
	unsigned char green	= (unsigned char)((color & 0xFF00) >> 8);
	unsigned char blue	= (unsigned char)((color & 0xFF));

	COLORREF txtColor = RGB(red, green, blue);	
	RECT rect;

	width = pRect->right - pRect->left;
	height = pRect->bottom - pRect->top;

	if( height < fontHeight )
		height = fontHeight;

	width = ((width + 0x00000001) & 0xfffffffe);
	pRect->right = pRect->left + width;

	rect.right = width + 10;
	rect.bottom = height + 10;
	rect.left = 0;
	rect.top = 0;

	HBITMAP hBitmap = ::CreateCompatibleBitmap( mhDC, width, height );

	HFONT hOldFont;
	hOldFont = (HFONT)::SelectObject( mhMemDC, hFont );

	::SetTextColor( mhMemDC, txtColor );
	::SetBkColor( mhMemDC, 0x00000000 );
	::SetTextAlign( mhMemDC, TA_TOP );

	HBITMAP hOldBitmap;
	hOldBitmap = (HBITMAP)::SelectObject( mhMemDC, hBitmap );

	::ExtTextOut( mhMemDC, 0, 0, ETO_OPAQUE, &rect, string, strLen, NULL );

	::SelectObject( mhMemDC, hOldFont );

	*pOldBitmap = hOldBitmap;

	return hBitmap;
}

/* ------------------------------------------------------------------
 * 함수이름 :	DDBToDIB( HDC hDC, HBITMAP hBitmap, unsigned char* pOut )
 * 목    적 :	DIB비트맵으로 변환
 * 주의사항 :	
 * ------------------------------------------------------------------ */
void cCacheAgent::DDBToDIB( HDC hDC, HBITMAP hBitmap, unsigned char* pOut )
{
	/// Spectify DIB properties.
	BITMAP bitmap;
	::GetObject( hBitmap, sizeof(BITMAP), &bitmap );

	BITMAPINFOHEADER bih;
	bih.biClrImportant	= 0;
	bih.biClrUsed		= 0;			
	bih.biXPelsPerMeter	= 0;
	bih.biYPelsPerMeter	= 0;
	bih.biSize			= sizeof(BITMAPINFOHEADER);
	bih.biWidth			= bitmap.bmWidth;
	bih.biHeight		= -bitmap.bmHeight;
	bih.biPlanes		= 1;
	bih.biBitCount		= mImageDepth*8;//16;
	bih.biCompression	= BI_RGB;	

	bih.biSizeImage = 0;

	/// Get Pixel data.
	BITMAPINFO bif;
	bif.bmiHeader = bih;

	::GetDIBits( hDC, hBitmap, 0, bitmap.bmHeight, pOut, &bif, DIB_RGB_COLORS );
}

/* ------------------------------------------------------------------
 * 함수이름 :	AllocTextureBlock( unsigned int width, unsigned int height )
 * 목    적 :	
 * 주의사항 :	
 * ------------------------------------------------------------------ */
sTextureBlockDesc*	cCacheAgent::AllocTextureBlock( unsigned int width, unsigned int height )
{
	// height가 맞는 라인 디스크립터부터 찾는다.
	// 검색
	sTextureLineDesc*	pLineDesc;
	sTextureBlockDesc*	pBlockDesc = NULL;

	sContainerAE*	pCur;
//	height += 1;
	unsigned long lineSearchKey = height;

lb_alloc_texblock:

	pBlockDesc = mpBlockDescPool->Alloc();
	if( !pBlockDesc )
	{
		ChangeTextureBuffer();
		goto lb_alloc_texblock;
	}

lb_begin_search:
	pCur = mpLineDescSearch->SearchContainer( lineSearchKey );
	if( !pCur )
	{
		/// 없으면 새로 할당...
		goto lb_alloc_linedesc;
	}

	if( pCur )
		lineSearchKey = ((sTextureLineDesc*)pCur->pItem)->height + 1;

	while (pCur)
	{
		pLineDesc = (sTextureLineDesc*)pCur->pItem;
		if( pLineDesc->availableWidth >= width )
			goto lb_find_ok;

		pCur = pCur->pNext;
	}

//lb_research:
	//	lineSearchKey = (unsigned long)pLineDesc->height + 1;
	goto lb_begin_search;

	/// 그래도 없으면 새로 할당..	
lb_alloc_linedesc:
	pLineDesc = AllocLineDesc( height );
	pLineDesc->pSearchHandle = mpLineDescSearch->InsertItem( pLineDesc->height, pLineDesc );

lb_find_ok:
	sTextureBufferDesc*	pBufferDesc;
	pBufferDesc = pLineDesc->pBufferDesc;

	pBlockDesc->pTexture = pLineDesc->pTexture;
	pBlockDesc->pNiTexture = pLineDesc->pNiTexture;
//	pBlockDesc->pNiElement = pLineDesc->pNiElement;

	pBlockDesc->height = pLineDesc->height;
	pBlockDesc->width = width;

	pBlockDesc->posX = pLineDesc->currentPosX;
	pBlockDesc->posY = pLineDesc->posY;

	/// 기존의 라인 디스크립터가 가지고 있던 버퍼 디스크립터 체인에 연결.한번에 지우기 위해서다.
	pBlockDesc->pBufferDesc = pBufferDesc;
	pBlockDesc->pNext = pBufferDesc->pBlockDesc;
	pBufferDesc->pBlockDesc = pBlockDesc;

	/// 다음번 할당받을 X좌표를 옮긴다.
	pLineDesc->currentPosX += width;
	/// 남은 폭을 계산한다.
	pLineDesc->availableWidth -= width;

//lb_return:
	return pBlockDesc;
}

/* ------------------------------------------------------------------
 * 함수이름 :	AllocLineDesc( unsigned int height )
 * 목    적 :	
 * 주의사항 :	
 * ------------------------------------------------------------------ */
sTextureLineDesc* cCacheAgent::AllocLineDesc( unsigned int height )
{
	int reservedHeight = (int)mpPrimaryBufferDesc->availableHeight - height;
	if( reservedHeight < 0 )
	{
		ChangeTextureBuffer();
	}

	sTextureLineDesc* pLineDesc = mpLineDescPool->Alloc();

	pLineDesc->pBufferDesc = mpPrimaryBufferDesc;
	pLineDesc->pNext = mpPrimaryBufferDesc->pLineDesc;
	mpPrimaryBufferDesc->pLineDesc = pLineDesc;

	pLineDesc->pTexture = mpPrimaryBufferDesc->pTexture;
	pLineDesc->pNiTexture = mpPrimaryBufferDesc->pNiTexture;
//	pLineDesc->pNiElement = mpPrimaryBufferDesc->pNiElement;

	pLineDesc->availableWidth = mTexWidth;
	pLineDesc->currentPosX = 0;
	pLineDesc->height = height;
	pLineDesc->posY = mpPrimaryBufferDesc->posY;

	/// 다음번 할당받을 Y좌표를 옮긴다.
	mpPrimaryBufferDesc->posY += height;
	/// 남은 높이를 계산한다.
	mpPrimaryBufferDesc->availableHeight -= height;

//lb_return:
	return pLineDesc;
}

/* ------------------------------------------------------------------
 * 함수이름 :	ChangeTextureBuffer()
 * 목    적 :	현재 텍스쳐가 모두 사용되면 다음 텍스쳐를 할당한다.
 * 주의사항 :	새로 할당되는 텍스쳐가 기존에 사용 되었다면, 기존 데이터는 모두 초기화 된다.
 * ------------------------------------------------------------------ */
void cCacheAgent::ChangeTextureBuffer()
{
	sTextureBufferDesc*	pBackBufferDesc;
	pBackBufferDesc = mpPrimaryBufferDesc->pNext;

	sTextureLineDesc*	pNextLineDesc;
	sTextureLineDesc*	pCurLineDesc = pBackBufferDesc->pLineDesc;
	while(pCurLineDesc)
	{
		pNextLineDesc = pCurLineDesc->pNext;
		mpLineDescSearch->DeleteItem( (sContainerAE*)pCurLineDesc->pSearchHandle );
		mpLineDescPool->Free( pCurLineDesc );
		pCurLineDesc = pNextLineDesc;
	}

	sTextureBlockDesc*	pNextBlockDesc;
	sTextureBlockDesc*	pCurBlockDesc = pBackBufferDesc->pBlockDesc;
	while(pCurBlockDesc)
	{
		pNextBlockDesc = pCurBlockDesc->pNext;

		/// 블럭디스크럽터를해쉬에서 제거.
		mpCacheHashBuffer->DeleteValue( pCurBlockDesc->pHashHandle );
		//
		//////////////////////////////

		mpBlockDescPool->Free( pCurBlockDesc );
		pCurBlockDesc = pNextBlockDesc;
	}

	mpPrimaryBufferDesc = pBackBufferDesc;
	mpPrimaryBufferDesc->availableHeight= mTexHeight;
	mpPrimaryBufferDesc->posY = 0;
	mpPrimaryBufferDesc->pBlockDesc = NULL;
	mpPrimaryBufferDesc->pLineDesc = NULL;
}