/*	
	Scenics.c
	For those times when full-on enemy code is a little bit too much...
	We're talking static, non interacting, animated objects, animating under puzzle or program control
*/

#include "global.h"
#include "bff_load.h"
#include "maths.h"
#include "inventory.h"
#include "lights.h"
#include "scenics.h"
#include "card.h"
#include "map_draw.h"

#include "enemies.h"	// rqd for interesting scenics
#include "charactr.h"
#include "fma.h"
#include "sprouts.h"

SCENIC *scenicList = NULL;
int	scenicPickupCounter = 0;
SCENIC *scenicLastSproutChecked;	// So we know which one the inventory's talking about when it gets back here


// ---- Creating Scenics ----
// Two ways of introducing a scenic
// From program code for one-offs. From a BFF for specifics

void scenicSetup(SCENIC *pS)
{
//int actorAnimate(ACTOR *actor, int animNum, char loop, char queue, int speed, char skipendframe, char blend);

	if(pS->pActor->animation.numSegments)
		actorAnimate(pS->pActor, 0, 1, 0, (1<<ANIMSHIFT), 0, 0);

//	pS->pActor->psiData.flags &= ~(ACTOR_DYNAMICSORT);
}

SCENIC *scenicCreate(char *psiname,VECTOR *pos)
{
	SCENIC *pS;

	pS = MALLOC(sizeof(SCENIC));
	FMEMZERO(pS,sizeof(SCENIC));

// organise a model and an actor
	pS->pModel=BFF_IsModelLoaded(psiname);
	ASSERT(pS->pModel);
	pS->pActor=actorCreate(pS->pModel);
	ASSERT(pS->pActor);

	pS->pActor->psiData.object->rotate.vy = pS->ya & 0xfff;

	pS->pActor->position = *pos;
	pS->flags = SCENICFLAG_ACTIVE;
	scenicSetup(pS);

	pS->next = scenicList;
	scenicList = pS;
	return pS;
}


void scenicOnePostBFF(SCENIC *pS)
{
	VECTOR v;
	int modelCRC;

	pS->flags &= ~SCENICFLAG_NEEDSACTOR;
	pS->flags |= SCENICFLAG_ACTIVE;

// If this scenic is an inventory item,
// 1) Figure out *which* inventory item we are.
// 2) Check to see if we've already been collected, or are in someone's inventory
// If so, turn ourselves very definitively OFF.

	if(pS->flags & SCENICFLAG_IS_INVENTORY_ITEM)
	{
		int model_name_crc;
		int i;
		model_name_crc = (int)pS->pModel;
		for(i = 0; i < TOTAL_ITEMS; i++)
		{
			if(model_name_crc == utilStr2CRC(inventory_tmap_names[i]))
			{
				pS->inventory_item_number = i;

				if(gamePlayData->inventory_item_status[(int)(pS->inventory_item_number)] != INV_STATUS_NOTCOLLECTED)
				{
					pS->flags &= ~SCENICFLAG_ACTIVE;
				}
				break;
			}
		}
		if(i == TOTAL_ITEMS)
		{
			utilPrintf("** Couldn't match inventory item to scenic\n");
		}
	}

// Similarly, if its a collectible
	if(pS->flags & SCENICFLAG_IS_SPROUTBOX)
	{
		pS->inventory_item_number = SPROUTS;

		ASSERT(scenicPickupCounter<16);	// Coz they need to fit in a short of flags per level (like Glover's lives)

		pS->pickup_index = scenicPickupCounter;

		if((gamePlayData->pickups_collected[(int)(levinfo->level_number)]) & (1<<(pS->pickup_index)))		// We've already got this one
		{
			pS->flags &= ~SCENICFLAG_ACTIVE;

			if(sproutsTimer==SPROUT_TIME)					// If its a while since we've picked any up
			{
				if(gamePlayData->sprouts_carried<SPROUT_LIMIT)	// And we've got less than limit atm
				{
					pS->flags |= SCENICFLAG_ACTIVE;		// Regenerate these sprouts
					gamePlayData->pickups_collected[(int)(levinfo->level_number)] -= 1<<pS->pickup_index;
					debugPrintf("Regenerating sprout box!!!\n");
				}
			}
		}
		scenicPickupCounter++;
	}

	if(pS->flags & SCENICFLAG_IS_SWITCH)
	{
		pS->pNoise = sfxFindSampleInAllBanks("lever1");
		ASSERT(pS->pNoise);
	}

	if(pS->flags & SCENICFLAG_IS_BUTTON)
	{
		pS->pNoise = sfxFindSampleInAllBanks("button1");
		ASSERT(pS->pNoise);
	}


// organise a model and an actor
	modelCRC = (int)pS->pModel;
	pS->pModel = BFF_IsCRCModelLoaded(modelCRC);
	if(!pS->pModel)
	{
		DB("Can't find scenic model (%08x) but unfortunately I don't know the name of it\n", modelCRC);
		ASSERT(0);
	}

	v = *((VECTOR *)pS->pActor);

	pS->pActor=actorCreate(pS->pModel);
//	pS->pActor->psiData.flags &= ~(ACTOR_DYNAMICSORT);

	ASSERT(pS->pActor);
	pS->pActor->position = v;
	scenicSetup(pS);


// root (motion?) bone of animating scenics
// Frig to zero the thing so it matches the object's position in egg.
// tbd - this shouldn't be necessary
// (It's now done by a "centre" option in bff_psi)
/*
	{
		PSIOBJECT *world;
		PSIMESH *mesh;
		int real_n_movekeys;
		int i;
		SVKEYFRAME *movekeys;

   		world = pS->pActor->psiData.object;
		mesh = world->meshdata;
		movekeys = mesh->movekeys;

		real_n_movekeys = ((int)mesh->scalekeys - (int)mesh->movekeys) / sizeof(SVKEYFRAME);
		for(i = 1; i < real_n_movekeys; i++)
		{
			movekeys[i].vect.x -= movekeys[0].vect.x;
			movekeys[i].vect.y -= movekeys[0].vect.y;
			movekeys[i].vect.z -= movekeys[0].vect.z;
		}
		movekeys[0].vect.x = 0;
		movekeys[0].vect.y = 0;
		movekeys[0].vect.z = 0;
	}
*/
}

