/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2012  Warzone 2100 Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/** \file
 *  Matrix manipulation functions.
 */

#include "lib/framework/frame.h"
#include "lib/framework/opengl.h"

#include "lib/framework/fixedpoint.h"
#include "lib/ivis_opengl/pieclip.h"
#include "piematrix.h"
#include "lib/ivis_opengl/piemode.h"

/***************************************************************************/
/*
 *	Local Definitions
 */
/***************************************************************************/

#define MATRIX_MAX 8

/*
 * Matrices are of this form:
 * [ a d g j ]
 * [ b e h k ]
 * [ c f i l ]
 * [ 0 0 0 1 ]
 */
struct SDMATRIX
{
	SDWORD a, b, c,
	       d, e, f,
	       g, h, i,
	       j, k, l;
};
static SDMATRIX	aMatrixStack[MATRIX_MAX];
static SDMATRIX *psMatrix = &aMatrixStack[0];

bool drawing_interface = true;

//*************************************************************************

static const SDMATRIX Identitymatrix = {
	FP12_MULTIPLIER,                  0,               0,
	              0,    FP12_MULTIPLIER,               0,
	              0,                  0, FP12_MULTIPLIER,
	              0,                  0,               0,
};
static SDWORD _MATRIX_INDEX;

//*************************************************************************
//*** reset transformation matrix stack and make current identity
//*
//******

static void pie_MatReset(void)
{
	psMatrix = &aMatrixStack[0];

	// make 1st matrix identity
	*psMatrix = Identitymatrix;

	glLoadIdentity();
}


//*************************************************************************
//*** create new matrix from current transformation matrix and make current
//*
//******

void pie_MatBegin(void)
{
	_MATRIX_INDEX++;
	ASSERT( _MATRIX_INDEX < MATRIX_MAX, "pie_MatBegin past top of the stack" );

	psMatrix++;
	aMatrixStack[_MATRIX_INDEX] = aMatrixStack[_MATRIX_INDEX-1];

	glPushMatrix();
}


//*************************************************************************
//*** make current transformation matrix previous one on stack
//*
//******

void pie_MatEnd(void)
{
	_MATRIX_INDEX--;
	ASSERT( _MATRIX_INDEX >= 0, "pie_MatEnd of the bottom of the stack" );

	psMatrix--;

	glPopMatrix();
}


void pie_TRANSLATE(int32_t x, int32_t y, int32_t z)
{
	/*
	 * curMatrix = curMatrix . translationMatrix(x, y, z)
	 *
	 *                         [ 1 0 0 x ]
	 *                         [ 0 1 0 y ]
	 * curMatrix = curMatrix . [ 0 0 1 z ]
	 *                         [ 0 0 0 1 ]
	 */
	psMatrix->j += x * psMatrix->a + y * psMatrix->d + z * psMatrix->g;
	psMatrix->k += x * psMatrix->b + y * psMatrix->e + z * psMatrix->h;
	psMatrix->l += x * psMatrix->c + y * psMatrix->f + z * psMatrix->i;

	glTranslatef(x, y, z);
}

//*************************************************************************
//*** matrix scale current transformation matrix
//*
//******
void pie_MatScale(float scale)
{
	/*
	 * s := scale
	 * curMatrix = curMatrix . scaleMatrix(s, s, s)
	 *
	 *                         [ s 0 0 0 ]
	 *                         [ 0 s 0 0 ]
	 * curMatrix = curMatrix . [ 0 0 s 0 ]
	 *                         [ 0 0 0 1 ]
	 *
	 * curMatrix = scale * curMatrix
	 */

	psMatrix->a = psMatrix->a * scale;
	psMatrix->b = psMatrix->b * scale;
	psMatrix->c = psMatrix->c * scale;

	psMatrix->d = psMatrix->d * scale;
	psMatrix->e = psMatrix->e * scale;
	psMatrix->f = psMatrix->f * scale;

	psMatrix->g = psMatrix->g * scale;
	psMatrix->h = psMatrix->h * scale;
	psMatrix->i = psMatrix->i * scale;

	glScalef(scale, scale, scale);
}


//*************************************************************************
//*** matrix rotate y (yaw) current transformation matrix
//*
//******

void pie_MatRotY(uint16_t y)
{
	/*
	 * a := angle
	 * c := cos(a)
	 * s := sin(a)
	 *
	 * curMatrix = curMatrix . rotationMatrix(a, 0, 1, 0)
	 *
	 *                         [  c  0  s  0 ]
	 *                         [  0  1  0  0 ]
	 * curMatrix = curMatrix . [ -s  0  c  0 ]
	 *                         [  0  0  0  1 ]
	 */
	if (y != 0)
	{
		int t;
		int64_t cra = iCos(y), sra = iSin(y);

		t = (cra*psMatrix->a - sra*psMatrix->g)>>16;
		psMatrix->g = (sra*psMatrix->a + cra*psMatrix->g)>>16;
		psMatrix->a = t;

		t = (cra*psMatrix->b - sra*psMatrix->h)>>16;
		psMatrix->h = (sra*psMatrix->b + cra*psMatrix->h)>>16;
		psMatrix->b = t;

		t = (cra*psMatrix->c - sra*psMatrix->i)>>16;
		psMatrix->i = (sra*psMatrix->c + cra*psMatrix->i)>>16;
		psMatrix->c = t;

		glRotatef(UNDEG(y), 0.0f, 1.0f, 0.0f);
	}
}


