// TerrainViewport.cpp : implementation file
//

#include "stdafx.h"
#include "TerrainViewport.h"
#include "CryEditDoc.h"

/////////////////////////////////////////////////////////////////////////////
// CTerrainViewport

// Used to give each static object type a different color
static DWORD StatObjColorArray[] =
{
	0x00FF0000,
	0x0000FF00,
	0x000000FF,
	0x00FFFFFF,
	0x00FF00FF,
	0x00FFFF00,
	0x0000FFFF,
	0x007F00FF,
	0x007FFF7F,
	0x00FF7F00,
	0x0000FF7F,
	0x007F7F7F,
	0x00000000
};

CTerrainViewport::CTerrainViewport()
{
	m_heightmapSize.cx = GetDocument()->m_cHeightmap.GetWidth();
	m_heightmapSize.cy = GetDocument()->m_cHeightmap.GetHeight();

	VERIFY(m_dcView.CreateCompatibleDC(NULL));

	// Create a DC and a bitmap
	VERIFY(m_dcTerrain.CreateCompatibleDC(NULL));
	VERIFY(m_bmpTerrain.CreateBitmap(m_heightmapSize.cx,m_heightmapSize.cy, 1, 32, NULL));
	m_dcTerrain.SelectObject(&m_bmpTerrain);

	m_iBrushSize = 50;
}

CTerrainViewport::~CTerrainViewport()
{
}


BEGIN_MESSAGE_MAP(CTerrainViewport, CViewport)
	//{{AFX_MSG_MAP(CTerrainViewport)
	ON_WM_PAINT()
	ON_WM_MOUSEWHEEL()
	ON_WM_MOUSEMOVE()
	ON_WM_ERASEBKGND()
	ON_WM_SIZE()
	ON_WM_RBUTTONDOWN()
	ON_WM_RBUTTONUP()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CTerrainViewport message handlers

//////////////////////////////////////////////////////////////////////////
CPoint CTerrainViewport::WorldToView( Vec3 wp )
{
	CPoint p;
	float x = wp.x / 2.0f;
	float y = wp.y / 2.0f;

	float lTemp;

	// Swap the axis
	lTemp = x;
	x = y;
	y = lTemp;
	
	x = (x * GetZoomFactor());
	y = (y * GetZoomFactor());
	
	// Add the offset
	x += GetScrollOffset().x;
	y += GetScrollOffset().y;
	p.x = (int)x;
	p.y = (int)y;

	return p;
}

//////////////////////////////////////////////////////////////////////////
Vec3	CTerrainViewport::ViewToWorld( CPoint vp,bool *collideWithTerrain,bool onlyTerrain )
{
	vp.x += abs(GetScrollOffset().x);
	vp.y += abs(GetScrollOffset().y);

	// Swap the axis
	int lTemp = vp.x;
	vp.x = vp.y;
	vp.y = lTemp;

	// Scale with the zoom factor
	float x = (float)vp.x / GetZoomFactor();
	float y = (float)vp.y / GetZoomFactor();

	Vec3 wp;
	wp.x = x * 2.0f;
	wp.y = y * 2.0f;
	wp.z = GetIEditor()->GetTerrainElevation(wp.x,wp.y);

	if (collideWithTerrain)
		*collideWithTerrain = true;

	/*
	CEngineSingleton::GetGameInterface()->GetInterface()->
			MovePlayer(point.x * 2.0f, point.y * 2.0f, CEngineSingleton::
			GetGameInterface()->GetInterface()->GetTerrainElevation(point.x * 2.0f, point.y * 2.0f));
	*/

	return wp;
}

void	CTerrainViewport::ViewToWorldRay( CPoint vp,Vec3 &raySrc,Vec3 &rayDir )
{
	raySrc = ViewToWorld( vp );
	raySrc.z = 1000;
	rayDir(0,0,-1);
}

