/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * Additional copyright for this file:
 * Copyright (C) 1999-2000 Revolution Software Ltd.
 * This code is based on source code created by Revolution Software,
 * used with permission.
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "engines/icb/common/px_common.h"
#include "engines/icb/p4.h"
#include "engines/icb/p4_generic.h"
#include "engines/icb/global_objects_psx.h"
#include "engines/icb/gfx/psx_pcdefines.h"
#include "engines/icb/common/px_capri_maths.h"
#include "engines/icb/drawpoly_pc.h"
#include "engines/icb/light_pc.h"
#include "engines/icb/shade_pc.h"

namespace ICB {

#if _PSX_ON_PC == 1

// Globals telling you "extra" parameters about the lights
extern uint32 useLampWidth;
extern uint32 useLampBounce;
extern int32 lampWidth[3];
extern int32 lampBounce[3];

#else // #if _PSX_ON_PC == 1

// Globals telling you "extra" parameters about the lights
uint32 useLampWidth;
uint32 useLampBounce;
int32 lampWidth[3];
int32 lampBounce[3];

#endif // #if _PSX_ON_PC == 1

//------------------------------------------------------------------------
// Set the GTE constant light variables
// Sets the direciton lights colour matrix
// Sets the ambient colour
// Fills in the lightDirects matrix

// Selects the 3 brightest lamps from the lamplist
// at the actors position and puts them into the
// GTE light matrix and the lightDirects matrix
//

int32 prepareLightsPC(VECTOR *pos, PSXrgb *ambient, PSXLampList *lamplist, PSXShadeList *shadelist, MATRIXPC *lDirects, LampInfo *linfo) {
	// Disable the slow features as default
	useLampBounce = 0;
	useLampWidth = 0;
	// Zero out the "extra" parameters
	lampWidth[0] = 0;
	lampWidth[1] = 0;
	lampWidth[2] = 0;
	lampBounce[0] = 0;
	lampBounce[1] = 0;
	lampBounce[2] = 0;

	if (lamplist == nullptr) {
		return prepareLightsGlobalPC(lDirects);
	}

	LampInfo lampInfo[MAX_NUMBER_LIGHTS + 1];

	// Turn them off
	linfo[0].intens = 0;
	linfo[1].intens = 0;
	linfo[2].intens = 0;

	uint32 brightest = MAX_NUMBER_LIGHTS;
	uint32 middle = MAX_NUMBER_LIGHTS;
	uint32 darkest = MAX_NUMBER_LIGHTS;
	lampInfo[brightest].intens = 0;
	lampInfo[brightest].index = MAX_NUMBER_LIGHTS;

	MATRIXPC lColours;

	// Zero out the matrix first off
	lDirects->m[0][0] = 0;
	lDirects->m[0][1] = 0;
	lDirects->m[0][2] = 0;
	lDirects->m[1][0] = 0;
	lDirects->m[1][1] = 0;
	lDirects->m[1][2] = 0;
	lDirects->m[2][0] = 0;
	lDirects->m[2][1] = 0;
	lDirects->m[2][2] = 0;

	lColours.m[0][0] = 0;
	lColours.m[0][1] = 0;
	lColours.m[0][2] = 0;
	lColours.m[1][0] = 0;
	lColours.m[1][1] = 0;
	lColours.m[1][2] = 0;
	lColours.m[2][0] = 0;
	lColours.m[2][1] = 0;
	lColours.m[2][2] = 0;

	uint32 num = lamplist->n;

	PSXLamp *plamp = nullptr;
	PSXLampState *plampstate;

	uint32 state;
	uint32 sstate;
	VECTOR dir;
	VECTOR normdir;
	uint32 i, s;

	uint32 *pstate = lamplist->states;
	PSXLamp **pplamp = lamplist->lamps;

	uint32 ns = shadelist->n;

	PSXShade *pshade;
	ShadeQuad *pshadestate;

	uint32 *psstate;
	PSXShade **ppshade;

	uint32 inten, m;
	for (i = 0; i < num; pplamp++, pstate++, i++) {
		// Make the direction vector
		plamp = *pplamp;
		state = *pstate;

		plampstate = plamp->states + state;

		// Ignore the lamp if it is switched off
		m = plampstate->m;

		// For animating lights which are turned off - turn them off
		// after the 3 brightest lights have been selected !
		if (plamp->nStates > 1)
			m = 128;

		if (m == 0)
			continue;

		// Squared distance to the lamp
		dir.vx = pos->vx - plampstate->pos.vx;
		dir.vy = pos->vy - plampstate->pos.vy;
		dir.vz = pos->vz - plampstate->pos.vz;
		// VectorNoraml returns the sum of the squares
		// i.e. the squared distance
		// Normalize the lamp_actor vector to 1.0
		uint32 rr = VectorNormal(&dir, &normdir);

#define DEBUG_PREPARE_LIGHTS 0
#if DEBUG_PREPARE_LIGHTS
		printf("Lamp %d rr %d actor %d %d %d lamp %d %d %d\nafs2 %d afe2 %d ans2 %d ane2 %d", i, rr, pos->vx, pos->vy, pos->vz, plampstate->pos.vx, plampstate->pos.vy,
		       plampstate->pos.vz, plampstate->ans2, plampstate->ane2, plampstate->afs2, plampstate->afe2);
#endif // #if DEBUG_PREPARE_LIGHTS

		// Get the light direction
		// non-directional lamps e.g. bulbs, OMNI's
		// then the direction is vector from lamp to actor i.e. dir
		// For directional lamps, the -ve 3rd column of the matrix
		// is what we want because MAX definition is the lamp direction
		// is 0,0,-1 in the light frame

		// The attenuation law is
		//
		// Intensity
		// multiplier
		//   |
		//   v         near_end             far_start
		//                 |                   |
		// 1.0 |           *********************
		//     |          *                     *
		//     |         *                       *
		//     |        *                         *
		//     |       *                           *
		//     |      *                             *
		//     |     *                               *
		//     |    *                                 *
		// 0.0 *****                                   *************
		//         |                                   |
		//       near_start                         far_end
		//
		//                   increasing distance --->

		// Ignore the lamp if rr > atten_far_end^2
		// In pre-process of lights if atten_far_use = 0
		// then it sets atten_far_end to a very big number

		// RLP schema >= 4 squared distances are stored in the light file
		uint32 afe2 = plampstate->afe2;
		// if ( ( afe2 < ATTEN_MAX_DISTANCE*ATTEN_MAX_DISTANCE ) && ( rr > afe2 ) ) continue;
		if (rr > afe2)
			continue;

		// RLP schema >= 4 squared distances are stored in the light file
		uint32 ans2 = plampstate->ans2;

		// Ignore the lamp if rr < atten_near_start^2
		// if ( ( ans2 < ATTEN_MAX_DISTANCE*ATTEN_MAX_DISTANCE ) && ( rr < ans2 ) ) continue;
		if (rr < ans2)
			continue;

		// RLP schema >= 4 squared distances are stored in the light file
		uint32 ane2 = plampstate->ane2;
		uint32 afs2 = plampstate->afs2;

		// Are we in the increasing portion ?
		if (rr < ane2) {
			m = ((rr - ans2) * m) / (ane2 - ans2);
		}
		// else are we in the decreasing portion ?
		else if ((afs2 < ATTEN_MAX_DISTANCE * ATTEN_MAX_DISTANCE) && (rr > afs2)) {
			m = ((afe2 - rr) * m) / (afe2 - afs2);
		}
// else we are in the constant portion : i.e. do nothing

#if DEBUG_PREPARE_LIGHTS
		printf("Lamp %d rr %d m %d afe2-rr %d afe2-afs2 %d", i, rr, m, (afe2 - rr), (afe2 - afs2));
#endif // #if DEBUG_PREPARE_LIGHTS

		// Ignore black lamps caused by the attenuation laws
		if (m == 0)
			continue;

		// For conical beams i.e. spot lights
		//
		// If the light has a beam then take into account the effect on m due
		// to angle between the light->actor vector and the light's direction
		//
		// The attenuation law is
		//
		// At angles less than beam_angle/2 then multiplier = 1.0
		// At angles beyond beam_softness/2 then multiplier = 0.0
		// Between these angles have straight line law
		//
		// Intensity
		// multiplier
		//   |
		//   v         beam_angle/2
		//                 |
		// 1.0 |************
		//     |            *
		//     |             *
		//     |              *
		//     |               *
		//     |                *
		//     |                 *
		// 0.0 |                  *************
		//                        |
		//                 beam_softness/2
		//
		//     -----------------> angle between actor and the light direction
		//

		if (plamp->type == SPOT_LIGHT) {
			// ba stored in the file is cos(beam_angle/2), so it can be
			// compared directly against the cos term we compute from the
			// dot product of light direction & light-actor direction
			int32 ba = plamp->ba;
			int32 bs = plamp->bs;
			// Get cos of the angle between light direciton & light-actor direction
			// These are both normalised to 4096, so end result has 1.0 = 4096*4096
			// Light direction is -ve z column of matrix (but normdir = -(light to actor vector))
			int32 cosa = normdir.vx * plampstate->vx + normdir.vy * plampstate->vy + normdir.vz * plampstate->vz;

#if DEBUG_PREPARE_LIGHTS
			printf("actor %d %d %d lamp %d %d %d dir %d %d %d cosa %d bs %d ba %d", pos->vx, pos->vy, pos->vz, plampstate->pos.vx, plampstate->pos.vy,
			       plampstate->pos.vz, plampstate->vx, plampstate->vy, plampstate->vz, (cosa >> 12), bs, ba);
#endif // #if DEBUG_PREPARE_LIGHTS

			// is the man more than 90 deg away from the beam direction
			if (cosa <= 0)
				continue;

			// reduce its range so that 1.0=4096
			cosa = cosa >> 12;

			// is the man outside the beam ?
			if (cosa < bs)
				continue;

			// is the man inside the decreasing portion
			if (cosa < ba) {
				m = ((cosa - bs) * m) / (ba - bs);
			}
// else the man is inside the constant portion
// and no need to change m
#if DEBUG_PREPARE_LIGHTS
			printf("m %d", m);
#endif // #if DEBUG_PREPARE_LIGHTS
		}

		// For cylindrical beams i.e. direct lights
		//
		// If the light has a beam then take into account the effect on m due
		// to the perpendicular distance between the light->actor vector and the light's direction vector
		//
		// The attenuation law is
		//
		// At distances less than beam_angle then multiplier = 1.0
		// At distance beyond beam_softness then multiplier = 0.0
		// Between these distance have straight line law
		//
		// Intensity
		// multiplier
		//   |
		//   v         beam_angle
		//                 |
		// 1.0 |************
		//     |            *
		//     |             *
		//     |              *
		//     |               *
		//     |                *
		//     |                 *
		// 0.0 |                  *************
		//                        |
		//                 beam_softness
		//
		//     -----------------> perpendicular distance between actor and the light direction
		//
		//    *  l - light position
		//    |+ -
		//    | + B
		//    |  +
		// A  |   +
		//    |    +
		//    |  /  +
		//    | / P  +
		//    |/      +|
		//    *       -  n - Light direction (normalised to unit vector)
		//    r          -
		//    -
		//
		// Actor position
		//
		// By pythagorous
		//
		// perpendicular distance = P = SQRT( A*A - B*B )
		//
		// B = ( r - l ) . n
		//       -   -     -
		//
		// A = ( r - l )
		//       -   -
		//
		// => P = SQRT( h*h - ( h . n )^2 )
		//
		// => P = SQRT( h*h - ( h . n )^2 )
		//
		// => P = SQRT( rr * ( 1 -  ( normdir . n )^2 ) )

		if (plamp->type == DIRECT_LIGHT) {
			// Light direction is -ve z column of matrix (but normdir = -(light to actor vector))
			// These are both normalised to 4096, so end result has 1.0 = 4096*4096
			int32 cosa = normdir.vx * plampstate->vx + normdir.vy * plampstate->vy + normdir.vz * plampstate->vz;

			// reduce its range so that 1.0=4096
			cosa = cosa >> 12;

			if (cosa < -4096)
				cosa = -4096;
			if (cosa > 4096)
				cosa = 4096;

			// to prevent overflows but make for slower code !
			int32 r = (int32)PXsqrt((PXfloat)rr);
			int32 P = (r * (int32)PXsqrt((PXfloat)(4096 * 4096 - cosa * cosa))) >> 12;

			int32 ba = plamp->ba;
			int32 bs = plamp->bs;

#if 0
			Message("xyz %d %d %d normdir %d %d %d",
			        pos->vx, pos->vy, pos->vz, normdir.vx, normdir.vy, normdir.vz) ;
			Message("r %d P %d cosa %d m = %d", r, P, cosa, m);
#endif
			// is the man outside the beam ?
			if (P > bs) {
				continue;
			}

			// is the man inside the decreasing portion
			if (P > ba) {
				m = ((bs - P) * m) / (bs - ba);
			} else {
			}
			// else the man is inside the constant portion
			// and no need to change m

			// The light direction is fixed
			normdir.vx = plampstate->vx;
			normdir.vy = plampstate->vy;
			normdir.vz = plampstate->vz;
		}

		// Account for decay
		int32 decaym = 4096;

		if (plamp->decay == DECAY_INV_SQR) {
			decaym = ((4096 * RLP_DECAY_CONSTANT * RLP_DECAY_CONSTANT) / rr);
		} else if (plamp->decay == DECAY_INV) {
			int32 r = (int32)PXsqrt((PXfloat)rr);
			decaym = ((4096 * RLP_DECAY_CONSTANT) / r);
		}

		if (plamp->decay != DECAY_NONE) {
			if (decaym > 4096)
				decaym = 4096;
			m = (m * decaym) >> 12;
		}

		// Ignore black lamps caused by the attenuation laws
		if (m == 0)
			continue;

		// Change the multiplier to account for any shades
		psstate = shadelist->states;
		ppshade = shadelist->shades;
		for (s = 0; s < ns; ppshade++, psstate++, s++) {
			pshade = *ppshade;
			sstate = *psstate;

			// Only do static shades in this loop - so we don't
			// get sudden light selection change due to animating shades
			// turning on/off
			if (pshade->nStates > 1)
				continue;

			pshadestate = pshade->states + sstate;
			m = computeShadeMultiplierPC(pshadestate, pos, &plampstate->pos, m);
		}

		// make the intensity = the multiplier * "value" (as in HSV)
		//   of the lights colour
		// RLP schema >= 4 v is stored in the lamps colour
		inten = m * plampstate->c.v;

#if DEBUG_PREPARE_LIGHTS
		printf("%HLamp %d m %d rr %d v %d inten %d ans %d ane %d afs %d afe %d", i, m, rr, plampstate->c.v, inten, ans2, ane2, afs2, afe2);
#endif // #if DEBUG_PREPARE_LIGHTS

		// Ignore lamps which are too dim
		if (inten < lampInfo[darkest].intens)
			continue;

		lampInfo[i].direct.vx = normdir.vx;
		lampInfo[i].direct.vy = normdir.vy;
		lampInfo[i].direct.vz = normdir.vz;

		lampInfo[i].index = i;
		lampInfo[i].intens = inten;
		lampInfo[i].mult = m;
		lampInfo[i].bounce = plamp->b;
		lampInfo[i].width = plamp->w;
		lampInfo[i].rr = rr;

		// compare against the 3 existing intensities
		// convention is that 0 = brightest, 1 = middle, 2 = dimmest
		if (inten > lampInfo[brightest].intens) {
			// Shift the lamps down in intensity
			darkest = middle;
			middle = brightest;
			brightest = i;
		} else if (inten > lampInfo[middle].intens) {
			// Shift the lamps down in intensity
			darkest = middle;
			middle = i;
		} else { // inten must be > lampInfo[darkest].intens
			darkest = i;
		}
	}

	pstate = lamplist->states;

	// Do just the brightest lamp
	// for ( i = 0; i < 1; pintens++, pindex++, i++ )

	// Put the 3 (at most) brightest lamps
	uint32 lamp_index[3];
	uint32 *pindex = lamp_index;
	lamp_index[0] = lampInfo[brightest].index;
	lamp_index[1] = lampInfo[middle].index;
	lamp_index[2] = lampInfo[darkest].index;

	// The brightness of the light at the actor

	// Ambient is 0-255, other lights are 0-4095
	int32 br = (ambient->r + ambient->g + ambient->g) << 4;

	int32 nlights = 0;
	// If we have an ambient
	if (br != 0)
		nlights = 1;

#if 0
	Message("Lamps %d %d %d", lamp_index[0], lamp_index[1], lamp_index[2]);
#endif

	for (i = 0; i < 3; pindex++, i++) {
		uint32 index = *pindex;
		// Ignore unset lamps
		if (index >= MAX_NUMBER_LIGHTS)
			continue;

		// Another lamp
		nlights++;

		// Fill in the lighting direction matrix
		// | Lx1 Ly1 Lz1 |
		// | Lx2 Ly2 Lz2 |
		// | Lx3 Ly3 Lz3 |
		// Lxn, Lyn, Lzn is the x/y/z component of directional light n
		LampInfo *pLampInfo = &(lampInfo[index]);
		linfo[i] = *pLampInfo;
		VECTOR *padirs = &(pLampInfo->direct);
		lDirects->m[i][0] = (int16)padirs->vx;
		lDirects->m[i][1] = (int16)padirs->vy;
		lDirects->m[i][2] = (int16)padirs->vz;

		// Fill in the lighting colour matrix
		// | Lr1 Lr2 Lr3 |
		// | Lg1 Lg2 Lg3 |
		// | Lb1 Lb2 Lb3 |
		// Lrn, Lgn, Lbn is the red/green/blue of directional light n
		// Should do this with GTE vector interpolation
		state = pstate[index];
		plamp = (lamplist->lamps[index]);
		PSXLampState *plampstat = plamp->states + state;
		m = pLampInfo->mult;

		// For animating lights which are turned off - turn them off
		// after the 3 brightest lights have been selected !
		if (plamp->nStates > 1) {
			m = (m * plampstat->m) >> 7;
		}

		// Change the multiplier to account for any shades
		psstate = shadelist->states;
		ppshade = shadelist->shades;
		for (s = 0; s < ns; ppshade++, psstate++, s++) {
			pshade = *ppshade;
			sstate = *psstate;

			// Static shades have been done in the previous loop
			// so only do animating static shades in this loop
			if (pshade->nStates == 1)
				continue;

			pshadestate = pshade->states + sstate;
			m = computeShadeMultiplierPC(pshadestate, pos, &plampstat->pos, m);
		}

		lColours.m[0][i] = (int16)(((uint32)(plampstat->c.r) * m) >> 7);
		lColours.m[1][i] = (int16)(((uint32)(plampstat->c.g) * m) >> 7);
		lColours.m[2][i] = (int16)(((uint32)(plampstat->c.b) * m) >> 7);

		linfo[i].colour.vx = (int16)lColours.m[0][i];
		linfo[i].colour.vy = (int16)lColours.m[1][i];
		linfo[i].colour.vz = (int16)lColours.m[2][i];

		br = br + (lColours.m[0][i] + lColours.m[1][i] + lColours.m[2][i]);

#if 0
		Message("Lamp %d %d RGB:%d %d %d dir:%d %d %d",
		        i, index,
		        lColours.m[0][i], lColours.m[1][i], lColours.m[2][i],
		        lDirects->m[i][0], lDirects->m[i][1], lDirects->m[i][2]);
#endif

		// Only do the square root when we have to
		if (pLampInfo->width > 0)
			lampWidth[i] = (ONE * pLampInfo->width) / (pLampInfo->width + (int32)PXsqrt((PXfloat)pLampInfo->rr));
		else
			lampWidth[i] = 0;

		lampBounce[i] = pLampInfo->bounce;
#if DEBUG_PREPARE_LIGHTS
		printf("%HActor %d %d %d Lamp %d %d m %d dir %d %d %d colour %d %d %d", pos->vx, pos->vy, pos->vz, i, index, m, lDirects->m[i][0], lDirects->m[i][1],
		       lDirects->m[i][2], lColours.m[0][i], lColours.m[1][i], lColours.m[2][i]);
#endif // #if DEBUG_PREPARE_LIGHTS
	}
	// Non-zero if any non-zero width's
	useLampWidth = lampWidth[0] | lampWidth[1] | lampWidth[2];
	// Non-zero if any non-zero bounces's
	useLampBounce = lampBounce[0] | lampBounce[1] | lampBounce[2];

	// Set the GTE lighting colour matrix
	gte_SetColorMatrix_pc(&lColours);

	// Set the ambient colour
	gte_SetBackColor_pc(ambient->r, ambient->g, ambient->b);

	// Average out the brightness over the number of lamps
	if (nlights > 0) {
		br = br / nlights;
	}
	return br;
#undef DEBUG_PREPARE_LIGHTS
}

//------------------------------------------------------------------------
// Set the GTE constant light variables
// Sets the direciton lights colour matrix
// Sets the ambient colour
// Fills in the lightDirects matrix
int32 prepareLightsGlobalPC(MATRIXPC *lightDirects) {
	// Compute the lighting matrix which is :
	// | Lx1 Ly1 Lz1 |
	// | Lx2 Ly2 Lz2 |  * local2world_matrix
	// | Lx3 Ly3 Lz3 |
	// Where : Lxn, Lyn, Lzn is the x/y/z component of directional light n
	lightDirects->m[0][0] = (int16)Lights[0].vx;
	lightDirects->m[0][1] = (int16)Lights[0].vy;
	lightDirects->m[0][2] = (int16)Lights[0].vz;
	lightDirects->m[1][0] = (int16)Lights[1].vx;
	lightDirects->m[1][1] = (int16)Lights[1].vy;
	lightDirects->m[1][2] = (int16)Lights[1].vz;
	lightDirects->m[2][0] = (int16)Lights[2].vx;
	lightDirects->m[2][1] = (int16)Lights[2].vy;
	lightDirects->m[2][2] = (int16)Lights[2].vz;

	MATRIXPC lightColours;
	// Fill in the lighting colour matrix
	// | Lr1 Lr2 Lr3 |
	// | Lg1 Lg2 Lg3 |
	// | Lb1 Lb2 Lb3 |
	lightColours.m[0][0] = (int16)Lights[0].r;
	lightColours.m[1][0] = (int16)Lights[0].g;
	lightColours.m[2][0] = (int16)Lights[0].b;
	lightColours.m[0][1] = (int16)Lights[1].r;
	lightColours.m[1][1] = (int16)Lights[1].g;
	lightColours.m[2][1] = (int16)Lights[1].b;
	lightColours.m[0][2] = (int16)Lights[2].r;
	lightColours.m[1][2] = (int16)Lights[2].g;
	lightColours.m[2][2] = (int16)Lights[2].b;

	// Set the GTE lighting colour matrix
	gte_SetColorMatrix_pc(&lightColours);

	// Set the ambient colour
	gte_SetBackColor_pc(Lights[3].r, Lights[3].g, Lights[3].b);

	int32 br;

	br = (Lights[3].r + Lights[3].g + Lights[3].b) << 4;
	br = br + Lights[0].r + Lights[0].g + Lights[0].b;
	br = br + Lights[1].r + Lights[1].g + Lights[1].b;
	br = br + Lights[2].r + Lights[2].g + Lights[2].b;

	return br;
}

} // End of namespace ICB
