//===========================================================================//
// File:	hud.cpp                                                             //
// Project: BattleTech                                                       //
// Contents: Cockpit Heads-Up Display subsystem                              //
//           (reticle / torso-horizon gimbal / threat & status read-outs)    //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
//                      PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, recovered shard
// part_013.c).  Cluster @004b77bc..@004b8568.  Each non-trivial method cites
// its @ADDR.  Hex float constants converted to decimal where known
// (0x44960000 = 1200.0, 0x43fa0000 = 500.0, 500.0 = max torso slew/sec).
//
// Helper-function name mapping (engine internals referenced by the decomp):
//      FUN_004b18a4  PowerWatcher base constructor             (powersub.cpp)
//      FUN_004b198c  PowerWatcher::CreateStreamedSubsystem      (powersub.cpp)
//      FUN_004b181c  PowerWatcher base per-frame update         (powersub.cpp)
//      FUN_004b1804  PowerWatcher::ResetToInitialState (slot10) (powersub.cpp)
//      FUN_004b1930  ~PowerWatcher                              (powersub.cpp)
//      FUN_0041b9ec  AlarmIndicator(levels)
//      FUN_0041baa4  ~AlarmIndicator
//      FUN_0041bbd8  AlarmIndicator::SetLevel(n)
//      FUN_0041a1a4  IsDerivedFrom(classDerivations)
//      FUN_00404118  NotationFile::ReadScalar(file,name,&dest)
//      FUN_00408440  Point3D::operator= / copy
//      FUN_004dbb24  diagnostic stream << (error reporting)
//      FUN_004022d0  operator delete / global free
//      FUN_004b7ed4  HUD::UpdateFlicker
//
#include <bt.hpp>
#pragma hdrstop

#if !defined(HUD_HPP)
#	include <hud.hpp>
#endif
#if !defined(APP_HPP)
#	include <app.hpp>
#endif
#if !defined(TESTBT_HPP)
#	include <testbt.hpp>
#endif

// BASE-CHAIN RE-BASE witness: HUD is the only currently-LIVE Watcher-branch class
// (HUD : PowerWatcher).  After the +156 re-base, its first own field must land at
// 0x1D8 -- the same base end as Torso::currentTwist -- which also corrects HUD's
// own torso+0x1D8 read.  A friend so it can offsetof the protected field.
struct HUDLayoutCheck
{
	static_assert(offsetof(HUD, flickerRate) == 0x1D8, "HUD first own field must be at 0x1D8 after the Watcher-branch re-base");
};

//
// Tuning constants observed as read-only float globals adjacent to the
// HudSimulation body (.rdata, recovered from section_dump.txt).
//
static const Point3D HudZeroVector(0.0f, 0.0f, 0.0f);	// DAT_004e0f74/78/7c
static const Scalar SegmentTempLimit  = 0.0f;	// _DAT_004b7ec4 (heat threshold for HUD page visibility)
static const Scalar TargetTempLimit   = 0.0f;	// _DAT_004b7ec8
static const Scalar RangeBias         = 0.0f;	// _DAT_004b7ecc
static const Scalar MaxTorsoSlew      = 500.0f;	// _DAT_004b7ed0
static const Scalar FlickerFloor      = 0.0f;	// _DAT_004b7f90

//
// Cross-family helper (definition lives in the mech game layer; declared here
// so this reconstructed body links without editing mech.hpp).  Returns the
// owning Mech's segment-flags word (Mech +segmentFlags).  See CROSS-FAMILY NEEDS.
//
extern LWord BT_GetSegmentFlags(Mech *owner);

//
// Resource-parse error reporter (original FUN_004dbb24 + abort path).  Routes
// through the shared recon DebugStream warning channel.
//
static void ReportError(const char *subsystem_name, const char *message)
{
	DebugStream << "HUD: " << subsystem_name << ": " << message << "\n";
}

//###########################################################################
//					Shared Data Support
//
// DefaultData @00511078 / class derivation chain @00511088.  Mirrors the
// engine's accessor-form shared-data construction (cf. PoweredSubsystem).
//
HUD::SharedData
	HUD::DefaultData(				// @00511078
		HUD::GetClassDerivations(),
		HUD::GetMessageHandlers(),
		HUD::GetAttributeIndex(),
		HUD::StateCount
	);

Derivation*
	HUD::GetClassDerivations()		// @00511088
	{
		static Derivation classDerivations(
			PowerWatcher::GetClassDerivations(),
			"HUD"
		);
		return &classDerivations;
	}