bool CTerrainViewport::SetScrollOffset(long iX, long iY)
{
	////////////////////////////////////////////////////////////////////////
	// Set the scrolling offset. If the passed values are invalid, the
	// nearest valid values will be set and false is returned
	////////////////////////////////////////////////////////////////////////

	bool bRangeValid = true;
	RECT rcWndPos;
	long iScaledTexWidth = (long) (m_heightmapSize.cx * GetZoomFactor());

	// Obtain the size of the window
	GetClientRect(&rcWndPos);

	// Don't allow to scroll beyond the lower right corner
	if (abs(iX) > iScaledTexWidth - rcWndPos.right)
	{
		iX = -(iScaledTexWidth - rcWndPos.right);
		bRangeValid = false;
	}

	if (abs(iY) > iScaledTexWidth - rcWndPos.bottom)
	{
		iY = -(iScaledTexWidth - rcWndPos.bottom);
		bRangeValid = false;
	}

	// Don't allow to scroll beyond the upper left corner
	if (iX > 0)
	{
		iX = 0;
		bRangeValid = false;
	}

	if (iY > 0)
	{
		iY = 0;
		bRangeValid = false;
	}

	// Save the (eventually corrected) scroll offset
	m_cScrollOffset = CPoint(iX, iY);

	RedrawWindow();

	return bRangeValid;
}


void CTerrainViewport::ResetContent()
{
		////////////////////////////////////////////////////////////////////////
	// Create a surface texture that consists entirely of water
	////////////////////////////////////////////////////////////////////////
	DWORD *pWaterTexData = NULL;
	DWORD *pSurfaceTextureData = NULL;
	DWORD *pPixData = NULL, *pPixDataEnd = NULL;
	CBitmap bmpLoad;
	bool bReturn;

	// Load the water texture out of the ressource
	bReturn = bmpLoad.LoadBitmap(MAKEINTRESOURCE(IDB_WATER));
	ASSERT(bReturn);

	// Allocate new memory to hold the bitmap data
	pWaterTexData = new DWORD[128 * 128];
	ASSERT(pWaterTexData);

	// Retrieve the bits from the bitmap
	VERIFY(bmpLoad.GetBitmapBits(128 * 128 * sizeof(DWORD), pWaterTexData));
 

	// Allocate memory for the surface texture
	pSurfaceTextureData = new DWORD[m_heightmapSize.cx*m_heightmapSize.cy];
	ASSERT(pSurfaceTextureData);


	// Fill the surface texture with the water texture, tile as needed
	for (int j=0; j<m_heightmapSize.cy; j++)
		for (int i=0; i<m_heightmapSize.cx; i++)
		{
			pSurfaceTextureData[i + j * m_heightmapSize.cx] =
				pWaterTexData[(i % 128) +  (j % 128) * 128];
		}


	// Set the loop pointers
	pPixData = pSurfaceTextureData;
	pPixDataEnd = &pSurfaceTextureData[m_heightmapSize.cx*m_heightmapSize.cy];

	/*
	// Switch R and B
	while (pPixData != pPixDataEnd)
	{
		// Extract the bits, shift them, put them back and advance to the next pixel
		*pPixData++ = ((* pPixData & 0x00FF0000) >> 16) | 
			(* pPixData & 0x0000FF00) | ((* pPixData & 0x000000FF) << 16);
	}
	*/

	// Load it into the bitmap
	m_bmpTerrain.SetBitmapBits(m_heightmapSize.cx*m_heightmapSize.cy*4, pSurfaceTextureData);

	if (::IsWindow(m_hWnd))
		RedrawWindow();

	// Free surface mem
	delete [] pSurfaceTextureData;
	pSurfaceTextureData = 0;

	// Free the water texture data
	delete [] pWaterTexData;
	pWaterTexData = 0;
}

