/*
 * Copyright (C) Volition, Inc. 1999.  All rights reserved.
 *
 * All source code herein is the property of Volition, Inc. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/ 




#include "gamesnd/gamesnd.h"
#include "hud/hudreticle.h"
#include "hud/hudtargetbox.h"
#include "io/timer.h"
#include "localization/localize.h"
#include "network/multi.h"
#include "playerman/player.h"
#include "ship/ship.h"
#include "weapon/emp.h"
#include "weapon/weapon.h"
#include "globalincs/alphacolors.h"

#define NUM_RETICLE_ANIS			11		// keep up to date when modifying the number of reticle ani files

#define RETICLE_TOP_ARC				0
#define RETICLE_LASER_WARN			1
#define RETICLE_LOCK_WARN			2
#define RETICLE_LEFT_ARC			3
#define RETICLE_RIGHT_ARC			4
#define RETICLE_ONE_PRIMARY			5
#define RETICLE_TWO_PRIMARY			6
#define RETICLE_ONE_SECONDARY		7
#define RETICLE_TWO_SECONDARY		8
#define RETICLE_THREE_SECONDARY		9
//#define RETICLE_LAUNCH_LABEL		5
#define RETICLE_CENTER				10

int Hud_throttle_frame_w[GR_NUM_RESOLUTIONS] = {
	49, 
	78
};
int Hud_throttle_h[GR_NUM_RESOLUTIONS] = {
	50, 80
};
int Hud_throttle_bottom_y[NUM_HUD_RETICLE_STYLES][GR_NUM_RESOLUTIONS] = {
	{ 309, 494 },
	{ 307, 491 }
};
int Hud_throttle_aburn_h[GR_NUM_RESOLUTIONS] = {
	17,
	27
};
int Hud_throttle_aburn_button[GR_NUM_RESOLUTIONS] = {
	259,
	414
};

int Outer_circle_radius[GR_NUM_RESOLUTIONS] = {
	104,
	166
};

int Hud_reticle_center[GR_NUM_RESOLUTIONS][2] =
{
	{ // GR_640
		320, 242
	},
	{ // GR_1024
		512, 387
	}
};

char Reticle_frame_names[NUM_HUD_RETICLE_STYLES][GR_NUM_RESOLUTIONS][NUM_RETICLE_ANIS][MAX_FILENAME_LEN] = 
{
//XSTR:OFF
	{
		{ // GR_640
			"toparc1_fs1",
			"toparc2_fs1",
			"toparc3_fs1",
			"leftarc_fs1",
			"rightarc1_fs1",
			"rightarc2_fs1",
			"rightarc3_fs1",
			"rightarc4_fs1",
			"rightarc5_fs1",
			"rightarc6_fs1",
//			"toparc4_fs1",
			"reticle1_fs1",	
		}, 
		{ // GR_1024
			"2_toparc1_fs1",
			"2_toparc2_fs1",
			"2_toparc3_fs1",
			"2_leftarc_fs1",
			"2_rightarc1_fs1",
			"2_rightarc2_fs1",
			"2_rightarc3_fs1",
			"2_rightarc4_fs1",
			"2_rightarc5_fs1",
			"2_rightarc6_fs1",
//			"2_toparc4_fs1",
			"2_reticle1_fs1",	
		}
	},
	{
		{ // GR_640
			"toparc1",
			"toparc2",
			"toparc3",
			"leftarc",
			"rightarc1",
			"<none>",
			"<none>",
			"<none>",
			"<none>",
			"<none>",
//			"<none>",
			"reticle1",	
		}, 
		{ // GR_1024
			"2_toparc1",
			"2_toparc2",
			"2_toparc3",
			"2_leftarc",
			"2_rightarc1",
			"<none>",
			"<none>",
			"<none>",
			"<none>",
			"<none>",
//			"<none>",
			"2_reticle1",	
		}
	}
//XSTR:ON
};

// reticle frame coords
int Reticle_frame_coords[NUM_HUD_RETICLE_STYLES][GR_NUM_RESOLUTIONS][NUM_RETICLE_ANIS][2] =
{
	{
		{ // GR_640
			{241, 137},
			{300, 137},
			{320, 137},
			{217, 244},
			{374, 242},
			{406, 253},
			{406, 253},
			{391, 276},
			{391, 276},
			{391, 276},
//			{297, 162},
			{308, 235}
		}, 
		{ // GR_1024
			{386, 219},
			{480, 219},
			{512, 219},
			{347, 390},
			{598, 387},
			{650, 405},
			{650, 405},
			{626, 442},
			{626, 442},
			{626, 442},
//			{475, 259},
			{493, 376}
		},
	},
	{
		{ // GR_640
			{241, 137},
			{400, 245},
			{394, 261},
			{216, 168},
			{359, 168},
			{406, 253},
			{406, 253},
			{391, 276},
			{391, 276},
			{391, 276},
//			{297, 161},
			{308, 235}
		}, 
		{ // GR_1024
			{386, 219},
			{640, 393},
			{631, 419},
			{346, 269},
			{574, 269},
			{649, 401},
			{649, 401},
			{625, 438},
			{625, 438},
			{625, 438},
//			{475, 258},
			{493, 370}
		}
	}
};

// "launch" gauge coords
int Reticle_launch_coords[GR_NUM_RESOLUTIONS][2] = {
	{ // GR_640
		297,	161
	},
	{ // GR_1024
		475,	258
	}
};

#define THREAT_DUMBFIRE				(1<<0)
#define THREAT_ATTEMPT_LOCK			(1<<1)
#define THREAT_LOCK					(1<<2)

#define THREAT_UPDATE_DUMBFIRE_TIME		1000		// time between checking for dumbfire threats
#define THREAT_UPDATE_LOCK_TIME			500		// time between checking for lock threats

#define THREAT_DUMBFIRE_FLASH				180
#define THREAT_LOCK_FLASH					180
static int Threat_lock_timer;				// timestamp for when to show next flashing frame for lock threat
static int Threat_lock_frame;				// frame offset of current lock flashing warning


HudGaugeReticle::HudGaugeReticle():
HudGauge(HUD_OBJECT_CENTER_RETICLE, HUD_CENTER_RETICLE, true, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_TOPDOWN | VM_OTHER_SHIP), 255, 255, 255)
{
	has_autoaim_lock = false;
}

void HudGaugeReticle::initBitmaps(char *fname)
{
	crosshair.first_frame = bm_load_animation(fname, &crosshair.num_frames);
	if (crosshair.first_frame < 0) {
		Warning(LOCATION, "Cannot load hud ani: %s\n", fname);
	}
}

void HudGaugeReticle::initFirepointDisplay(bool firepoint, int scaleX, int scaleY, int size) {
	firepoint_display = firepoint;
	firepoint_scale_x = scaleX;
	firepoint_scale_y = scaleY;
	firepoint_size = size;
}

void HudGaugeReticle::render(float  /*frametime*/)
{
	if (crosshair.first_frame < 0) {
		return;
	}

	ship_info *sip = &Ship_info[Player_ship->ship_info_index];

	if (autoaim_frame_offset > 0 || sip->autoaim_lock_snd.isValid() || sip->autoaim_lost_snd.isValid()) {
		ship *shipp = &Ships[Objects[Player->objnum].instance];
		ship_weapon *swp = &shipp->weapons;
		ai_info *aip = &Ai_info[shipp->ai_index];

		if (aip->target_objnum != -1) {
			bool autoaiming = false;

			autoaiming = in_autoaim_fov(shipp, swp->current_primary_bank, &Objects[aip->target_objnum]);

			if (autoaiming) {
				if (!has_autoaim_lock && sip->autoaim_lock_snd.isValid()) {
					snd_play(gamesnd_get_game_sound(sip->autoaim_lock_snd));
					//snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::THREAT_FLASH)));
				}
				has_autoaim_lock = true;
			}
			else {
				if (has_autoaim_lock && sip->autoaim_lost_snd.isValid()) {
					snd_play(gamesnd_get_game_sound(sip->autoaim_lost_snd));
				}
				has_autoaim_lock = false;
			}
		}
	}

	setGaugeColor(HUD_C_BRIGHT);

	if (HUD_shadows){
		gr_set_color_fast(&Color_black);

		if (has_autoaim_lock)
		{
			// Render the shadow twice to increase visibility
			renderBitmap(crosshair.first_frame + autoaim_frame_offset, position[0] + 1, position[1] + 1);
			renderBitmap(crosshair.first_frame + autoaim_frame_offset, position[0] + 1, position[1] + 1);
		}
		else
		{
			// Render the shadow twice to increase visibility
			renderBitmap(crosshair.first_frame, position[0] + 1, position[1] + 1);
			renderBitmap(crosshair.first_frame, position[0] + 1, position[1] + 1);
		}
		gr_set_color_fast(&gauge_color);
	}

	if (has_autoaim_lock)
		renderBitmap(crosshair.first_frame + autoaim_frame_offset, position[0], position[1]);
	else
		renderBitmap(crosshair.first_frame, position[0], position[1]);

	if (firepoint_display) {
		fp.clear();
		getFirepointStatus();
		
		if (!fp.empty()) {
			int ax, ay;
			bm_get_info(crosshair.first_frame, &ax, &ay);
			int centerX = position[0] + (ax / 2);
			int centerY = position[1] + (ay / 2);

			for (SCP_vector<firepoint>::iterator fpi = fp.begin(); fpi != fp.end(); ++fpi) {
				if (fpi->active == 2)
					setGaugeColor(HUD_C_BRIGHT);
				else if (fpi->active == 1)
					setGaugeColor(HUD_C_NORMAL);
				else
					setGaugeColor(HUD_C_DIM);
			
				renderCircle((int) (centerX + (fpi->xy.x * firepoint_scale_x)), (int) (centerY + (fpi->xy.y * firepoint_scale_y)), firepoint_size);
			}
		}
	}
}