//###########################################################################
//					Construction -- @004b7f94
//
// Chains to the PowerWatcher ctor (FUN_004b18a4) with HUD::DefaultData
// (&DAT_00511078), installs the HUD vtable (PTR_FUN_00511510), primes the
// gauge read-out block, builds the 2-level status AlarmIndicator (@0x238) and
// -- unless this is a damaged copy segment ((segmentFlags & 0xC) != 0) and the
// "has-performance" flag (0x100) is set -- registers HudSimulation as the
// active Performance (this[7..9] <- PTR_FUN_00511170).  Finally it copies the
// streamed FlickerRate / HorizontalMovementPerSecond / HorizontalLimit into
// place and snapshots the torso/target read-outs from the linked entity
// (owner Mech +0x438).
//
HUD::HUD(
	Mech				*owner,
	int					subsystem_ID,
	SubsystemResource	*subsystem_resource,
	SharedData			&shared_data
):
	PowerWatcher(owner, subsystem_ID, subsystem_resource, shared_data)
{
	Check(owner);
	Check_Pointer(subsystem_resource);

	flickerRate         = 0.0f;		// @0x1D8 (overwritten below from resource)
	torsoLimitRight     = 0.0f;		// @0x1DC
	torsoLimitLeft      = 0.0f;		// @0x1E0
	torsoSpeed          = 0.0f;		// @0x1E4
	rangeToTarget       = 0.0f;		// @0x1E8
	targetRange         = 0.0f;		// @0x1EC
	lockFlag            = True;		// @0x1F4 = 1
	visible             = False;	// @0x1F8
	threatVector        = HudZeroVector;	// @0x1FC
	compassHeading      = HudZeroVector;	// @0x208
	hotBoxScalar        = 0.0f;		// @0x214
	blinkTimer          = 0.0f;		// @0x218
	flickerTimer        = 0.0f;		// @0x21C
	blinkState          = True;		// @0x224 = 1
	transitionFlag      = False;	// @0x22C
	previousHorizontal  = 0.0f;		// @0x230

	statusAlarm.Initialize(2);		// FUN_0041b9ec(this+0x238, 2)

	freeAimSlew           = 0.0f;	// @0x28C (mapper free-aim demand)
	scratch290            = 0.0f;	// @0x290
	horizontalTorsoOffset = 0.0f;	// @0x294

	if (((BT_GetSegmentFlags(owner) & 0xC) == 0) &&
		((BT_GetSegmentFlags(owner) & 0x100) != 0))
	{
		SetPerformance(&HUD::HudSimulation);	// this[7..9] <- PTR_FUN_00511170
	}

	flickerRate                 = subsystem_resource->flickerRate;					// @0x1D8 <- res +0xF4
	horizontalMovementPerSecond = subsystem_resource->horizontalMovementPerSecond;	// @0x298 <- res +0xFC
	horizontalLimit             = subsystem_resource->horizontalLimit;				// @0x29C <- res +0x100

	// owner+0x438 is the torso-orientation source -- a Mech RAW OFFSET that does not
	// hold a valid pointer in the reconstructed Mech layout (and isn't linked yet at
	// subsystem-construction time), so dereferencing it crashes.  Default the
	// torso/range readouts to 0; HudSimulation refreshes them per-frame once the link
	// is live.  TODO: wire a real Mech torso-orientation-source accessor.
	linkedEntity = 0;									// @0x234
	torsoLimitRight = 0.0f;
	torsoLimitLeft  = 0.0f;
	rangeToTarget   = 0.0f;
	torsoSpeed      = 0.0f;

	Check_Fpu();
}

//
// Destruction -- @004b816c (vtable slot0).  Reinstalls the vtable, clears the
// owning Mech's cached HUD back-reference (*(owner + 0x374) = 0), tears down
// the status AlarmIndicator and chains to ~PowerWatcher (FUN_004b1930);
// frees the block when the deleting-dtor bit is set.
//
HUD::~HUD()
{
	Check(this);
	// *(this->owner + 0x374) = 0;
	// (original FUN_0041baa4 finalised the AlarmIndicator; HeatAlarm needs no teardown)
	Check_Fpu();
}

//###########################################################################
//					TestInstance -- @004b81b8
//
Logical
	HUD::TestInstance() const
{
	return IsDerivedFrom(*GetClassDerivations());	// FUN_0041a1a4(**this[3], 0x00511088)
}

Logical
	HUD::TestClass(Mech &)
{
	return True;	// BEST-EFFORT (family convention; no distinct body captured)
}

//###########################################################################
//					SetCompassHeading -- @004b7810
//
// Copies a Point3D into compassHeading (@0x208).
//
void
	HUD::SetCompassHeading(const Point3D &heading)
{
	compassHeading = heading;		// FUN_00408440(this+0x208, &heading)
}