void CTerrainViewport::UpdateContent( int flags )
{
	////////////////////////////////////////////////////////////////////////
	// Generate a new surface texture
	////////////////////////////////////////////////////////////////////////
	DWORD *pSurfaceData = NULL;
	bool bReturn;

	// Allocate memory
	pSurfaceData = new DWORD[m_heightmapSize.cx*m_heightmapSize.cy];
	ASSERT(pSurfaceData);

	bReturn = GetDocument()->GetHeightmap().GetPreviewBitmap( (DWORD*)pSurfaceData,m_heightmapSize.cx,false,false );
	/*
	// Fill in the surface data into the array. Apply lighting and water, use
	// the settings from the document
	bReturn = cSurfaceTexture.GenerateSurface(pSurfaceData, SURFACE_TEXTURE_WIDTH, 
		SURFACE_TEXTURE_WIDTH, true, true, GLOBAL_GET_DOC->GetWaterLevel(), true);
		*/
 
	if (bReturn)
	{
		m_bmpTerrain.SetBitmapBits( m_heightmapSize.cx*m_heightmapSize.cy*4, pSurfaceData);
		if (IsWindow(GetSafeHwnd()))
			RedrawWindow();
	}

	// Free the surface data array
	delete [] pSurfaceData;
	pSurfaceData = 0;

	/*
	CCryEditDoc *doc = GLOBAL_GET_DOC;
	if (!doc)
		return;

	m_heightmapSize.cx = doc->m_cHeightmap.GetWidth();
	m_heightmapSize.cy = doc->m_cHeightmap.GetHeight();
	m_waterLevel = doc->GetWaterLevel();

	/*
	////////////////////////////////////////////////////////////////////////
	// Generate a new surface texture
	////////////////////////////////////////////////////////////////////////
	CTerrainTexture cSurfaceTexture;
	bool bReturn;

	CLogFile::FormatLine("Updating top view surface data (%i x %i)...",
		m_textureSize.cx, m_textureSize.cy);

	// Allocate memory
	if (m_textureData)
		delete []m_textureData;

	if (!m_bShowHeightmap)
	{
		m_textureSize.cx = SURFACE_TEXTURE_WIDTH;
		m_textureSize.cy = SURFACE_TEXTURE_WIDTH;
		
		m_textureData = new uint[m_textureSize.cx * m_textureSize.cy];
		ASSERT(m_textureData);

		// Fill in the surface data into the array. Apply lighting and water, use
		// the settings from the document
		bReturn = cSurfaceTexture.GenerateSurface( (unsigned long*)m_textureData,m_textureSize.cx,m_textureSize.cy,true,true,m_waterLevel,true);
	}
	else
	{
		m_textureSize.cx = 512;
		m_textureSize.cy = 512;
		m_textureData = new uint[m_textureSize.cx * m_textureSize.cy];
		ASSERT(m_textureData);

		// Show heightmap data.
		//GLOBAL_GET_DOC->m_cHeightmap.GetDataEx(pHeightmap, (m_rcView.right - m_rcView.left), false, false);
		GLOBAL_GET_DOC->m_cHeightmap.GetPreviewBitmap( (DWORD*)m_textureData,m_textureSize.cx,false,false );
	}
	m_textureValid = false;
 
	if (bReturn)
	{
		if (::IsWindow(m_hWnd))
			RedrawWindow();
	}

	if (m_bShowStatObjects)
	{
		int w,h;
		GetIEditor()->GetDocument()->GetStatObjDistArray( w,h );
		if (m_statObjTexData)
			delete []m_statObjTexData;
		m_statObjTexSize.cx = w;
		m_statObjTexSize.cy = h;
		m_statObjTexData = new uint[w*h];
		memset( m_statObjTexData,0,w*h );

		DrawStaticObjects();
	}
	*/

	SetZoomFactor( GetZoomFactor() );
}
	