void HudGaugeReticle::getFirepointStatus() {

	// allow the firepoint status to be empty when a multiplayer observer
	// this is not a bug, the observer will simply *not* have any firepoints.
	if (Objects[Player->objnum].type == OBJ_OBSERVER) {
		// only multiplayer instances should be getting here!
		Assertion((Game_mode & GM_MULTIPLAYER), "Somehow FSO thinks its player object is an observer even though it's not in Multiplayer. Please report!");
		return; 
	}

	//First, get the player ship
	ship_info* sip;
	ship* shipp;
	polymodel* pm;

	Assertion(Objects[Player->objnum].type == OBJ_SHIP, "HudGaugeReticle::getFirepointStatus was passed an invalid object type of %d. Please report!", Objects[Player->objnum].type);

	if (Objects[Player->objnum].type == OBJ_SHIP) {
		shipp = &Ships[Objects[Player->objnum].instance];
		sip = &Ship_info[shipp->ship_info_index];
	
		//Get the player eyepoint
		pm = model_get(sip->model_num);

		if (pm->n_view_positions == 0) {
			mprintf(("Model %s does not have a defined eyepoint. Firepoint display could not be generated\n", pm->filename));
		} else  {
			if (pm->n_guns > 0) {
				eye eyepoint = pm->view_positions[shipp->current_viewpoint];
				vec2d ep = { eyepoint.pnt.xyz.x, eyepoint.pnt.xyz.y };

				for (int i = 0; i < pm->n_guns; i++) {
					int bankactive = 0;
					ship_weapon *swp = &shipp->weapons;

					// If this firepoint doesn't actually have a weapon mounted, skip all of this
					if (swp->primary_bank_weapons[i] < 0) {
						continue;
					}

					// bank is firing
					// (make sure we haven't cycled banks recently, since that will also reset the timestamp)
					if (   (!Control_config[CYCLE_NEXT_PRIMARY].digital_used.isValid() || timestamp_since(Control_config[CYCLE_NEXT_PRIMARY].digital_used) > BANK_SWITCH_DELAY)
						&& (!Control_config[CYCLE_PREV_PRIMARY].digital_used.isValid() || timestamp_since(Control_config[CYCLE_PREV_PRIMARY].digital_used) > BANK_SWITCH_DELAY)
						&& (!timestamp_elapsed(shipp->weapons.next_primary_fire_stamp[i]) || !timestamp_elapsed(shipp->weapons.primary_animation_done_time[i]))) {
						bankactive = 2;
					}
					// bank is selected
					else if (i == shipp->weapons.current_primary_bank || shipp->flags[Ship::Ship_Flags::Primary_linked]) {
						bankactive = 1;
					}

					int num_slots = pm->gun_banks[i].num_slots;
					for (int j = 0; j < num_slots; j++) {
						int fpactive = bankactive;

						if (sip->flags[Ship::Info_Flags::Dyn_primary_linking]) {
							// If this firepoint is not among the next shot(s) to be fired, dim it one step
							if ( !( (j >= (shipp->last_fired_point[i]+1) % num_slots) && (j <= (shipp->last_fired_point[i]+swp->primary_bank_slot_count[i]) % num_slots) ) ) {
								fpactive--;
							}
						} else if (Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Cycle]) {
							// If this firepoint is not the next one to be fired, dim it one step
							if (j != (shipp->last_fired_point[i]+1) % num_slots) {
								fpactive--;
							}
						}

						vec3d fpfromeye;

						matrix eye_orient, player_transpose;

						vm_copy_transpose(&player_transpose, &Objects[Player->objnum].orient);
						vm_matrix_x_matrix(&eye_orient, &player_transpose, &Eye_matrix);
						vm_vec_rotate(&fpfromeye, &pm->gun_banks[i].pnt[j], &eye_orient);

						firepoint tmp = { { fpfromeye.xyz.x - ep.x, ep.y - fpfromeye.xyz.y }, fpactive };
						fp.push_back(tmp);
					}
				}
			}
		}
	}
}

