/** @file src/script/structure.c %Structure script routines. */

#include <stdio.h>
#include "enum_string.h"
#include "types.h"
#include "../os/math.h"

#include "script.h"

#include "../ai.h"
#include "../config.h"
#include "../enhancement.h"
#include "../explosion.h"
#include "../house.h"
#include "../map.h"
#include "../net/server.h"
#include "../newui/menubar.h"
#include "../opendune.h"
#include "../pool/pool.h"
#include "../pool/pool_house.h"
#include "../pool/pool_unit.h"
#include "../scenario.h"
#include "../sprites.h"
#include "../string.h"
#include "../structure.h"
#include "../table/locale.h"
#include "../tile.h"
#include "../timer/timer.h"
#include "../tools/coord.h"
#include "../tools/encoded_index.h"
#include "../tools/orientation.h"
#include "../tools/random_general.h"
#include "../unit.h"


/**
 * Get the state of the current structure.
 *
 * Stack: *none*
 *
 * @param script The script engine to operate on.
 * @return State of current structure.
 */
uint16 Script_Structure_GetState(ScriptEngine *script)
{
	Structure *s;

	VARIABLE_NOT_USED(script);

	s = g_scriptCurrentStructure;
	return s->state;
}

/**
 * Set the state for the current structure.
 *
 * Stack: 1 - The state.
 *
 * @param script The script engine to operate on.
 * @return The value 0. Always.
 */
uint16 Script_Structure_SetState(ScriptEngine *script)
{
	Structure *s;
	int16 state;

	s = g_scriptCurrentStructure;
	state = STACK_PEEK(1);

	if (state == STRUCTURE_STATE_DETECT) {
		if (s->o.linkedID == 0xFF) {
			state = STRUCTURE_STATE_IDLE;
		} else {
			if (s->countDown == 0) {
				state = STRUCTURE_STATE_READY;
			} else {
				state = STRUCTURE_STATE_BUSY;
			}
		}
	}

	Structure_Server_SetState(s, state);

	return 0;
}

/**
 * Remove fog around the current structure.
 * Radius to uncover is taken from the current structure info.
 *
 * Stack: *none*
 *
 * @param script The script engine to operate on.
 * @return The value 0. Always.
 */
uint16 Script_Structure_RemoveFogAroundTile(ScriptEngine *script)
{
	VARIABLE_NOT_USED(script);

	Structure_RemoveFog(UNVEILCAUSE_STRUCTURE_SCRIPT, g_scriptCurrentStructure);

	return 0;
}

/**
 * Refine spice in the current structure.
 *
 * Stack: *none*
 *
 * @param script The script engine to operate on.
 * @return 0 if there is no spice to refine, otherwise 1.
 */
uint16 Script_Structure_RefineSpice(ScriptEngine *script)
{
	const StructureInfo *si;
	Structure *s;
	Unit *u;
	House *h;
	uint16 harvesterStep, creditsStep;

	VARIABLE_NOT_USED(script);

	s = g_scriptCurrentStructure;

	if (s->o.linkedID == 0xFF) {
		Structure_Server_SetState(s, STRUCTURE_STATE_IDLE);
		return 0;
	}

	u = Unit_Get_ByIndex(s->o.linkedID);
	si = &g_table_structureInfo[s->o.type];

	harvesterStep = (s->o.hitpoints * 256 / si->o.hitpoints) * 3 / 256;

	if (u->amount < harvesterStep) harvesterStep = u->amount;
	if (u->amount != 0 && harvesterStep < 1) harvesterStep = 1;
	if (harvesterStep == 0) return 0;

	/* Humans' harvesters give 7 credits for each percent in the harvester.
	 * CPUs' harvesters (may be deviated) give 6 to 9 credits.
	 */
	creditsStep = 7;
	if (!House_IsHuman(u->o.houseID)) {
		creditsStep += (Tools_Random_256() % 4) - 1;
	}

	creditsStep *= harvesterStep;

	g_scenario.spiceHarvested[s->o.houseID] += creditsStep;
	if (g_scenario.spiceHarvested[s->o.houseID] > 65000)
		g_scenario.spiceHarvested[s->o.houseID] = 65000;

	h = House_Get_ByIndex(s->o.houseID);
	h->credits += creditsStep;
	u->amount -= harvesterStep;

	if (u->amount == 0) u->o.flags.s.inTransport = false;
	s->o.script.delay = 6;
	return 1;
}