void CTerrainViewport::SetZoomFactor(float fZoomFactor)
{
	////////////////////////////////////////////////////////////////////////
	// Adjust the zoom factor of the view. If the passed values are invalid,
	// the nearest valid values will be set and false is returned
	////////////////////////////////////////////////////////////////////////

	RECT rcWndPos;
	bool bRangeValid = true;
	float fNewSizeTemp = 0;
	float fEnlargementFactor;

	// Zero can produce artifacts and errors
	if (fZoomFactor == 0.0f)
	{
		fZoomFactor = m_fZoomFactor;
		bRangeValid = false;
	}
	// Obtain the dimensions of the window
	GetClientRect(&rcWndPos);

	// Is the new zoom factor smaller than the last one ?
	if (fZoomFactor < m_fZoomFactor)
	{
		// We might run into the problem that the map is smaller than the
		// the window when displayed with the specified zoom factor

		if (m_heightmapSize.cx * fZoomFactor < rcWndPos.right)
		{
			// Calculate the new size
			fZoomFactor = (float) rcWndPos.right / m_heightmapSize.cx;

			bRangeValid = false;
		}

		if (m_heightmapSize.cx * fZoomFactor < rcWndPos.bottom)
		{
			// Calculate the new size
			fNewSizeTemp = (float) rcWndPos.bottom / m_heightmapSize.cx;

			// Only set the new zoom if it is larger than the previous
			fZoomFactor = (fZoomFactor > fNewSizeTemp) ? fZoomFactor : fNewSizeTemp;

			bRangeValid = false;
		}
	}

	// Calculate how much larger the new zoom is compared to the old one
	fEnlargementFactor = fZoomFactor / m_fZoomFactor;
	
	// Save the new zoom factor
	m_fZoomFactor = fZoomFactor;

	if (m_cScrollOffset.x + m_heightmapSize.cx*fZoomFactor < rcWndPos.right)
		m_cScrollOffset.x = rcWndPos.right - m_heightmapSize.cx*fZoomFactor;

	if (m_cScrollOffset.y + m_heightmapSize.cy*fZoomFactor < rcWndPos.bottom)
		m_cScrollOffset.y = rcWndPos.bottom - m_heightmapSize.cy*fZoomFactor;

	if (m_cScrollOffset.x > 0) m_cScrollOffset.x = 0;
	if (m_cScrollOffset.y > 0) m_cScrollOffset.y = 0;

	RedrawWindow();
}

void CTerrainViewport::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	if (!GetDocument()) return;

	int iX, iY, iWidth, iHeight;

	/* OLD
	// Blit the image
	dc.SetStretchBltMode(HALFTONE);
	dc.StretchBlt(iX, iY, iWidth, iHeight, &m_dcTerrain, 0, 0, m_heightmapSize.cx, m_heightmapSize.cy, SRCCOPY);
	// Reset brush origin after strech blit.
	dc.SetBrushOrg( 0,0 );
	*/

	// Free any old bitmap first

	if (m_bmpView.GetSafeHandle())
	{
		//m_dcView.SelectObject(NULL);
		::SelectObject(m_dcView.m_hDC, NULL);
		m_bmpView.DeleteObject();
	}

	CRect rc( dc.m_ps.rcPaint );

	// Calculate the position and dimension of the image
	iX = m_cScrollOffset.x;
	iY = m_cScrollOffset.y;
	iWidth = (m_heightmapSize.cx * m_fZoomFactor);
	iHeight = (m_heightmapSize.cy * m_fZoomFactor);

	CPoint hp1 = ViewToHeightmap( CPoint(rc.left,rc.top) );
	CPoint hp2 = ViewToHeightmap( CPoint(rc.right,rc.bottom) );

	// Create bitmap
	BITMAPINFO BmpInfo;
	BmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	BmpInfo.bmiHeader.biWidth = hp2.x - hp1.x;
	BmpInfo.bmiHeader.biHeight = -(hp2.y - hp1.y);
	BmpInfo.bmiHeader.biPlanes = 1;
	BmpInfo.bmiHeader.biBitCount = 32;
	BmpInfo.bmiHeader.biCompression = BI_RGB;
	BmpInfo.bmiHeader.biSizeImage = 0;
	BmpInfo.bmiHeader.biXPelsPerMeter = 0;
	BmpInfo.bmiHeader.biYPelsPerMeter = 0;
	BmpInfo.bmiHeader.biClrUsed = 0;
	BmpInfo.bmiHeader.biClrImportant = 0;

	DWORD *pBits = NULL;
	// Create the DIB section to draw into
	HBITMAP bmpView = ::CreateDIBSection(m_dcView.m_hDC, &BmpInfo, DIB_RGB_COLORS,(void**)&pBits, NULL, 0);
	ASSERT(bmpView);
	m_bmpView.Attach(bmpView);

	// Select the DIB into the DC
	m_dcView.SelectObject( m_bmpView );

	////////////////////////////////////////////////////////////////////////
	// Draw the heightmap into the final image
	////////////////////////////////////////////////////////////////////////
	//m_dcView.BitBlt( 0,0,m_heightmapSize.cx,m_heightmapSize.cy,&m_dcTerrain, 0, 0, SRCCOPY);
	m_dcView.BitBlt( 0, 0,hp2.x-hp1.x,hp2.y-hp1.y, &m_dcTerrain, hp1.x,hp1.y, SRCCOPY);
	//m_dcView.SetBrushOrg( 0,0 );

	DrawStaticObjects( pBits, hp2.x-hp1.x,hp2.y-hp1.y, CRect(hp1,hp2) );

	//dc.BitBlt( 0,0,rc.right,rc.bottom,&m_dcView, 0, 0, SRCCOPY);

	dc.SetStretchBltMode(COLORONCOLOR);
	dc.StretchBlt(iX, iY, iWidth, iHeight, &m_dcView, 0, 0, hp2.x-hp1.x,hp2.y-hp1.y, SRCCOPY);
	//dc.BitBlt(0,0,rc.right,rc.bottom, &m_dcView, 0, 0, SRCCOPY);
	// Reset brush origin after strech blit.
	dc.SetBrushOrg( 0,0 );
	
	/*
	CPoint p;
	p.x = m_viewerPos.x;
	p.y = m_viewerPos.y;
	HMCoordToWndCoord( &p );
	dc.Ellipse( p.x-3,p.y-3,p.x+3,p.y+3 );
	*/
	// Do not call CViewport::OnPaint() for painting messages
}