void scenicPostBFF()
{
	SCENIC *pS;

	for(pS = scenicList; pS; pS=pS->next)
	{
		if(pS->flags & SCENICFLAG_NEEDSACTOR)
		{
			scenicOnePostBFF(pS);
		}
	}
}

void scenicLinkBFF(SCENIC_BFF *sb)
{
	int i;
	SCENIC *pS;
	VECTOR *v;

	v = ADD2POINTER(sb, sizeof(SCENIC_BFF));
	pS = ADD2POINTER(v, sb->n_scenics * sizeof(VECTOR));

	for(i = 0; i < sb->n_scenics; i++,pS++,v++)
	{
		pS->flags |= SCENICFLAG_FROMBFF;
		pS->flags |= SCENICFLAG_NEEDSACTOR;

		pS->pActor = (void *)v;
		pS->next = scenicList;
		scenicList = pS;
	}
}



// If the chicken asks to pick something up, is there a pickupable scenic?
// (decisions about inventory space are left to the caller)

int scenicFacing(SCENIC *pS,VECTOR *characterpos, short characterangle)
{
	VECTOR temp;
	int d;
	SUBVECTOR(&temp,characterpos,&pS->pActor->position);
	d = Magnitude(&temp);
	if(d < 1000)// CPW: An optimization would be to square the 1000 before the comparison, avoiding the square root
	{
// Coz the chicken isn't actually on the floor, do a 2d check against the object's radius plus a bit for the chicken
		d = Magnitude2D(temp.vx,temp.vz);
		if(d < pS->pActor->radius + 50 * 8)
		{
// If we're stood inside it (tsk) then pick up
			if (d < pS->pActor->radius * 3/4)
			{
				return 1;
			}
			else
			{
				int angle;
				int fov = 1024;

// Otherwise, insist on facing it
				angle = utilCalcAngle(temp.vx, temp.vz);
				angle -= characterangle;
				angle &=4095;

				if(angle < fov || angle > 4096 - fov)
				{
					return 1;
				}
				else
				{
//					DB("Not facing pickupable scenic\n");
					return 0;
				}
			}
		}
	}
	return 0;
}

SCENIC *scenicAvailableForPickup(VECTOR *characterpos, short characterangle)
{
	SCENIC *pS;

	for(pS = scenicList; pS; pS=pS->next)
	{
		if(pS->flags & SCENICFLAG_ACTIVE)
		{
			if(    (pS->flags & SCENICFLAG_IS_INVENTORY_ITEM)
				&& gamePlayData->inventory_item_status[(int)(pS->inventory_item_number)] == INV_STATUS_NOTCOLLECTED
				)
			{
				if(scenicFacing(pS,characterpos,characterangle))
				{
					return pS;
				}
			}

			if(pS->flags & SCENICFLAG_IS_SPROUTBOX)
			{
				if(scenicFacing(pS,characterpos,characterangle))
				{
					scenicLastSproutChecked = pS;
					return pS;
				}
			}
		}
	}
	return NULL;
}