/**
 * Unknown function 0A81.
 *
 * Stack: *none*
 *
 * @param script The script engine to operate on.
 * @return unknown.
 */
uint16 Script_Structure_Unknown0A81(ScriptEngine *script)
{
	uint16 structureIndex;
	Structure *s;
	Unit *u;

	VARIABLE_NOT_USED(script);

	s = g_scriptCurrentStructure;

	structureIndex = Tools_Index_Encode(s->o.index, IT_STRUCTURE);

	u = Tools_Index_GetUnit(s->o.script.variables[4]);
	if (u != NULL) {
		if (structureIndex == u->o.script.variables[4]) return s->o.script.variables[4];
		Object_Script_Variable4_Clear(&u->o);
	}

	Object_Script_Variable4_Clear(&s->o);

	return 0;
}

/**
 * Find a UnitType and make it go to the current structure. In general, type
 *  should be a Carry-All for this to make any sense.
 *
 * Stack: 1 - An unit type.
 *
 * @param script The script engine to operate on.
 * @return unknown.
 */
uint16 Script_Structure_FindUnitByType(ScriptEngine *script)
{
	Structure *s;
	Unit *u;
	Unit *carryall;
	uint16 type;
	uint16 position;
	uint16 carryallIndex;

	s = g_scriptCurrentStructure;

	if (s->state != STRUCTURE_STATE_READY) return IT_NONE;
	if (s->o.linkedID == 0xFF) return IT_NONE;

	type = STACK_PEEK(1);

	position = Structure_FindFreePosition(s, false);

	u = Unit_Get_ByIndex(s->o.linkedID);

	/* Humans' harvesters will not call for a carryall if the last
	 * position is invalid and there is a free tile around the
	 * structure.
	 */
	if (House_IsHuman(s->o.houseID)
			&& (u->o.type == UNIT_HARVESTER)
			&& (u->targetLast.x == 0 && u->targetLast.y == 0)
			&& (position != 0)) {
		return IT_NONE;
	}

	carryall = Unit_CallUnitByType(type, s->o.houseID, Tools_Index_Encode(s->o.index, IT_STRUCTURE), position == 0);

	if (carryall == NULL) return IT_NONE;

	carryallIndex = Tools_Index_Encode(carryall->o.index, IT_UNIT);
	Object_Script_Variable4_Set(&s->o, carryallIndex);

	return carryallIndex;
}

/**
 * Unknown function 0C5A.
 *
 * Stack: *none*
 *
 * @param script The script engine to operate on.
 * @return unknown.
 */