void HudGaugeReticle::setAutoaimFrame(int framenum) {
	if (framenum < 0 || framenum > crosshair.num_frames - 1)
		autoaim_frame_offset = 0;
	else
		autoaim_frame_offset = framenum;
}

void HudGaugeReticle::pageIn()
{
	bm_page_in_aabitmap( crosshair.first_frame, crosshair.num_frames);
}

HudGaugeThrottle::HudGaugeThrottle():
HudGauge(HUD_OBJECT_THROTTLE, HUD_THROTTLE_GAUGE, true, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255)
{

}
void HudGaugeThrottle::initThrottleStartY(int y)
{
	Bottom_offset_y = y;
}

void HudGaugeThrottle::initThrottleSizes(int w, int h)
{
	throttle_w = w;
	throttle_h = h;
}

void HudGaugeThrottle::initAburnHeight(int h)
{
	throttle_aburn_h = h;
}

void HudGaugeThrottle::initMaxSpeedOffsets(int x, int y, bool show)
{
	Max_speed_offsets[0] = x;
	Max_speed_offsets[1] = y;
	Show_max_speed = show;
}

void HudGaugeThrottle::initZeroSpeedOffsets(int x, int y, bool show)
{
	Zero_speed_offsets[0] = x;
	Zero_speed_offsets[1] = y;
	Show_min_speed = show;
}