SCENIC *scenicNear(VECTOR *characterpos, short characterangle, short dist)
{
	SCENIC *pS;

	for(pS = scenicList; pS; pS=pS->next)
	{
		if(pS->flags & SCENICFLAG_ACTIVE)
		{
			VECTOR temp;
			int d;

			SUBVECTOR(&temp,characterpos, &pS->pActor->position);
			
			// CPW: d = Magnitude(&temp);
			d = Magnitude2D(temp.vx, temp.vz);

			if(d < dist)
			{
				return pS;
			}
		}
	}
	return NULL;	
}

SCENIC *scenicNearARatClingThing(VECTOR *characterpos, short characterangle)
{
	SCENIC *pS;

	// JS : Gahhhh !!! NULL pointers anybody ? 
	if ((pS=scenicNear(characterpos, characterangle, 512))==NULL)
		return NULL;


	if(pS->flags & SCENICFLAG_IS_CLINGY_FOR_RATS)	
		return pS;
	else
		return NULL;
}





// This is called from the inventory when things get passed around
void scenicInventoryNotification(int inventory_item, int what)
{
	SCENIC *pS;

	if(inventory_item == SPROUTS)
	{
//		char temp[20];
		pS = scenicLastSproutChecked;
		pS->flags &= ~(SCENICFLAG_ACTIVE);

		gamePlayData->pickups_collected[(int)(levinfo->level_number)] |= (1<<(pS->pickup_index));
		gamePlayData->sprouts_carried += 10;

//		sprintf(temp,"Sprouts: %d\n",gamePlayData->sprouts_carried);
//		overlayDebugMessage(temp, 25 * 2);

		return;
	}

	for(pS = scenicList; pS; pS=pS->next)
	{
		if((pS->flags & SCENICFLAG_IS_INVENTORY_ITEM) && (pS->inventory_item_number == inventory_item))
		{
			switch(what)
			{
// Technical note: "Passing objects from one character to another" calls here with "removing" and then "picking up"
// Respotting will probably call here with "removing" before "respot"

				case SCENIC_INVENTORY_RESPOT:
					pS->flags |= (SCENICFLAG_ACTIVE);
					break;
				case SCENIC_INVENTORY_PICKINGUP:
					pS->flags &= ~(SCENICFLAG_ACTIVE);
					break;
				case SCENIC_INVENTORY_REMOVING:
					pS->flags &= ~(SCENICFLAG_ACTIVE);
					break;
			}
		}
	}

	switch(what)
	{
		case SCENIC_INVENTORY_RESPOT:
			gamePlayData->inventory_item_status[inventory_item] = INV_STATUS_NOTCOLLECTED;
			break;
		case SCENIC_INVENTORY_PICKINGUP:
			gamePlayData->inventory_item_status[inventory_item] = INV_STATUS_IN_INVENTORY;
			break;
		case SCENIC_INVENTORY_REMOVING:
			gamePlayData->inventory_item_status[inventory_item] = INV_STATUS_REMOVED;
			break;
	}
}

// ---- Freeing 'em all up ----

void scenicFree(SCENIC *pS)
{
	SCENIC **ptr;
	ptr = &scenicList;

// Unhook from a singly-linked list.
	while((*ptr) && (*ptr) != pS)
	{
		ptr = &((*ptr)->next);
	}
	if(!(*ptr))
		return;
	*ptr = pS->next;

// Unhooked from the list. Free it.

	actorFree(pS->pActor);

	if(!(pS->flags & SCENICFLAG_FROMBFF))
	{
		FREE(pS);
	}
}

// ---- Start,End ----

void scenicInit()
{
	scenicList = NULL;
	scenicPickupCounter = 0;
}
void scenicEnd()
{
	while(scenicList)
	{
		scenicFree(scenicList);
	}
}
// ---- Utils ----

SCENIC *scenicFind(int tag)
{
	SCENIC *pS;
	for(pS = scenicList; pS; pS=pS->next)
	{
		if(pS->tag == tag)
			return pS;
	}
	return NULL;
}

void scenicEnable(SCENIC *pS, int enable)
{
	if(!pS)
		return;
	if(enable)
	{
		pS->flags |= SCENICFLAG_ACTIVE;

		if(pS->flags & SCENICFLAG_IS_INVENTORY_ITEM)
		{
			if(gamePlayData->inventory_item_status[(int)(pS->inventory_item_number)] != INV_STATUS_NOTCOLLECTED)
				pS->flags &= ~SCENICFLAG_ACTIVE;
		}
	}
	else
	{
		pS->flags &= ~SCENICFLAG_ACTIVE;
	}
}

// tbd - make a macro in the h file
int scenicIsEnabled(SCENIC *pS)
{
	if(!pS)
		return 0;
	return((pS->flags & SCENICFLAG_ACTIVE) != 0);
}