//*************************************************************************
//*** matrix rotate z (roll) current transformation matrix
//*
//******

void pie_MatRotZ(uint16_t z)
{
	/*
	 * a := angle
	 * c := cos(a)
	 * s := sin(a)
	 *
	 * curMatrix = curMatrix . rotationMatrix(a, 0, 0, 1)
	 *
	 *                         [ c  -s  0  0 ]
	 *                         [ s   c  0  0 ]
	 * curMatrix = curMatrix . [ 0   0  1  0 ]
	 *                         [ 0   0  0  1 ]
	 */
	if (z != 0)
	{
		int t;
		int64_t cra = iCos(z), sra = iSin(z);

		t = (cra*psMatrix->a + sra*psMatrix->d)>>16;
		psMatrix->d = (cra*psMatrix->d - sra*psMatrix->a)>>16;
		psMatrix->a = t;

		t = (cra*psMatrix->b + sra*psMatrix->e)>>16;
		psMatrix->e = (cra*psMatrix->e - sra*psMatrix->b)>>16;
		psMatrix->b = t;

		t = (cra*psMatrix->c + sra*psMatrix->f)>>16;
		psMatrix->f = (cra*psMatrix->f - sra*psMatrix->c)>>16;
		psMatrix->c = t;

		glRotatef(UNDEG(z), 0.0f, 0.0f, 1.0f);
	}
}


//*************************************************************************
//*** matrix rotate x (pitch) current transformation matrix
//*
//******

void pie_MatRotX(uint16_t x)
{
	/*
	 * a := angle
	 * c := cos(a)
	 * s := sin(a)
	 *
	 * curMatrix = curMatrix . rotationMatrix(a, 0, 0, 1)
	 *
	 *                         [ 1  0   0  0 ]
	 *                         [ 0  c  -s  0 ]
	 * curMatrix = curMatrix . [ 0  s   c  0 ]
	 *                         [ 0  0   0  1 ]
	 */
	if (x != 0.f)
	{
		int t;
		int64_t cra = iCos(x), sra = iSin(x);

		t = (cra*psMatrix->d + sra*psMatrix->g)>>16;
		psMatrix->g = (cra*psMatrix->g - sra*psMatrix->d)>>16;
		psMatrix->d = t;

		t = (cra*psMatrix->e + sra*psMatrix->h)>>16;
		psMatrix->h = (cra*psMatrix->h - sra*psMatrix->e)>>16;
		psMatrix->e = t;

		t = (cra*psMatrix->f + sra*psMatrix->i)>>16;
		psMatrix->i = (cra*psMatrix->i - sra*psMatrix->f)>>16;
		psMatrix->f = t;

		glRotatef(UNDEG(x), 1.0f, 0.0f, 0.0f);
	}
}


/*!
 * 3D vector perspective projection
 * Projects 3D vector into 2D screen space
 * \param v3d       3D vector to project
 * \param[out] v2d  resulting 2D vector
 * \return projected z component of v2d
 */
int32_t pie_RotateProject(const Vector3i *v3d, Vector2i *v2d)
{
	/*
	 * v = curMatrix . v3d
	 */
	Vector3i v(
		v3d->x * psMatrix->a + v3d->y * psMatrix->d + v3d->z * psMatrix->g + psMatrix->j,
		v3d->x * psMatrix->b + v3d->y * psMatrix->e + v3d->z * psMatrix->h + psMatrix->k,
		v3d->x * psMatrix->c + v3d->y * psMatrix->f + v3d->z * psMatrix->i + psMatrix->l
	);

	const int zz = v.z >> STRETCHED_Z_SHIFT;

	if (zz < MIN_STRETCHED_Z)
	{
		v2d->x = LONG_WAY; //just along way off screen
		v2d->y = LONG_WAY;
	}
	else
	{
		v2d->x = rendSurface.xcentre + (v.x / zz);
		v2d->y = rendSurface.ycentre - (v.y / zz);
	}

	return zz;
}

void pie_PerspectiveBegin(void)
{
	const float width = pie_GetVideoBufferWidth();
	const float height = pie_GetVideoBufferHeight();
	const float xangle = width/6.0f;
	const float yangle = height/6.0f;

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glTranslatef(
		(2 * rendSurface.xcentre-width) / width,
		(height - 2 * rendSurface.ycentre) / height,
		0);
	glFrustum(-xangle, xangle, -yangle, yangle, 330, 100000);
	glScalef(1, 1, -1);
	glMatrixMode(GL_MODELVIEW);
}

void pie_PerspectiveEnd(void)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0.0f, (double) pie_GetVideoBufferWidth(), (double) pie_GetVideoBufferHeight(), 0.0f, 1.0f, -1.0f);
	glMatrixMode(GL_MODELVIEW);
}

void pie_Begin3DScene(void)
{
	glDepthRange(0.1, 1);
	drawing_interface = false;
}

void pie_BeginInterface(void)
{
	glDepthRange(0, 0.1);
	drawing_interface = true;
}

void pie_SetGeometricOffset(int x, int y)
{
	rendSurface.xcentre = x;
	rendSurface.ycentre = y;
}

/** Sets up transformation matrices/quaternions and trig tables
 */
void pie_MatInit(void)
{
	// init matrix/quat stack
	pie_MatReset();
}