void HudGaugeThrottle::initOrbitCenterOffsets(int x, int y, bool orbiting)
{
	Orbit_center_offsets[0] = x;
	Orbit_center_offsets[1] = y;
	orbit = orbiting;
}

void HudGaugeThrottle::initOrbitRadius(int radius)
{
	orbit_radius = radius;
}

void HudGaugeThrottle::initTargetSpeedOffsets(int x, int y, bool show, bool percent)
{
	Target_speed_offsets[0] = x;
	Target_speed_offsets[1] = y;
	Show_target_speed = show;
	Show_percent = percent;
}

void HudGaugeThrottle::showBackground(bool show)
{
	Show_background = show;
}

void HudGaugeThrottle::initGlideOffsets(int x, int y, bool custom)
{
	Glide_offsets[0] = x;
	Glide_offsets[1] = y;
	Use_custom_glide = custom;
}

void HudGaugeThrottle::initMatchSpeedOffsets(int x, int y, bool custom)
{
	Match_speed_offsets[0] = x;
	Match_speed_offsets[1] = y;
	Use_custom_match_speed = custom;

	auto match_speed_str = XSTR("m", 1667);

	font::FSFont* fsFont = font::get_font(font_num);
	if (fsFont->getType() == font::VFNT_FONT)
	{
		ubyte sc = lcl_get_font_index(font_num);
		// NOTE: default to normal m because either
		// a) the german font has no special m (its an a)
		// b) the font has no special characters
		if (sc == 0 || Lcl_gr) {
			Match_speed_icon = match_speed_str[0];
		}
		else {
			Match_speed_icon = sc + 3;
		}
		Match_speed_draw_background = false;
	}
	else
	{
		// Default version for other fonts, draw a black character on a rectangle
		Match_speed_icon = match_speed_str[0];
		Match_speed_draw_background = true;
	}

	SCP_stringstream stream;
	stream << static_cast<char>(Match_speed_icon);
	const SCP_string& iconStr = stream.str();

	gr_get_string_size(&Match_speed_icon_width, nullptr, iconStr.c_str());
}

void HudGaugeThrottle::initBitmaps(char *fname)
{
	throttle_frames.first_frame = bm_load_animation(fname, &throttle_frames.num_frames);

	if (throttle_frames.first_frame < 0) {
		Warning(LOCATION, "Cannot load hud ani: %s\n", fname);
	}
}

void HudGaugeThrottle::pageIn()
{
	bm_page_in_aabitmap( throttle_frames.first_frame, throttle_frames.num_frames);
}

void HudGaugeThrottle::render(float  /*frametime*/)
{
	if (throttle_frames.first_frame < 0) {
		return;
	}

	float	desired_speed, max_speed, current_speed, absolute_speed, absolute_displayed_speed, max_displayed_speed, percent_max, percent_aburn_max;
	int	desired_y_pos, y_end;

	ship_info	*sip;
	sip = &Ship_info[Player_ship->ship_info_index];

	current_speed = Player_obj->phys_info.fspeed;
	if ( current_speed < 0.0f){
		current_speed = 0.0f;
	}

	max_speed = Player_obj->phys_info.max_vel.xyz.z;
	if ( max_speed <= 0 ) {
		max_speed = sip->max_vel.xyz.z;
	}

	absolute_speed = Player_obj->phys_info.speed;

	// scale by distance modifier from hud_guages.tbl for display purposes
	absolute_displayed_speed = absolute_speed * Hud_speed_multiplier;
	max_displayed_speed = max_speed * Hud_speed_multiplier;

	desired_speed = Player->ci.forward * max_speed;
	if ( desired_speed < 0.0f ){		// so ships that go backwards don't force the indicators below where they can go
		desired_speed = 0.0f;
	}

	desired_y_pos = position[1] + Bottom_offset_y - (int)std::lround(throttle_h*desired_speed/max_speed) - 1;

	if (max_speed <= 0) {
		percent_max = 0.0f;
	} else {
		percent_max = current_speed / max_speed;
	}

	percent_aburn_max = 0.0f;
	if ( percent_max > 1 ) {
		percent_max = 1.0f;
		percent_aburn_max = (current_speed - max_speed) / (Player_obj->phys_info.afterburner_max_vel.xyz.z - max_speed);
		if ( percent_aburn_max > 1.0f ) {
			percent_aburn_max = 1.0f;
		}
		if ( percent_aburn_max < 0 ) {
			percent_aburn_max = 0.0f;
		}
	}

	y_end = position[1] + Bottom_offset_y - (int)std::lround(throttle_h*percent_max);
	if ( percent_aburn_max > 0 ) {
		y_end -= (int)std::lround(percent_aburn_max * throttle_aburn_h);
	}

	if ( Player_obj->phys_info.flags & PF_AFTERBURNER_ON ) {
		// default value is 240 when afterburner is on. 
		//I'm assuming that this value is basically Bottom_offset_y - throttle_aburn_h - throttle_h
		desired_y_pos = position[1] + Bottom_offset_y - throttle_aburn_h - throttle_h; 
	}

	setGaugeColor();
	
	if(Show_background) {
		renderThrottleBackground(y_end);
	} else {
		renderBitmap(throttle_frames.first_frame, position[0], position[1]);			
	}

	// draw throttle speed number
	//hud_render_throttle_speed(current_speed, y_end);
	// Absolute speed, not forward speed, for hud speed reticle - fixes the guage for sliding -- kazan
	renderThrottleSpeed(absolute_displayed_speed, y_end);

	// draw target speed if necessary
	if ( Show_target_speed ) {
		char buf[32];
		int w, h;

		if ( Show_percent ) {
			if ( Player_obj->phys_info.flags & PF_AFTERBURNER_ON ) {
				strcpy_s(buf, XSTR( "A/B", 1669 ));
			} else {
				sprintf(buf, XSTR( "%d%%", 326), (int)std::lround( (desired_speed/max_speed)*100 ));
			}
		} else {
			sprintf(buf, "%d", (int)std::lround(desired_speed * Hud_speed_multiplier));
		}

		hud_num_make_mono(buf, font_num);
		gr_get_string_size(&w, &h, buf);

		renderString(position[0] + Target_speed_offsets[0] - w, position[1] + Target_speed_offsets[1], buf);
	}

	// draw the "desired speed" bar on the throttle
	renderThrottleLine(desired_y_pos);

	// draw left arc (the bright portion of the throttle gauge)
	renderThrottleForeground(y_end);

	if ( Show_max_speed ) {
		renderPrintf(position[0] + Max_speed_offsets[0], position[1] + Max_speed_offsets[1], "%d", (int)std::lround(max_displayed_speed));
	}
	
	if ( Show_min_speed ) {
		renderPrintf(position[0] + Zero_speed_offsets[0], position[1] + Zero_speed_offsets[1], "%s", XSTR( "0", 292));
	}
}

