/** SGX

The   sss    gggg x   x   
     s      g      x x    Engine
      sss   g  gg   x
         s  g   g  x x          Copyright 1995-2008.
     ssss    gggg x   x         Steven Goodwin. All Rights Reserved.
    
This version has been released under the GNU Public License v2.0.

Please see the license files for details.

File: src/sgx/graphics/drawsurface.cpp

*/

#include "sgx/core/core.hpp"
#include "domains/graphics/graphics.hpp"
#include "sgx/graphics/graphics.hpp"

//
// Maintenance and handlers
//
CSGXDrawSurface::CSGXDrawSurface(tUINT32 w, tUINT32 h) {
	m_Width = w;
	m_Height = h;
	m_CurrentDrawColor = sgxColorRGB::White;
	m_CurrentFillColor = sgxColorRGB::Black;
	m_pTexture = NULL; 
	m_Xform.m.identity();

	clearClipRect();
}

void
CSGXDrawSurface::resize(tUINT32 w, tUINT32 h) {
	m_Width = w;
	m_Height = h;
}

tUINT32 CSGXDrawSurface::getWidth() {
	return m_Width;
}

tUINT32 CSGXDrawSurface::getHeight() {
	return m_Height;
}


sgxColorRGB CSGXDrawSurface::setDrawColor(const sgxColorRGB &col) {
	sgxColorRGB prev = m_CurrentDrawColor;
	m_CurrentDrawColor = col;
	return prev;
}

sgxColorRGB CSGXDrawSurface::setFillColor(const sgxColorRGB &col) {
	sgxColorRGB prev = m_CurrentFillColor;
	m_CurrentFillColor = col;
	m_UseColorFill = TRUE;

	CSGXGraphicsEngine::get()->setColor(col);
	return prev;
}

sgxColorRGB CSGXDrawSurface::setFillColor(const sgxColorRGBA &col) {
	sgxColorRGB prev = m_CurrentFillColor;
	m_CurrentFillColor = col.rgb();
	m_UseColorFill = TRUE;

	CSGXGraphicsEngine::get()->setColor(col);
	return prev;
}

void CSGXDrawSurface::clearFillColor() {
	m_UseColorFill = FALSE;
}

void CSGXDrawSurface::getDrawColor(sgxColorRGB &col) const {
	col = m_CurrentDrawColor;
}

void CSGXDrawSurface::getFillColor(sgxColorRGB &col) const {
	col = m_CurrentFillColor;
}

void CSGXDrawSurface::setFillTextureRegion(const tUINT32 region) {
 SGX_UNUSED(region);

	if (m_pTexture) {
		if (region >= 0 && region < m_pTexture->getNumRegions()) {
			m_CurrentUVRegion = region;
			m_pTexture->getRegionArea(region, m_CurrentUVPixels);
			m_pTexture->getRegion(region, m_CurrentUV);
		}
	}
}

void CSGXDrawSurface::setFillTextureRegion(const tREAL32 u1, const tREAL32 v1, const tREAL32 u2, const tREAL32 v2) {
	if (m_pTexture) {
	
		tREAL32 w = (tREAL32)m_pTexture->getWidth();
		tREAL32 h = (tREAL32)m_pTexture->getHeight();

		m_CurrentUVPixels.left = (tINT32)(u1 * w);
		m_CurrentUVPixels.top = (tINT32)(v1 * h);
		m_CurrentUVPixels.right = (tINT32)(u2 * w);
		m_CurrentUVPixels.bottom = (tINT32)(v2 * h);
		
		m_CurrentUV.u1 = u1;
		m_CurrentUV.v1 = v1;
		m_CurrentUV.u2 = u2;
		m_CurrentUV.v2 = v2;
		
		m_CurrentUVRegion = -1;
	}

}

void CSGXDrawSurface::setFillTextureRegion(const tUINT32 u1, const tUINT32 v1, const tUINT32 u2, const tUINT32 v2) {
	if (m_pTexture) {
	
		tREAL32 w = (tREAL32)m_pTexture->getWidth();
		tREAL32 h = (tREAL32)m_pTexture->getHeight();

		m_CurrentUVPixels.left = u1;
		m_CurrentUVPixels.top = v1;
		m_CurrentUVPixels.right = u2;
		m_CurrentUVPixels.bottom = v2;
		
		m_CurrentUV.u1 = u1/w;
		m_CurrentUV.v1 = v1/h;
		m_CurrentUV.u2 = u2/w;
		m_CurrentUV.v2 = v2/h;
		
		m_CurrentUVRegion = -1;
	}
}

