//===========================================================================//
// File:	thermalsight.cpp                                                    //
// Project: BattleTech                                                       //
// Contents: ThermalSight (thermal / infra-red vision mode) subsystem        //
//---------------------------------------------------------------------------//
// 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 @004b8718..@004b881c.  Each method cites its @ADDR.
//
// Helper-function name mapping:
//      FUN_004b18a4  PowerWatcher base constructor             (powersub.cpp)
//      FUN_004b198c  PowerWatcher::CreateStreamedSubsystem      (powersub.cpp)
//      FUN_004b181c  PowerWatcher base per-frame update         (powersub.cpp)
//      FUN_004b179c  PowerWatcher::HandleMessage (slot 9)       (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_0045fe44  ToggleGlobalThermalVision (flips _DAT_004f1ab8 "pvision",
//                    prints "pvision ON/OFF", re-applies the view palette)
//      FUN_004022d0  operator delete / global free
//      PTR_DAT_004fd4b8  the active L4 application/viewport (its +0x48 flag is
//                    non-zero only when THIS mech is the locally-viewed mech)
//
#include <bt.hpp>
#pragma hdrstop

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

//###########################################################################
//					Shared Data Support
//
Derivation
	ThermalSight::ClassDerivations(		// @00511238
		PowerWatcher::GetClassDerivations(),
		"ThermalSight"
	);

Receiver::MessageHandlerSet
	ThermalSight::MessageHandlers;

ThermalSight::AttributeIndexSet
	ThermalSight::AttributeIndex;

ThermalSight::SharedData
	ThermalSight::DefaultData(			// @00511228
		&ThermalSight::ClassDerivations,
		ThermalSight::MessageHandlers,
		ThermalSight::AttributeIndex,
		ThermalSight::StateCount
	);

//###########################################################################
//					Construction -- @004b8718
//
// Chains to the PowerWatcher ctor (FUN_004b18a4) with &DAT_00511228, installs
// the ThermalSight vtable (PTR_FUN_00511498), builds the 2-level state
// AlarmIndicator (@0x1E0).  When this is a live (non-copy) segment with the
// has-performance flag set it registers ThermalSightSimulation
// (this[7..9] <- PTR_FUN_00511290).
//
ThermalSight::ThermalSight(
	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);

	// (WAVE 4 de-shim) the 4 cross-family shim fields are gone; the accessors
	// read the inherited base state directly.  Only the own fields init here.
	thermalActive = 0;					// @0x1D8
	requestedOn   = 0;					// @0x1DC
	stateAlarm.Initialize(2);			// FUN_0041b9ec(this+0x1E0, 2)

	// GATE FIX (the functional bug the de-shim unblocks): the binary reads OWNER
	// simulationFlags (param_2+0x28, raw part_013.c:6140), NOT the old shadow
	// `segmentFlags` (seeded 0 -> gate always FALSE -> Performance never
	// installed).  No else branch (matches the binary; unlike Searchlight/Torso).
	if (((owner->simulationFlags & 0xC) == 0) &&
		((owner->simulationFlags & 0x100) != 0))
	{
		SetPerformance(&ThermalSight::ThermalSightSimulation);	// this[7..9] <- PTR_FUN_00511290
	}

	Check_Fpu();
}

//
// Destruction -- @004b8794 (vtable slot0).  Reinstalls the vtable, tears down
// the AlarmIndicator (@0x1E0), chains to ~PowerWatcher (FUN_004b1930), frees
// the block when the deleting-dtor bit is set.
//
ThermalSight::~ThermalSight()
{
	Check(this);
	stateAlarm.Finalize();				// FUN_0041baa4(this+0x1E0, 2)
	Check_Fpu();
}

//###########################################################################
//					TestInstance -- @004b881c
//
Logical
	ThermalSight::TestInstance() const
{
	return IsDerivedFrom(ClassDerivations);	// FUN_0041a1a4(**this[3], 0x00511238)
}

Logical
	ThermalSight::TestClass(Mech &)
{
	return True;	// BEST-EFFORT (family convention)
}