uint16 Script_Structure_Unknown0C5A(ScriptEngine *script)
{
	tile32 tile;
	Structure *s;
	Unit *u;
	uint16 position;

	VARIABLE_NOT_USED(script);

	s = g_scriptCurrentStructure;

	if (s->o.linkedID == 0xFF) return 0;

	u = Unit_Get_ByIndex(s->o.linkedID);

	if (g_table_unitInfo[u->o.type].movementType == MOVEMENT_WINGER && Unit_SetPosition(u, s->o.position)) {
		s->o.linkedID = u->o.linkedID;
		u->o.linkedID = 0xFF;

		if (s->o.linkedID == 0xFF)
			Structure_Server_SetState(s, STRUCTURE_STATE_IDLE);
		Object_Script_Variable4_Clear(&s->o);

		Server_Send_PlayVoice(1 << s->o.houseID,
				VOICE_HARKONNEN_UNIT_LAUNCHED + s->o.houseID);

		return 1;
	}

	position = Structure_FindFreePosition(s, u->o.type == UNIT_HARVESTER);
	if (position == 0) return 0;

	u->o.seenByHouses |= s->o.seenByHouses;

	tile = Tile_Center(Tile_UnpackTile(position));

	if (!Unit_SetPosition(u, tile)) return 0;

	s->o.linkedID = u->o.linkedID;
	u->o.linkedID = 0xFF;

	if (s->rallyPoint != 0xFFFF) {
		const int encoded = Tools_Index_Encode(s->rallyPoint, IT_TILE);
		enum UnitActionType action = ACTION_MOVE;

		/* Harvesters should NOT harvest if it knows the destination
		 * is not sand.  Note: sand tiles can't become non-sand tiles.
		 */
		if (u->o.type == UNIT_HARVESTER) {
			action = ACTION_HARVEST;

			if (Map_IsUnveiledToHouse(Unit_GetHouseID(u), s->rallyPoint)) {
				const enum LandscapeType lst = Map_GetLandscapeType(s->rallyPoint);

				if (!g_table_landscapeInfo[lst].isSand)
					action = ACTION_MOVE;
			}
		}

		Unit_Server_SetAction(u, action);
		Unit_SetDestination(u, encoded);
	}

	Unit_SetOrientation(u, Tile_GetDirection(s->o.position, u->o.position) & 0xE0, true, 0);
	Unit_SetOrientation(u, u->orientation[0].current, true, 1);

	/* ENHANCEMENT -- Only display the hint for harvesters. */
	if (u->o.type == UNIT_HARVESTER) {
		GUI_DisplayHint(u->o.houseID,
				STR_HINT_SEARCH_FOR_SPICE_FIELDS_TO_HARVEST,
				0x6A);
	}

	if (s->o.linkedID == 0xFF)
		Structure_Server_SetState(s, STRUCTURE_STATE_IDLE);
	Object_Script_Variable4_Clear(&s->o);

	if (s->o.type == STRUCTURE_REPAIR) {
		/* ENHANCEMENT -- Units can be ejected from the repair bay.
		 *
		 * uint16 countDown = ((ui->o.hitpoints - u->o.hitpoints) * 256 / ui->o.hitpoints) * (ui->o.buildTime << 6) / 256;
		 */
		const UnitInfo *ui = &g_table_unitInfo[u->o.type];
		int new_hitpoints = ui->o.hitpoints - (s->countDown * ui->o.hitpoints + (ui->o.buildTime << 6) - 1) / (ui->o.buildTime << 6);
		new_hitpoints = clamp(u->o.hitpoints, new_hitpoints, ui->o.hitpoints);

		u->o.hitpoints = new_hitpoints;

		s->countDown = 0;
		return 1;
	}

	const enum VoiceID voiceID
		= (u->o.type == UNIT_HARVESTER)
		? VOICE_HARKONNEN_HARVESTER_DEPLOYED
		: VOICE_HARKONNEN_UNIT_DEPLOYED;
	Server_Send_PlayVoice(1 << s->o.houseID, voiceID + s->o.houseID);

	return 1;
}

/**
 * Find a Unit which is within range and not an ally.
 *
 * Stack: 1 - Range to find a target in (amount of tiles multiplied with 256).
 *
 * @param script The script engine to operate on.
 * @return The Unit Index of the closest unit within range and not friendly,
 *   or 0 if none exists.
 */
uint16 Script_Structure_FindTargetUnit(ScriptEngine *script)
{
	PoolFindStruct find;
	Structure *s;
	const Unit *u;
	uint32 distanceCurrent;
	uint32 targetRange;
	tile32 position;

	s = g_scriptCurrentStructure;
	targetRange = STACK_PEEK(1);
	distanceCurrent = 32000;
	u = NULL;

	/* ENHANCEMENT -- The original code calculated distances from the top-left corner of the structure. */
	if (g_dune2_enhanced) {
		position = Tile_Center(s->o.position);
	} else {
		position = s->o.position;
	}

	for (const Unit *uf = Unit_FindFirst(&find, HOUSE_INVALID, UNIT_INVALID);
			uf != NULL;
			uf = Unit_FindNext(&find)) {
		uint16 distance;

		if (House_AreAllied(s->o.houseID, Unit_GetHouseID(uf))) continue;

		if (uf->o.type != UNIT_ORNITHOPTER) {
			if ((uf->o.seenByHouses & (1 << s->o.houseID)) == 0) continue;
		}

		distance = Tile_GetDistance(uf->o.position, position);
		if (distance >= distanceCurrent) continue;

		if (g_dune2_enhanced) {
			if (uf->o.type == UNIT_ORNITHOPTER) {
				if (distance > targetRange * 3) continue;
			} else {
				if (distance > targetRange) continue;
			}
		} else {
			if (uf->o.type == UNIT_ORNITHOPTER) {
				if (distance >= targetRange * 3) continue;
			} else {
				if (distance >= targetRange) continue;
			}
		}

		/* ENHANCEMENT -- The original code swapped the assignment, making it do nothing, Now it finds the closest unit to shoot at, what seems to be the intention */
		if (g_dune2_enhanced) distanceCurrent = distance;
		u = uf;
	}

	if (u == NULL) return IT_NONE;
	return Tools_Index_Encode(u->o.index, IT_UNIT);
}