//int actorAnimate(ACTOR *actor, int animNum, char loop, char queue, int speed, char skipendframe, char blend);
void scenicSetAnim(SCENIC *pS, int anim, int flags)
{
	int loop;
	int queue;
	//int blend;
	int speed;

	if(!pS)
		return;
	loop = (flags & SCENIC_LOOP)!=0;
	queue =(flags & SCENIC_QUEUE) != 0;
	//blend = flags & 255;

	if(flags & SCENIC_REVERSE)
	{
		speed = -(1<<ANIMSHIFT);
	}
	else
	{
		speed = (1<<ANIMSHIFT);
	}


	if(anim >= pS->pActor->animation.numSegments)
	{
		anim = 0;
	}


//	printf("scenicanim %d set (flags %d) loop %d, queue %d, speed %d, blend %d\n",anim,flags, loop,queue,speed, blend);
	if(pS->pActor->animation.numSegments)
		actorAnimate(pS->pActor, anim, loop, queue , speed, 0, 2048);
}
//	actorAnimate(pS->pActor, 0, 1, 0, (1<<ANIMSHIFT), 0, 0);

// ---- Cycle ----

void scenicCycle(void)
{
	SCENIC *pS;

//	debugPrintf("Sproutstimer %d  ",sproutsTimer);

	for(pS = scenicList; pS; pS=pS->next)
	{

		if(pS->flags & SCENICFLAG_NEEDSACTOR)
		{
			scenicOnePostBFF(pS);	// Should already have been done
		}


		if(pS->flags & SCENICFLAG_ACTIVE)
		{
			if(pS->pActor->animation.numSegments)
				actorUpdateAnimations(pS->pActor);
			else
				actorSetAnimation(pS->pActor,0);	// necessary for Tim's cogwheel until he animates it

		}
	}
/*	
	// Tell the lead character (if any) to look at the nearest interesting scenic
	if(lead)
	{
		if(nearest_interesting)
		{
			enemyLookAtPos3D(lead, &nearest_interesting->pActor->position);
		}
		else
		{
			enemyLookAtPos3D(lead, NULL);
		}
	}
*/
}

// See if there's an "interesting" scenic closer than "range" away
//	int near_m = 100 * 8 * 4;	// distance at which interesting items become interesting

void scenicFindInteresting(VECTOR *character_pos, int character_dir, VECTOR **best, int *max_range)
{
	SCENIC *pS;
	SCENIC *nearest_interesting;
	int m;
	int a;

	nearest_interesting = NULL;

	for(pS = scenicList; pS; pS=pS->next)
	{
		if((pS->flags & SCENICFLAG_IS_INTERESTING) && (pS->flags & SCENICFLAG_ACTIVE))
		{
			VECTOR t;
			int m;

			t.vx = character_pos->vx - pS->pActor->position.vx;
			t.vy = character_pos->vy - pS->pActor->position.vy;
			t.vz = character_pos->vz - pS->pActor->position.vz;
			m = Magnitude(&t);
			if (m < *max_range)
			{
// Check it's in front of us
				a = utilCalcAngle(character_pos->vx - pS->pActor->position.vx, character_pos->vz - pS->pActor->position.vz);
				a -= character_dir;
				a &=4095;
				if((a<1024) || (a>4096 - 1024))
				{
					nearest_interesting = pS;
					*max_range = m;
					*best = &nearest_interesting->pActor->position;
				}
			}
		}
	}
}

// ---- Draw ----

void scenicDrawAll()
{
	SCENIC *pS;
	int prev_lighting;

	prev_lighting = PSImodelctrl.lighting;
	PSImodelctrl.lighting = NOLIGHTING;

	for(pS = scenicList; pS; pS=pS->next)
	{	


		pS->oldState = pS->state;	// CPW: for SWITCH_JUST_ON/OFF

		if(pS->flags & SCENICFLAG_NEEDSACTOR)
		{
			scenicOnePostBFF(pS);	// Should already have been done
		}
		if(pS->flags & SCENICFLAG_ACTIVE)
		{
			if(mapdrawIsSphereVisible(&pS->pActor->position, pS->pActor->radius))
			{
				pS->pActor->psiData.object->rotate.vy = pS->ya;

				EXLIGHT_SetupActorLights(pS->pActor);
				if(pS->pActor->animation.numSegments)
					actorMove(pS->pActor);

#ifndef _WINDOWS
				actorDraw(pS->pActor);
#else
				d2mActorDraw(pS->pActor);
#endif

				if(overlay_show_collision == DEBUG_OVERLAY_ENEMY_INFO)
					overlayShowSphere(pS->pActor->position.vx,pS->pActor->position.vy,pS->pActor->position.vz,200,0x0000ff);
			}
		}
	}
	PSImodelctrl.lighting = prev_lighting;
}