void CSGXDrawSurface::setFillTextureRegion(const sgxUV &uv) {
	if (m_pTexture) {
		setFillTextureRegion(uv.u1, uv.v1, uv.u2, uv.v2);
	}
}

void CSGXDrawSurface::setFillTexture(const CSGXTexture *pTexture, const tREAL32 u1, const tREAL32 v1, const tREAL32 u2, const tREAL32 v2) {
	m_pTexture = pTexture;
	setFillTextureRegion(u1,v1,u2,v2);
}

void CSGXDrawSurface::setFillTexture(const CSGXTexture *pTexture, const tUINT32 region) {
	CSGXTextureManager::get()->setCurrentTexture(pTexture);
	m_pTexture = pTexture;
	setFillTextureRegion(region);
}


void CSGXDrawSurface::clearFillTexture() {
	m_pTexture = NULL;
	CSGXTextureManager::get()->applyTexture(m_pTexture);
}
//
// The only retrieval method is to get the whole xform. The individual components
// e.g. rotate X, translate Y, etc are lost since the order matters, and we are
// not concerned with keeping a list of them.
//
void CSGXDrawSurface::getRenderTransform(sgxTransform2D &xform) const  {
	xform.m = m_Xform.m;
	xform.invalidate();
}

//
// Explicitly setting the position of all future renders
//
void CSGXDrawSurface::resetRenderTransform() {
	m_Xform.m.identity();
	m_Xform.invalidate();
}

void CSGXDrawSurface::setRenderTransform(const sgxTransform2D &xform) {
	m_Xform.m = xform.m;
	m_Xform.invalidate();
}

void CSGXDrawSurface::setRenderTransformPosition(const sgxVector2 &position) {
	m_Xform.m.setPosition(position);
	m_Xform.invalidate();
}


void CSGXDrawSurface::setRenderTransformScale(const tREAL32 &axisScale) {
	m_Xform.m.normalize();
	scaleRenderTransform(axisScale);
}

void CSGXDrawSurface::setRenderTransformScale(const sgxVector2 &axisScales) {
	m_Xform.m.normalize();
	scaleRenderTransform(axisScales);
}

//
// Modify the position of all future renders, by an amount
//
void CSGXDrawSurface::translateRenderTransform(const sgxVector2 &offset) {
	m_Xform.m.translate(offset);
	m_Xform.invalidate();
}

void CSGXDrawSurface::rotateXRenderTransform(const tREAL32 rotateXRadians) {
	m_Xform.m.setRotateX(rotateXRadians);
	m_Xform.invalidate();
}

void CSGXDrawSurface::rotateYRenderTransform(const tREAL32 rotateYRadians) {
	m_Xform.m.setRotateY(rotateYRadians);
	m_Xform.invalidate();
}

void CSGXDrawSurface::rotateZRenderTransform(const tREAL32 rotateZRadians) {
	m_Xform.m.setRotateZ(rotateZRadians);
	m_Xform.invalidate();
}

void CSGXDrawSurface::scaleRenderTransform(const tREAL32 &axisScale) {
	m_Xform.m.scale(axisScale);
	m_Xform.invalidate();
}

void CSGXDrawSurface::scaleRenderTransform(const sgxVector2 &axisScales) {
	m_Xform.m.scale(axisScales);
	m_Xform.invalidate();
}


//
// Rendering lines, drawn in the current 'draw ink' colour.
//
void CSGXDrawSurface::drawPoint(const sgxPoint2i &pt) {
	drawPoint(pt.x, pt.y);
}

void CSGXDrawSurface::drawPoint(const tPOINT x, const tPOINT y) {
	// Graphical component would go here
	m_fLastX = x;
	m_fLastY = y;
}

void CSGXDrawSurface::setLinePoint(const tPOINT x, const tPOINT y) {
	m_fLastX = x;
	m_fLastY = y;
}

void CSGXDrawSurface::drawLine(const sgxPoint2i &ptFrom, const sgxPoint2i &ptTo) {
	setLinePoint(ptFrom.x, ptFrom.y);
	drawLineTo(ptTo.x, ptTo.y);
}