void CTerrainViewport::DrawStaticObjects( DWORD* pBits,int trgWidth,int trgHeight,CRect rc )
{
}

BOOL CTerrainViewport::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) 
{
	float prevz = GetZoomFactor();

	float z = GetZoomFactor() + (zDelta / 120.0f) * 2.0f;

	// Zoom to mouse position.
	float x = (float)(m_cMousePos.x - m_cScrollOffset.x)/prevz;
	float y = (float)(m_cMousePos.y - m_cScrollOffset.y)/prevz;
	m_cScrollOffset.x = m_cMousePos.x - z*x;
	m_cScrollOffset.y = m_cMousePos.y - z*y;

	SetZoomFactor( z );

	return TRUE;
}

void CTerrainViewport::OnMouseMove(UINT nFlags, CPoint point) 
{
	m_cMousePos = point;

	if (GetViewMode() == ScrollZoomMode)
	{
		CRect rc;
		// You can only scroll while the middle or right mouse button is down
		if (nFlags & MK_RBUTTON || nFlags & MK_MBUTTON)
		{
			if (nFlags & MK_SHIFT)
			{
				// Get the dimensions of the window
				GetClientRect(&rc);

				CRect rc;
				GetClientRect( rc );
				int w = rc.right;
				int h = rc.bottom;

				// Zoom to mouse position.
				float z = m_prevZoomFactor + (point.y - m_cMouseDownPos.y) * 0.1f;
				float x = (float)(m_cMouseDownPos.x - m_prevScrollOffset.cx)/m_prevZoomFactor;
				float y = (float)(m_cMouseDownPos.y - m_prevScrollOffset.cy)/m_prevZoomFactor;
				m_cScrollOffset.x = m_cMouseDownPos.x - z*x;
				m_cScrollOffset.y = m_cMouseDownPos.y - z*y;
				SetZoomFactor( z );
			}
			else
			{
				// Set the new scrolled coordinates
				SetScrollOffset(GetScrollOffset() + (point - m_cPrevMousePos));
			}
			m_cPrevMousePos = point;
		}
		return;
	}

	// Only proceed when the mouse is over the drawing area
	if (nFlags & MK_LBUTTON)
	{
		// Calculate the coordinates in heightmap coordinates

		// Translate into drawing window coordinates
		CPoint hp = ViewToHeightmap(point);

		/*
		if (!(nFlags & MK_CONTROL))
		{
			// Add new static objects
			GetIEditor()->GetDocument()->GetStatObjMap()->DistributeObjects( hp, m_iBrushSize );
		}
		else
		{
			// Remove static objects
			GetIEditor()->GetDocument()->GetStatObjMap()->ClearObjects( hp, m_iBrushSize );
		}
		*/
/*
		// Redraw the modified area
		rcRedraw.left = point.x - m_scaledBrushSize;
		rcRedraw.top = point.y - m_scaledBrushSize;
		rcRedraw.right = point.x + m_scaledBrushSize;
		rcRedraw.bottom = point.y + m_scaledBrushSize;
		RedrawWindow(&rcRedraw, NULL, RDW_NOERASE | RDW_INVALIDATE);
		*/
		RedrawWindow();
	}
	
	CViewport::OnMouseMove(nFlags, point);
}