//###########################################################################
//					ThermalSightSimulation -- @004b8648 (Performance)
//
// Same power/heat gating as the Searchlight, but the watched quantity is the
// thermal-vision mode, and when the active state CHANGES on the locally-viewed
// mech it toggles the global "pvision" render flag:
//   * base tick (FUN_004b181c).
//   * thermalActive follows requestedOn (@0x1DC) unless the host is shut down
//     (this[0x40] == 1), in which case it is forced off.
//   * the watched-voltage alarm level (@0x198) must read Ready(4); else blank.
//   * heat-state level (@0x140): <2 keeps it, ==2 blanks it.
//   * push on/off into the AlarmIndicator (@0x1E0).
//   * if the state changed AND this mech is the one being viewed
//     (PTR_DAT_004fd4b8 -> +0x48 != 0), call ToggleGlobalThermalVision
//     (FUN_0045fe44) to flip the cockpit IR view.
//
void
	ThermalSight::ThermalSightSimulation(Scalar time_slice)
{
	Check(this);

	PowerWatcher::Simulation(time_slice);		// FUN_004b181c (base per-frame update)
	int previous = thermalActive;					// @0x1D8

	thermalActive = (HostShutDown()) ? 0 : requestedOn;	// this[0x40]==1 ? 0 : @0x1DC

	if (WatchedVoltageLevel() == 4)		// @0x198 == Ready
		thermalActive = (thermalActive != 0);
	else
		thermalActive = 0;

	if (HeatStateLevel() < 2)			// @0x140
		thermalActive = (thermalActive != 0);
	else if (HeatStateLevel() == 2)
		thermalActive = 0;

	stateAlarm.SetLevel(thermalActive);	// FUN_0041bbd8(this+0x1E0, thermalActive)

	if ((previous != thermalActive) && IsLocallyViewed())	// was LocalViewport()->viewingThisMech
	{
		ToggleGlobalThermalVision();	// FUN_0045fe44 -- bring-up no-op (pvision unported)
	}

	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem -- ThermalSight  @004b87d4
//
// Chains to PowerWatcher::CreateStreamedSubsystem (FUN_004b198c) then stamps
// classID 0x0BDE (+0x20) and model size 0xF4 (+0x24).  No thermal-specific
// fields are read.
//
int
	ThermalSight::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 = 0xF4;						// +0x24
	subsystem_resource->classID            = RegisteredClass::ThermalSightClassID;	// 0x0BDE, +0x20

	return True;
}


//===========================================================================//
// WAVE 4 -- compile-time layout locks.  PowerWatcher is Torso-proven at 0x1D8,
//   so the own fields land at their EXACT binary offsets; sizeof is bounded to
//   the factory alloc.  (BtAlarm is a 4-byte stand-in for the binary's 0x54
//   GaugeAlarm -- immaterial: nothing reads ThermalSight at a raw offset past
//   its own fields, and the shared BT_LOCAL_ALARM_SHIM stays 4 bytes because
//   searchlight.hpp shares that guard.)
//===========================================================================//
struct ThermalSightLayoutCheck
{
	static_assert(offsetof(ThermalSight, thermalActive) == 0x1D8, "ThermalSight thermalActive @0x1D8 (PowerWatcher ends 0x1D8)");
	static_assert(offsetof(ThermalSight, requestedOn)   == 0x1DC, "ThermalSight requestedOn @0x1DC");
	static_assert(offsetof(ThermalSight, stateAlarm)    == 0x1E0, "ThermalSight stateAlarm @0x1E0 (proves base ends 0x1D8)");
	static_assert(sizeof(ThermalSight) <= 0x234, "sizeof(ThermalSight) must fit the factory Memory::Allocate(0x234)");
};


//===========================================================================//
// WAVE 4 factory bridge -- ThermalSight (factory case 0xBDE, "MechDisplay" stub).
//   The real class at 0xBDE (ctor @004b8718) is ThermalSight; the factory built
//   a MechDisplay RECON_SUBSYS stub.  No cache write; not a weapon (raw
//   part_012.c:10186-10195 stores roster-only).
//===========================================================================//
Subsystem *CreateThermalSightSubsystem(Mech *owner, int id, void *seg)
{
	return (Subsystem *) new (Memory::Allocate(0x234))
		ThermalSight(owner, id, (ThermalSight::SubsystemResource *)seg, ThermalSight::DefaultData);
}