void CSGXDrawSurface::drawLine(const tPOINT x1, const tPOINT y1, const tPOINT x2, const tPOINT y2) {
	setLinePoint(x1, y1);
	drawLineTo(x2, y2);
}

void CSGXDrawSurface::drawLineTo(const sgxPoint2i &pt) {
	drawLineTo(pt.x, pt.y);
}

void CSGXDrawSurface::drawLineTo(const tPOINT x, const tPOINT y) {
	// This is the bottom layer of a 'double chance' function
	// update stats
	m_fLastX = x;
	m_fLastY = y;
}

void CSGXDrawSurface::drawRectangle(const sgxPoint2i &ptFrom, const sgxPoint2i &ptTo) {
	drawRectangle(ptFrom.x, ptFrom.y, ptTo.x, ptTo.y);
}

void CSGXDrawSurface::drawRectangle(const tPOINT x1, const tPOINT y1, const tPOINT x2, const tPOINT y2) {
	drawLine(x1,y1, x2, y1);
	drawLineTo(x2,y2);
	drawLineTo(x1,y2);
	drawLineTo(x1,y1);
}

void CSGXDrawSurface::drawCircle(const sgxCircle2D &circle) {
	SGX_UNUSED(circle);
}


//
// Solid rendering, using either the texture (usual) or solida colour
//
void CSGXDrawSurface::fillPoint(const sgxPoint2i &focus, const tFocusPoint &fp) {
	fillPoint(focus.x, focus.y, fp);
}

void CSGXDrawSurface::fillPoint(const tPOINT x, const tPOINT y, const tFocusPoint &fp) {	
	if (!m_pTexture) {
		return;
	}

	sgxDimension2i size;
	
	size.width = m_CurrentUVPixels.right - m_CurrentUVPixels.left;
	size.height = m_CurrentUVPixels.bottom - m_CurrentUVPixels.top;

	tUINT32 width2 = size.width/2;
	tUINT32 height2 = size.height/2;

	sgxPoint2i pt1(x,y);

	switch(fp) {
		case eFromCentre:
			pt1.x -= width2;
			pt1.y -= height2;
			break;
		case eFromTopLeft:
			// as given
			break;
		case eFromTopRight:
			pt1.x -= size.width;
			break;
		case eFromBottomLeft:
			pt1.y -= size.height;
			break;
		case eFromBottomRight:
			pt1.x -= size.width;
			pt1.y -= size.height;
			break;
	}

	fillQuad(pt1.x,pt1.y,  pt1.x+size.width,pt1.y,  pt1.x+size.width,pt1.y+size.height,  pt1.x,pt1.y+size.height);
}

void CSGXDrawSurface::fillPoint(const sgxPoint2i &center, tPOINT size) {
	sgxPoint2i pt1(center);
	pt1 -= (size/2);

	fillQuad(pt1.x,pt1.y,  pt1.x+size,pt1.y,  pt1.x+size,pt1.y+size,  pt1.x,pt1.y+size);
}

void CSGXDrawSurface::fillCircle(const sgxCircle2D &circle) {
	SGX_UNUSED(circle);
// Q. are the shapes in the graphics domain all derived from one type?
// Q. Do they exist for 2D?
// Q. create 2D & 3D versions in: domain/geometry, 
}


void CSGXDrawSurface::tileBox() {
	tileBox(0,0, (int)m_Width, m_Height);
}

void CSGXDrawSurface::tileBox(const sgxPoint2i &ptFrom, const sgxPoint2i &ptTo) {
	tileBox(ptFrom.x, ptFrom.y, ptTo.x, ptTo.y);
}

void CSGXDrawSurface::tileBox(const tPOINT x1, const tPOINT y1, const tPOINT x2, const tPOINT y2) {
	tUINT32 tw = (m_CurrentUVPixels.right - m_CurrentUVPixels.left);
	tUINT32 th = (m_CurrentUVPixels.bottom - m_CurrentUVPixels.top);

	sgxAssert(tw && th);

	CSGXTextureManager::get()->applyTexture(m_pTexture);
	CSGXTextureManager::get()->applyTextureRegion(m_CurrentUV);

	sgxPoint2i topleft(x1, y1);
	sgxPoint2i bottomright(x2, y2);

	for(;topleft.y<y2;topleft.y+=th) {
		for(topleft.x=x1;topleft.x<x2;topleft.x+=tw) {
			bottomright.x = topleft.x + tw;
			bottomright.y = topleft.y + th;
			fillBox(topleft, bottomright);
		}
	}
}