void HudGaugeThrottle::renderThrottleSpeed(float current_speed, int y_end)
{
	char buf[32];
	int sx, sy, x_pos, y_pos, w, h;

	//setGaugeColor();
	sprintf(buf, "%d", (int)std::lround(current_speed));
	hud_num_make_mono(buf, font_num);
	gr_get_string_size(&w, &h, buf);

	if ( orbit ) {
		// y_end is the y-coordinate of the current throttle setting, calc x-coordinate for edge of 
		// circle (x^2 + y^2 = r^2)
		y_pos = position[1] + Orbit_center_offsets[1] - y_end;
		x_pos = (int)sqrt(double(orbit_radius * orbit_radius - y_pos * y_pos) );
		x_pos = position[0] + Orbit_center_offsets[0] - x_pos;

		// draw current speed at (x_pos, y_end);
		sx = x_pos - w - 2;
		sy = fl2i(y_end - h/2.0f + 1.5);
	} else {
		sx = position[0] + Orbit_center_offsets[0] - w;
		sy = position[1] + Orbit_center_offsets[1];
	}
	
	renderPrintf(sx, sy, "%s", buf);

	if ( object_get_gliding(Player_obj) ) { 
		auto glide_str = XSTR("GLIDE", 1668);

		if ( Use_custom_glide ) {
			renderString(position[0] + Glide_offsets[0], position[1] + Glide_offsets[1], glide_str);
		} else {
			int offset;
			if ( current_speed <= 9.5 ) {
				offset = -31;
			} else if ( current_speed <= 99.5 ) {
				offset = -22;
			} else {
				offset = -13;
			}

			renderString(sx+offset, sy + h, glide_str);
		}
	} else if ( Players[Player_num].flags & PLAYER_FLAGS_MATCH_TARGET ) {
		if ( Use_custom_match_speed ) {
			renderMatchSpeedIcon(position[0] + Match_speed_offsets[0], position[1] + Match_speed_offsets[1]);
		} else {
			int offset;
			if ( current_speed <= 9.5 ) {
				offset = 0;
			} else {
				offset = 3;
			}

			renderMatchSpeedIcon(sx+offset, sy + h);
		}
	}
}

void HudGaugeThrottle::renderThrottleLine(int y_line)
{
	// hud_set_bright_color();
	//setGaugeColor(HUD_C_BRIGHT);

	renderBitmapEx(throttle_frames.first_frame+3, 
		position[0], y_line, 
		throttle_w, 1, 
		0, 
		y_line-position[1]);
}

void HudGaugeThrottle::renderThrottleForeground(int y_end)
{
	int w,h;

	//setGaugeColor();

	bm_get_info(throttle_frames.first_frame+1,&w,&h);

	if ( y_end < (position[1] + h - 1) ) {		
		renderBitmapEx(throttle_frames.first_frame + 2, position[0], y_end, w, h - (y_end - position[1]), 0, y_end - position[1]);
	}
}