//###########################################################################
//					UpdateFlicker -- @004b7ed4
//
// Decays flickerActive (@0x2A0... actually horizontalTorsoOffset region @0x294)
// toward FlickerFloor by horizontalMovementPerSecond*time, clamping at the
// floor, and reports whether motion is still in progress.  Used to give the
// torso-horizon needle a damped settle.
//
Logical
	HUD::UpdateFlicker(Scalar time_slice)
{
	Scalar step = horizontalMovementPerSecond * time_slice;

	if (FlickerFloor < horizontalTorsoOffset)
	{
		horizontalTorsoOffset -= step;
		if (horizontalTorsoOffset < FlickerFloor)
			horizontalTorsoOffset = FlickerFloor;
	}
	if (horizontalTorsoOffset < FlickerFloor)
	{
		horizontalTorsoOffset += step;
		if (horizontalTorsoOffset < FlickerFloor)	// (decompiled clamp, verbatim)
			horizontalTorsoOffset = FlickerFloor;
	}
	return (horizontalTorsoOffset != FlickerFloor) ? True : False;
}

//###########################################################################
//					ResetToInitialState -- @004b77bc (slot 10)
//
// On a powered reset clears the blink/horizontal-slew accumulators, then
// chains to PowerWatcher::ResetToInitialState (FUN_004b1804).
//
void
	HUD::ResetToInitialState(Logical powered)
{
	if (powered)
	{
		blinkTimer            = 0.0f;	// @0x218
		transitionFlag        = False;	// @0x22C
		flickerTimer          = 0.0f;	// @0x21C
		previousHorizontal    = 0.0f;	// @0x230
	}
	horizontalTorsoOffset = 0.0f;		// @0x294
	freeAimSlew           = 0.0f;		// @0x28C (mapper free-aim demand)
	scratch290            = 0.0f;		// @0x290

	PowerWatcher::ResetToInitialState(True);		// FUN_004b1804 (base override is parameterless)
	(void)powered;
}

//###########################################################################
//					HandleMessage -- @004b7780 (slot 9)
//
// BEST-EFFORT: the vtable installs a HUD-specific HandleMessage at @004b7780,
// but no body was captured in the recovered shards.  A handler table
// @00511180 binds the message "ToggleLamp" to FUN_004b838c.  Chains to the
// PowerWatcher handler otherwise.
//
Logical
	HUD::HandleMessage(int message)
{
	return PowerWatcher::HandleMessage(message);	// TODO: verify @004b7780
}