/**
A special version which applies different textures to the area,
according to whether it's the edge or not.

The texture specified should be the first in the sequence, as governed by
the order:

0 1 2
3 4 5
6 7 8

*/
void 
CSGXDrawSurface::surroundBox(const tPOINT x1, const tPOINT y1, const tPOINT x2, const tPOINT y2, tBOOL fillCentre) {
tUINT32 r = m_CurrentUVRegion;
tUINT32 tw = m_pTexture->getRegionWidth(r+1);	// top centre	- all tiles should be square
tUINT32 th = m_pTexture->getRegionHeight(r+3);	// left middle
tUINT32 tilesOnWidth = (x2-x1-2) / tw;
tUINT32 tilesOnHeight = (y2-y1-2) / th;
sgxPoint2i topleft(x1, y1);
sgxPoint2i bottomright;

	for(topleft.y=y1;topleft.y<y2;topleft.y+=th) {
		for(topleft.x=x1;topleft.x<x2;topleft.x+=tw) {
			tUINT32 portion;
			
			if (topleft.y == y1) {	// top edge
				portion = 0;
			} else if (topleft.y+tw >= (tUINT32)y2) {	// bottom edge
				portion = 6;
			} else {	// central span
				portion = 3;
			}
			//
			if (topleft.x == x1) {	// left edge
				// null - it's already correct
			} else if (topleft.x+tw >= (tUINT32)x2) {	// right edge
				portion += 2;
			} else {	// central column
				portion += 1;
			}
			//
			bottomright.x = topleft.x + tw;
			bottomright.y = topleft.y + th;
			if (fillCentre || portion != 4) {
				setFillTextureRegion(r+portion);
				fillBox(topleft, bottomright);
			}
		}
	}
 
}

void CSGXDrawSurface::fillBox(const sgxPoint2i &ptFrom, const sgxPoint2i &ptTo) {
	fillQuad(ptFrom.x,ptFrom.y,   ptTo.x,ptFrom.y,   ptTo.x,ptTo.y,  ptFrom.x,ptTo.y);
}

void CSGXDrawSurface::fillBox() {
	fillQuad(0,0, m_Width,0, m_Width,m_Height, 0,m_Height);
}

void CSGXDrawSurface::fillBox(const tPOINT x1, const tPOINT y1, const tPOINT x2, const tPOINT y2) {
// Could be color-filled, texture-filled, or both. (i.e. with vertex colouring)
	fillQuad(x1,y1, x2,y1, x2,y2, x1,y2);
}
 
void CSGXDrawSurface::fillQuad(const tPOINT x1, const tPOINT y1, const tPOINT x2, const tPOINT y2, const tPOINT x3, const tPOINT y3, const tPOINT x4, const tPOINT y4) {
// Apply m_Xform.m and draw
	CSGXTextureManager::get()->applyTexture(m_pTexture);
	CSGXTextureManager::get()->applyTextureRegion(m_CurrentUV);

	CSGXGraphicsEngine::get()->setClipRect(m_CurrentClipArea);

	if (m_UseColorFill) {
	}

	if (m_pTexture) {
	}
	
	SGX_UNUSED(x1);
	SGX_UNUSED(y1);
	SGX_UNUSED(x2);
	SGX_UNUSED(y2);
	SGX_UNUSED(x3);
	SGX_UNUSED(y3);
	SGX_UNUSED(x4);
	SGX_UNUSED(y4);

}

void CSGXDrawSurface::fillPolygon(const sgxVector<sgxPoint2i> &ptList) {
	SGX_UNUSED(ptList);
}


void 
CSGXDrawSurface::clearClipRect() {
	setClipRect(0, 0, getWidth(), getHeight());
}

void 
CSGXDrawSurface::setClipRect(const tPOINT x1, const tPOINT y1, const tPOINT x2, const tPOINT y2) {
	m_CurrentClipArea.left = x1;
	m_CurrentClipArea.top = y1;
	m_CurrentClipArea.right = x2;
	m_CurrentClipArea.bottom = y2;
}

void 
CSGXDrawSurface::setClipRect(const sgxRect2i &rc) {
	setClipRect(rc.left,rc.top,rc.right,rc.bottom);
}

void CSGXDrawSurface::getClipRect(sgxRect2i &rc) {
	rc = m_CurrentClipArea;
}