BOOL CTerrainViewport::OnEraseBkgnd(CDC* pDC) 
{
	return TRUE;
}

void CTerrainViewport::OnSize(UINT nType, int cx, int cy) 
{
	CViewport::OnSize(nType, cx, cy);
	
		////////////////////////////////////////////////////////////////////////
	// Re-evaluate the zoom / scroll offset values
	// TODO: Restore the zoom rectangle instead of resetting it
	////////////////////////////////////////////////////////////////////////

	float fZoomFac1, fZoomFac2;

	fZoomFac1 = (float) cx / m_heightmapSize.cx;
	fZoomFac2 = (float) cx / m_heightmapSize.cy;

	SetZoomFactor((fZoomFac1 < fZoomFac2) ? fZoomFac1 : fZoomFac2);
}

void CTerrainViewport::OnRButtonDown(UINT nFlags, CPoint point) 
{
	if (GetViewMode() != NothingMode)
		return;
/*
	if (GetIEditor()->GetEditMode() == eEditModeCreate)
	{
		// Stop creation.
		GetIEditor()->GetObjectManager()->MouseCreateCallback( this,MOUSECREATE_RPOINT,point,0 );
	}
*/

	// Save the mouse down position
	m_cMouseDownPos = point;
	m_cPrevMousePos = point;

	m_prevZoomFactor = GetZoomFactor();
	m_prevScrollOffset = m_cScrollOffset;

	SetCapture();
	SetViewMode( ScrollZoomMode );
	
	CViewport::OnRButtonDown(nFlags, point);
}

void CTerrainViewport::OnRButtonUp(UINT nFlags, CPoint point) 
{
	ReleaseCapture();
	SetViewMode( NothingMode );
	
	CViewport::OnRButtonUp(nFlags, point);
}

void CTerrainViewport::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	
	CViewport::OnLButtonDown(nFlags, point);
}

void CTerrainViewport::OnLButtonUp(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	
	CViewport::OnLButtonUp(nFlags, point);
}

CPoint CTerrainViewport::ViewToHeightmap( CPoint hp )
{
	hp.x -= m_cScrollOffset.x;
	hp.y -= m_cScrollOffset.y;

	// Scale with the zoom factor
	float z1 = 1.0f / GetZoomFactor();
	int x = FloatToIntRet((float)hp.x * z1);
	int y = FloatToIntRet((float)hp.y * z1);
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x >= m_heightmapSize.cx) x = m_heightmapSize.cx-1;
	if (y >= m_heightmapSize.cx) y = m_heightmapSize.cx-1;

	return CPoint( x,y );
}

CPoint CTerrainViewport::HeightmapToView( CPoint hp )
{
	int x = FloatToIntRet(hp.x * GetZoomFactor());
	int y = FloatToIntRet(hp.y * GetZoomFactor());
	
	// Add the offset
	x += m_cScrollOffset.x;
	y += m_cScrollOffset.y;

	return CPoint( x,y );
}