void HudGaugeThrottle::renderThrottleBackground(int y_end)
{
	int w,h;

	//setGaugeColor();

	bm_get_info( throttle_frames.first_frame+1,&w,&h);

	if ( y_end > position[1] ) {
		renderBitmapEx(throttle_frames.first_frame+1, position[0], position[1], w, y_end-position[1]+1, 0, 0);		
	}
}

void HudGaugeThrottle::renderMatchSpeedIcon(int x, int y)
{
	if (Match_speed_draw_background)
	{
		// One pixel boundary
		renderRect(x, y, Match_speed_icon_width + 2, gr_get_font_height() + 2);
		
		gr_set_color_fast(&Color_black);
		renderPrintf(x + 1, y + 1, "%c", Match_speed_icon);

		setGaugeColor();
	}
	else
	{
		renderPrintf(x, y, "%c", Match_speed_icon);
	}
}

HudGaugeThreatIndicator::HudGaugeThreatIndicator():
HudGauge(HUD_OBJECT_THREAT, HUD_THREAT_GAUGE, true, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255)
{
}

void HudGaugeThreatIndicator::initLaserWarnOffsets(int x, int y)
{
	Laser_warn_offsets[0] = x;
	Laser_warn_offsets[1] = y;
}

void HudGaugeThreatIndicator::initLockWarnOffsets(int x, int y)
{
	Lock_warn_offsets[0] = x;
	Lock_warn_offsets[1] = y;
}

void HudGaugeThreatIndicator::initBitmaps(char *fname_arc, char *fname_laser, char *fname_lock)
{
	threat_arc.first_frame = bm_load_animation(fname_arc, &threat_arc.num_frames);
	if (threat_arc.first_frame < 0) {
		Warning(LOCATION, "Cannot load hud ani: %s\n", fname_arc);
	}
	
	laser_warn.first_frame = bm_load_animation(fname_laser, &laser_warn.num_frames);
	if (laser_warn.first_frame < 0) {
		Warning(LOCATION, "Cannot load hud ani: %s\n", fname_laser);
	}

	lock_warn.first_frame = bm_load_animation(fname_lock, &lock_warn.num_frames);
	if (lock_warn.first_frame < 0) {
		Warning(LOCATION, "Cannot load hud ani: %s\n", fname_lock);
	}
}

void HudGaugeThreatIndicator::initialize()
{
	laser_warn_timer = timestamp(1);
	laser_warn_frame = 0;

	lock_warn_timer = timestamp(1);
	lock_warn_frame = 0;

	HudGauge::initialize();
}

void HudGaugeThreatIndicator::pageIn()
{
	bm_page_in_aabitmap(threat_arc.first_frame, threat_arc.num_frames);
	bm_page_in_aabitmap(laser_warn.first_frame, laser_warn.num_frames);
	bm_page_in_aabitmap(lock_warn.first_frame, lock_warn.num_frames);
}

void HudGaugeThreatIndicator::render(float  /*frametime*/)
{
	setGaugeColor();

	if (threat_arc.first_frame >= 0)
		renderBitmap(threat_arc.first_frame+1, position[0], position[1]);

	renderLaserThreat();
	renderLockThreat();
}

void HudGaugeThreatIndicator::renderLaserThreat()
{
	if (laser_warn.first_frame < 0)
		return;

	int frame_offset, num_frames;

	//Check how many frames the ani actually has
	num_frames = laser_warn.num_frames;
	//We need at least two frames here
	Assert( num_frames >= 2 );

	if ( Player->threat_flags & THREAT_DUMBFIRE ) {
		if ( timestamp_elapsed(laser_warn_timer) ) {
			laser_warn_timer = timestamp(THREAT_DUMBFIRE_FLASH);
			laser_warn_frame++;
			if ( laser_warn_frame > (num_frames - 1) ) { //The first frame being the default "off" setting, we need to cycle through all the other frames
				laser_warn_frame = 1;
			}
		}
		frame_offset = laser_warn_frame;
	} else {
		frame_offset = 0;
	}

	renderBitmap(laser_warn.first_frame + frame_offset, position[0] + Laser_warn_offsets[0], position[1] + Laser_warn_offsets[1]);	
}

void HudGaugeThreatIndicator::renderLockThreat()
{
	if (lock_warn.first_frame < 0)
		return;

	int frame_offset, num_frames;

	//Let's find out how many frames our ani has, and adjust accordingly
	num_frames = lock_warn.num_frames;
	//We need at least two frames here
	Assert( num_frames >= 2 );

	if ( Player->threat_flags & (THREAT_LOCK | THREAT_ATTEMPT_LOCK) ) {
		if ( timestamp_elapsed(lock_warn_timer) ) {
			if ( Player->threat_flags & THREAT_LOCK )  {
				lock_warn_timer = timestamp(fl2i(THREAT_LOCK_FLASH/2.0f));
			} else {
				lock_warn_timer = timestamp(THREAT_LOCK_FLASH);
			}
			lock_warn_frame++;
			if ( lock_warn_frame > (num_frames - 1) ) { //The first frame being the default "off" setting, we need to cycle through all the other frames
				lock_warn_frame = 1;
			}
		}
		frame_offset = lock_warn_frame;
	} else {
		frame_offset = 0;
	}

	renderBitmap(lock_warn.first_frame+frame_offset, position[0] + Lock_warn_offsets[0], position[1] + Lock_warn_offsets[1]);
}