/**
 * Rotate the turret to look at a tile.
 *
 * Stack: 1 - Tile to look at.
 *
 * @param script The script engine to operate on.
 * @return 0 if looking at target, otherwise 1.
 */
uint16 Script_Structure_RotateTurret(ScriptEngine *script)
{
	Structure *s;
	tile32 lookAt;
	Tile *tile;
	uint16 baseSpriteID;
	uint16 encoded;
	int16 rotation;
	int16 rotationNeeded;
	int16 rotateDiff;

	encoded = STACK_PEEK(1);

	if (encoded == 0) return 0;

	s      = g_scriptCurrentStructure;
	lookAt = Tools_Index_GetTile(encoded);
	tile   = &g_map[Tile_PackTile(s->o.position)];

	/* Find the base sprite of the structure */
	if (s->o.type == STRUCTURE_ROCKET_TURRET) {
		baseSpriteID = g_iconMap[g_iconMap[ICM_ICONGROUP_BASE_ROCKET_TURRET] + 2];
	} else {
		baseSpriteID = g_iconMap[g_iconMap[ICM_ICONGROUP_BASE_DEFENSE_TURRET] + 2];
	}

	rotation = tile->groundSpriteID - baseSpriteID;
	if (rotation < 0 || rotation > 7) return 1;

	/* Find what rotation we should have to look at the target */
	rotationNeeded = Orientation_256To8(Tile_GetDirection(s->o.position, lookAt));

	/* Do we need to rotate */
	if (rotationNeeded == rotation) return 0;

	/* Find the fastest way to rotate to the correct rotation */
	rotateDiff = rotationNeeded - rotation;
	if (rotateDiff < 0) rotateDiff += 8;

	if (rotateDiff < 4) {
		rotation++;
	} else {
		rotation--;
	}
	rotation &= 0x7;

	/* Set the new sprites */
	tile->groundSpriteID = baseSpriteID + rotation;
	s->rotationSpriteDiff = rotation;

	return 1;
}

/**
 * Find the direction a tile is, seen from the structure. If the tile is
 *  invalid it gives the direction the structure is currently looking at.
 *
 * Stack: 1 - Tile to get the direction to, or the current direction of the
 *   structure in case the tile is invalid.
 *
 * @param script The script engine to operate on.
 * @return The direction (value between 0 and 7, shifted to the left with 5).
 */
uint16 Script_Structure_GetDirection(ScriptEngine *script)
{
	Structure *s;
	tile32 tile;
	uint16 encoded;

	s = g_scriptCurrentStructure;
	encoded = STACK_PEEK(1);

	if (!Tools_Index_IsValid(encoded)) return s->rotationSpriteDiff << 5;

	tile = Tools_Index_GetTile(encoded);

	return Orientation_256To8(Tile_GetDirection(s->o.position, tile)) << 5;
}

/**
 * Unknown function 11B9.
 *
 * Stack: 1 - Encoded tile.
 *
 * @param script The script engine to operate on.
 * @return unknown.
 */
uint16 Script_Structure_Unknown11B9(ScriptEngine *script)
{
	uint16 encoded;
	Unit *u;

	encoded = STACK_PEEK(1);

	if (!Tools_Index_IsValid(encoded)) return 0;
	if (Tools_Index_GetType(encoded) != IT_UNIT) return 0;

	u = Tools_Index_GetUnit(encoded);
	if (u == NULL) return 0;

	Object_Script_Variable4_Clear(&u->o);
	u->targetMove = 0;

	return 0;
}

/**
 * Play a voice on the structure.
 *
 * Stack: 1 - The VoiceID to play.
 *
 * @param script The script engine to operate on.
 * @return unknown.
 */
uint16 Script_Structure_VoicePlay(ScriptEngine *script)
{
	Structure *s;

	s = g_scriptCurrentStructure;

	Server_Send_PlaySoundAtTile(1 << s->o.houseID,
			STACK_PEEK(1), s->o.position);

	return 0;
}

/**
 * Fire a bullet or missile from a (rocket) turret.
 *
 * Stack: *none*
 * Variables: 2 - Target to shoot at.
 *
 * @param script The script engine to operate on.
 * @return The time between this and the next time firing.
 */