//###########################################################################
//					HudSimulation -- @004b7830 (Performance, PTR @00511170)
//
// The per-frame HUD update (torso-horizon gimbal + reticle/threat read-outs).
// High-level behaviour recovered from the decomp; offsets retained so a human
// can finish the member naming:
//
//   1. Base update: PowerWatcher base tick (FUN_004b181c).
//   2. Heat / voltage gating of the HUD page via the inherited state levels:
//        watched-voltage alarm level @0x198  (2 -> off-but-visible, 4 -> ready)
//        heat-state level             @0x140  (0/1 -> visible, 2 -> blank)
//      The status AlarmIndicator @0x238 is driven to 0/1 accordingly and a
//      composite "page visible" flag is formed.
//   3. Blink: blinkTimer (@0x218) accumulates time and is compared against
//      flickerRate (@0x1D8); on expiry blinkState (@0x224) toggles and the
//      timer resets.  When the blink is in its OFF phase the page is hidden.
//   4. Engine/start state of the host (mech +0x128 roster[0] state @0x198):
//        0 -> set HUD flag bit 0x1 on the graphic, clear bit 0x2
//        3 -> set bit 0x2, clear bit 0x1 (and gate on mech +0x410)
//      writes the resulting "active" flag to *(mech +0x374).
//   5. Target line-of-sight: if a target entity (mech +0x388) exists and is
//      not over the heat limits, computes targetRange (@0x1EC) as the distance
//      between the HUD anchor (mech +0x37C) and the target (+0x100); sets
//      visible (@0x1F8); otherwise blanks.  A default range 1200.0 is used
//      when there is no target.
//   6. Torso-horizon slew: horizontalTorsoOffset (@0x294) is moved toward the
//      commanded torso heading at up to MaxTorsoSlew (500/sec), clamped to
//      +/- horizontalLimit (@0x29C), then written to the graphic at
//      mech +0x36C.  The flicker helper (@004b7ed4) damps the settle.
//
void
	HUD::HudSimulation(Scalar time_slice)
{
	Check(this);

	PowerWatcher::Simulation(time_slice);	// FUN_004b181c (base per-frame tick)

	//
	// --- Heat / voltage gating of the HUD page ----------------------------
	//
	Logical pageVisible = (HeatWatcherFlag() != 1);		// this[0x40] != 1

	switch (WatchedVoltageLevel())		// @0x198
	{
	case 2:	statusAlarm.SetLevel(0);				break;	// off but page shown
	case 4:	statusAlarm.SetLevel(1);				break;	// ready
	default: statusAlarm.SetLevel(1); pageVisible = False;	break;
	}

	switch (HeatStateLevel())			// @0x140
	{
	case 0:	statusAlarm.SetLevel(1);				break;
	case 1:	statusAlarm.SetLevel(0);				break;
	case 2:	statusAlarm.SetLevel(1); pageVisible = False;	break;
	}

	//
	// --- Blink ------------------------------------------------------------
	//
	if (transitionFlag == 0)
	{
		blinkTimer += time_slice;
		if (blinkTimer != flickerRate)
		{
			blinkTimer = 0.0f;
			blinkState = (blinkState == 0);
		}
	}
	else if (transitionFlag == 1)
	{
		blinkState = True;
	}
	pageVisible = (pageVisible && blinkState) ? True : False;

	//
	// --- (remaining torso-horizon / target / slew logic; see header) ------
	// Faithful offsets are preserved in the recovered shard part_013.c
	// @004b7830; reproduced here in summarised form -- a human should expand
	// the reticle/threat-vector math against the original source.
	//

	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem -- HUD  @004b81d4
//
// Chains to PowerWatcher::CreateStreamedSubsystem (FUN_004b198c), stamps the
// resource (classID 0x0BD6 @ +0x20, model size 0x104 @ +0x24), resolves the
// "SegmentPageName" (when passes==1 it seeds FlickerRate := -1.0 and copies
// the segment index from +0x28 to +0xF8; on later passes it restores +0x28
// from +0xF8, erroring "missing SegmentPageName!" if unset), then reads the
// three HUD scalars, erroring if any is absent/zero:
//      "FlickerRate"                 -> +0xF4
//      "HorizontalMovementPerSecond" -> +0xFC
//      "HorizontalLimit"             -> +0x100
//
int
	HUD::CreateStreamedSubsystem(
		NotationFile				*model_file,
		const char					*model_name,
		const char					*subsystem_name,
		SubsystemResource			*subsystem_resource,
		NotationFile				*subsystem_file,
		const ResourceDirectories	*directories,
		int							passes
	)
{
	if (
		!PowerWatcher::CreateStreamedSubsystem(		// FUN_004b198c
			model_file, model_name, subsystem_name,
			subsystem_resource, subsystem_file, directories, passes
		)
	)
	{
		return False;
	}

	subsystem_resource->subsystemModelSize = 0x104;					// +0x24
	subsystem_resource->classID            = (RegisteredClass::ClassID)0x0BD6;	// HUDClassID, +0x20

	if (passes == 1)
	{
		subsystem_resource->flickerRate      = -1.0f;	// +0xF4
		subsystem_resource->segmentPageIndex = subsystem_resource->segmentIndex;	// +0xF8 <- +0x28
	}
	if ((subsystem_resource->segmentPageIndex == -1) && (1 < passes))
	{
		ReportError(subsystem_name, "missing SegmentPageName!");
		return False;
	}
	if (1 < passes)
	{
		subsystem_resource->segmentIndex = subsystem_resource->segmentPageIndex;	// +0x28 <- +0xF8
	}

	if (!model_file->GetEntry(subsystem_name, "FlickerRate", &subsystem_resource->flickerRate)
		&& subsystem_resource->flickerRate == 0.0f)
	{
		ReportError(subsystem_name, "missing FlickerRate!");
		return False;
	}
	if (!model_file->GetEntry(subsystem_name, "HorizontalMovementPerSecond",
			&subsystem_resource->horizontalMovementPerSecond)
		&& subsystem_resource->horizontalMovementPerSecond == 0.0f)
	{
		ReportError(subsystem_name, "missing HorizontalMovementPerSecond!");
		return False;
	}
	if (!model_file->GetEntry(subsystem_name, "HorizontalLimit",
			&subsystem_resource->horizontalLimit)
		&& subsystem_resource->horizontalLimit == 0.0f)
	{
		ReportError(subsystem_name, "missing HorizontalLimit!");
		return False;
	}

	Check_Fpu();
	return True;
}

//===========================================================================//
// WAVE 2 factory bridge -- HUD (factory case 0xBD6, "MechTech" label).
//===========================================================================//
Subsystem *CreateHUDSubsystem(Mech *owner, int id, void *seg)
{
	Check(sizeof(HUD) <= 0x2a4);
	return (Subsystem *) new (Memory::Allocate(0x2a4))
		HUD(owner, id, (HUD::SubsystemResource *)seg, HUD::DefaultData);
}