HudGaugeWeaponLinking::HudGaugeWeaponLinking():
HudGauge(HUD_OBJECT_WEAPON_LINKING, HUD_THREAT_GAUGE, true, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255)
{
}

void HudGaugeWeaponLinking::init1PrimaryOffsets(int x, int y)
{
	Weapon_link_offsets[LINK_ONE_PRIMARY][0] = x;
	Weapon_link_offsets[LINK_ONE_PRIMARY][1] = y;
}

void HudGaugeWeaponLinking::init2PrimaryOffsets(int x, int y)
{
	Weapon_link_offsets[LINK_TWO_PRIMARY][0] = x;
	Weapon_link_offsets[LINK_TWO_PRIMARY][1] = y;
}

void HudGaugeWeaponLinking::init1SecondaryOffsets(int x, int y)
{
	Weapon_link_offsets[LINK_ONE_SECONDARY][0] = x;
	Weapon_link_offsets[LINK_ONE_SECONDARY][1] = y;
}

void HudGaugeWeaponLinking::init2SecondaryOffsets(int x, int y)
{
	Weapon_link_offsets[LINK_TWO_SECONDARY][0] = x;
	Weapon_link_offsets[LINK_TWO_SECONDARY][1] = y;
}

void HudGaugeWeaponLinking::init3SecondaryOffsets(int x, int y)
{
	Weapon_link_offsets[LINK_THREE_SECONDARY][0] = x;
	Weapon_link_offsets[LINK_THREE_SECONDARY][1] = y;
}

void HudGaugeWeaponLinking::initBitmaps(char *fname_arc, 
										char *fname_primary_link_1, 
										char *fname_primary_link_2, 
										char *fname_secondary_link_1, 
										char *fname_secondary_link_2, 
										char *fname_secondary_link_3)
{
	arc.first_frame = bm_load_animation(fname_arc, &arc.num_frames);
	if (arc.first_frame < 0) {
		Warning(LOCATION, "Cannot load hud ani: %s\n", fname_arc);
	}
	
	weapon_linking_modes[LINK_ONE_PRIMARY].first_frame = bm_load_animation(fname_primary_link_1, &weapon_linking_modes[LINK_ONE_PRIMARY].num_frames);
	if (weapon_linking_modes[LINK_ONE_PRIMARY].first_frame < 0) {
		Warning(LOCATION, "Cannot load hud ani: %s\n", fname_primary_link_1);
	}

	weapon_linking_modes[LINK_TWO_PRIMARY].first_frame = bm_load_animation(fname_primary_link_2, &weapon_linking_modes[LINK_TWO_PRIMARY].num_frames);
	if (weapon_linking_modes[LINK_TWO_PRIMARY].first_frame < 0) {
		Warning(LOCATION, "Cannot load hud ani: %s\n", fname_primary_link_2);
	}

	weapon_linking_modes[LINK_ONE_SECONDARY].first_frame = bm_load_animation(fname_secondary_link_1, &weapon_linking_modes[LINK_ONE_SECONDARY].num_frames);
	if (weapon_linking_modes[LINK_ONE_SECONDARY].first_frame < 0) {
		Warning(LOCATION, "Cannot load hud ani: %s\n", fname_secondary_link_1);
	}

	weapon_linking_modes[LINK_TWO_SECONDARY].first_frame = bm_load_animation(fname_secondary_link_2, &weapon_linking_modes[LINK_TWO_SECONDARY].num_frames);
	if (weapon_linking_modes[LINK_TWO_SECONDARY].first_frame < 0) {
		Warning(LOCATION, "Cannot load hud ani: %s\n", fname_secondary_link_2);
	}

	weapon_linking_modes[LINK_THREE_SECONDARY].first_frame = bm_load_animation(fname_secondary_link_3, &weapon_linking_modes[LINK_THREE_SECONDARY].num_frames);
	if (weapon_linking_modes[LINK_THREE_SECONDARY].first_frame < 0) {
		Warning(LOCATION, "Cannot load hud ani: %s\n", fname_secondary_link_3);
	}
}


void HudGaugeWeaponLinking::pageIn()
{
	bm_page_in_aabitmap(arc.first_frame, arc.num_frames);

	for(int i = 0; i < NUM_WEAPON_LINK_MODES; i++) {
		bm_page_in_aabitmap(weapon_linking_modes[i].first_frame, weapon_linking_modes[i].num_frames);
	}
}