uint16 Script_Structure_Fire(ScriptEngine *script)
{
	Structure *s;
	Unit *u;
	tile32 position;
	uint16 target;
	uint16 damage;
	uint16 fireDelay;
	uint16 type;

	s = g_scriptCurrentStructure;

	target = script->variables[2];
	if (target == 0) return 0;

	if (s->o.type == STRUCTURE_ROCKET_TURRET && Tile_GetDistance(Tools_Index_GetTile(target), s->o.position) >= 0x300) {
		type      = UNIT_MISSILE_TURRET;
		damage    = 30;
		fireDelay = Tools_AdjustToGameSpeed(g_table_unitInfo[UNIT_LAUNCHER].fireDelay, 1, 0xFFFF, true);
	} else {
		type      = UNIT_BULLET;
		damage    = 20;
		fireDelay = Tools_AdjustToGameSpeed(g_table_unitInfo[UNIT_TANK].fireDelay, 1, 0xFFFF, true);

		/* ENHANCEMENT -- Cannon turrets and rocket turrets at close
		 * range were silent.
		 */
		Server_Send_PlaySoundAtTile(FLAG_HOUSE_ALL, SOUND_TANK, s->o.position);
	}

	position.x = s->o.position.x + 0x80;
	position.y = s->o.position.y + 0x80;
	u = Unit_CreateBullet(position, type, s->o.houseID, damage, target);

	if (u == NULL) return 0;

	u->originEncoded = Tools_Index_Encode(s->o.index, IT_STRUCTURE);

	return fireDelay;
}

/**
 * Make the structure explode.
 *
 * Stack: *none*
 *
 * @param script The script engine to operate on.
 * @return unknown.
 */
uint16 Script_Structure_Explode(ScriptEngine *script)
{
	Structure *s;
	uint16 position;
	uint16 layout;
	uint16 i;

	VARIABLE_NOT_USED(script);

	s = g_scriptCurrentStructure;
	layout = g_table_structureInfo[s->o.type].layout;
	position = Tile_PackTile(s->o.position);

	for (i = 0; i < g_table_structure_layoutTileCount[layout]; i++) {
		tile32 tile;

		tile = Tile_UnpackTile(position + g_table_structure_layoutTiles[layout][i]);

		Map_MakeExplosion(EXPLOSION_STRUCTURE, tile, 0, 0);
	}

	return 0;
}

/**
 * Destroy a structure and spawn soldiers around the place.
 *
 * Stack: *none*
 *
 * @param script The script engine to operate on.
 * @return Always 0.
 */
uint16 Script_Structure_Destroy(ScriptEngine *script)
{
	Structure *s;
	uint16 position;
	uint16 layout;
	uint16 i;

	VARIABLE_NOT_USED(script);

	s = g_scriptCurrentStructure;
	layout = g_table_structureInfo[s->o.type].layout;
	position = Tile_PackTile(s->o.position);

	Structure_Remove(s);

	for (i = 0; i < g_table_structure_layoutTileCount[layout]; i++) {
		tile32 tile;
		Unit *u;

		tile = Tile_UnpackTile(position + g_table_structure_layoutTiles[layout][i]);

		if (g_table_structureInfo[s->o.type].o.spawnChance < Tools_Random_256()) continue;

		u = Unit_Create(UNIT_INDEX_INVALID, UNIT_SOLDIER, s->o.houseID, tile, Tools_Random_256());
		if (u == NULL) continue;

		u->o.hitpoints = g_table_unitInfo[UNIT_SOLDIER].o.hitpoints * (Tools_Random_256() & 3) / 256;

		if (!House_IsHuman(s->o.houseID)) {
			/* AI units shouldn't use ACTION_ATTACK.  Use ACTION_HUNT
			 * so the soldier will move and we can rebuild here later.
			 */
			if (enhancement_ai_respects_structure_placement || AI_IsBrutalAI(s->o.houseID)) {
				Unit_Server_SetAction(u, ACTION_HUNT);
			} else {
				Unit_Server_SetAction(u, ACTION_ATTACK);
			}
			continue;
		}

		Unit_Server_SetAction(u, ACTION_MOVE);

		tile = Tile_MoveByRandom(u->o.position, 32, true);

		u->targetMove = Tools_Index_Encode(Tile_PackTile(tile), IT_TILE);
	}

	if (g_debugScenario) return 0;

	Server_Send_StatusMessage3(1 << s->o.houseID, 0,
			STR_IS_DESTROYED,
			STR_HOUSE_HARKONNEN + s->o.houseID,
			g_table_structureInfo[s->o.type].o.stringID_full);

	return 0;
}