void HudGaugeWeaponLinking::render(float  /*frametime*/)
{
	int			gauge_index=0, frame_offset=0;
	ship_weapon	*swp;

	setGaugeColor();

	if (arc.first_frame >= 0) {
		renderBitmap(arc.first_frame+1, position[0], position[1]);
	}

	swp = &Player_ship->weapons;

	switch( swp->num_primary_banks ) {
		case 0:
			gauge_index = -1;
			break;

		case 1:
			gauge_index = LINK_ONE_PRIMARY;
			if ( Player_ship->weapons.current_primary_bank == -1 ) {
				frame_offset = 0;	
			} else {
				frame_offset = 1;	
			}
			break;

		case 2:
			gauge_index = LINK_TWO_PRIMARY;
			if ( swp->current_primary_bank == -1 ) {
				frame_offset = 0;	
			} else {
				if ( Player_ship->flags[Ship::Ship_Flags::Primary_linked] ) {
					frame_offset = 3;
				} else {
					if ( swp->current_primary_bank == 0 ) {
						frame_offset = 1;
					} else {
						frame_offset = 2;
					}
				}
			}
			break;

		default:
			Int3();	// shouldn't happen (get Alan if it does)
			return;
			break;
	}
	
	if ( gauge_index != -1 ) {
		if (weapon_linking_modes[gauge_index].first_frame >= 0) {
			renderBitmap(weapon_linking_modes[gauge_index].first_frame+frame_offset, position[0] + Weapon_link_offsets[gauge_index][0], position[1] + Weapon_link_offsets[gauge_index][1]);
		}
	}

	int num_banks = swp->num_secondary_banks;
	if ( num_banks <= 0 ) {
		num_banks = Ship_info[Player_ship->ship_info_index].num_secondary_banks;
	}

	switch( num_banks ) {
		case 0:
			gauge_index = -1;
			break;

		case 1:
			gauge_index = LINK_ONE_SECONDARY;
			break;

		case 2:
			gauge_index = LINK_TWO_SECONDARY;
			break;

		case 3:
			gauge_index = LINK_THREE_SECONDARY;
			break;

		default:
			Int3();	// shouldn't happen (get Alan if it does)
			return;
			break;
	}
	
	if ( gauge_index != -1 ) {
		if ( swp->num_secondary_banks <= 0 ) {
			frame_offset = 0;
		} else {
			frame_offset = swp->current_secondary_bank+1;
		}
		if (weapon_linking_modes[gauge_index].first_frame >= 0) {
			renderBitmap(weapon_linking_modes[gauge_index].first_frame+frame_offset, position[0] + Weapon_link_offsets[gauge_index][0],  position[1] + Weapon_link_offsets[gauge_index][1]);
		}
	}
}

// Called at the start of each level.. use Reticle_inited so we only load frames once
void hud_init_reticle()
{
	Threat_lock_timer = timestamp(0);
	Threat_lock_frame = 1;
	Player->threat_flags = 0;
	Player->update_dumbfire_time = timestamp(0);		
	Player->update_lock_time = timestamp(0);
}

// called once per frame to update the reticle gauges.  Makes calls to
// ship_dumbfire_threat() and ship_lock_threat() and updates Threat_flags.
void hud_update_reticle( player *pp )
{
	int rval;
	ship *shipp;

	// multiplayer clients won't call this routine
	if ( MULTIPLAYER_CLIENT || MULTI_OBSERVER(Net_players[MY_NET_PLAYER_NUM]))
		return;

	shipp = &Ships[Objects[pp->objnum].instance];

	if ( ship_dumbfire_threat(shipp) ) {
		pp->threat_flags |= THREAT_DUMBFIRE;
		pp->update_dumbfire_time = timestamp(THREAT_UPDATE_DUMBFIRE_TIME);
	}

	if ( timestamp_elapsed(pp->update_dumbfire_time) ) {
		pp->update_dumbfire_time = timestamp(THREAT_UPDATE_DUMBFIRE_TIME);
		pp->threat_flags &= ~THREAT_DUMBFIRE;
	}

	if ( timestamp_elapsed(pp->update_lock_time) ) {
		pp->threat_flags &= ~(THREAT_LOCK | THREAT_ATTEMPT_LOCK);
		pp->update_lock_time = timestamp(THREAT_UPDATE_LOCK_TIME);
		rval = ship_lock_threat(shipp);
		if ( rval == 1 ) {
			pp->threat_flags |= THREAT_ATTEMPT_LOCK;
		} else if ( rval == 2 ) {
			pp->threat_flags |= THREAT_LOCK;
		}
	}

	if(Player->threat_flags & THREAT_LOCK ) {
		// a less hacked up version of the missile launch warning
		hud_start_text_flash(XSTR("Launch", 1507), THREAT_LOCK_FLASH, fl2i(THREAT_LOCK_FLASH/2.0f));
	}

	if ( Player->threat_flags & (THREAT_ATTEMPT_LOCK) ) {
		if ( timestamp_elapsed(Threat_lock_timer) ) {
			Threat_lock_timer = timestamp(THREAT_LOCK_FLASH);
			
			Threat_lock_frame++;
			if ( Threat_lock_frame > 2 ) {
				Threat_lock_frame = 1;
			}
			if ( (Threat_lock_frame == 2) && (Player->threat_flags & THREAT_ATTEMPT_LOCK ) ) {
				snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::THREAT_FLASH)));
			}
		}
	} 
}
