//===========================================================================//
// File:	searchlight.cpp                                                     //
// Project: BattleTech                                                       //
// Contents: Searchlight (illumination) 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 @004b84dc..@004b8648.  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_0041bd34  Subsystem::ReadUpdate  (base, slot 6)
//      FUN_0041c500  Subsystem::WriteUpdate (base, slot 7)
//      FUN_004022d0  operator delete / global free
//
#include <bt.hpp>
#pragma hdrstop

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

//###########################################################################
//					Shared Data Support
//
Derivation
	Searchlight::ClassDerivations(		// @005111a8
		PowerWatcher::GetClassDerivations(),
		"Searchlight"
	);

Receiver::MessageHandlerSet
	Searchlight::MessageHandlers;		// includes { "ToggleLamp", &Searchlight::ToggleLamp } @00511210

// gauge data-binding wave: publish "LightOn" -> lightState@0x1D8 (the button-5
// searchlight lamp; the empty default-constructed index resolved NOTHING).
// Chained to PowerWatcher's dense index.
const Searchlight::IndexEntry
	Searchlight::AttributePointers[]=				// @005111e0
{
	ATTRIBUTE_ENTRY(Searchlight, LightOn, lightState)	// @0x1D8 (reported on/off)
};

Searchlight::AttributeIndexSet
	Searchlight::AttributeIndex(
		ELEMENTS(Searchlight::AttributePointers),
		Searchlight::AttributePointers,
		PowerWatcher::GetAttributeIndex()
	);

Searchlight::SharedData
	Searchlight::DefaultData(			// @00511198
		&Searchlight::ClassDerivations,
		Searchlight::MessageHandlers,
		Searchlight::AttributeIndex,
		Searchlight::StateCount
	);

//###########################################################################
//					Construction -- @004b84dc
//
// Chains to the PowerWatcher ctor (FUN_004b18a4) with &DAT_00511198, installs
// the Searchlight vtable (PTR_FUN_005114d4), builds the 2-level state
// AlarmIndicator (@0x1E4) and seeds commandedOn from the segment field
// (+0x28).  When this is a live (non-copy) segment with the has-performance
// flag set it registers SearchlightSimulation (this[7..9] <- PTR_FUN_00511200);
// otherwise it marks the instance "no per-frame performance" (flag bit 2).
//
Searchlight::Searchlight(
	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 7 cross-family shim fields are gone; the accessors
	// read the inherited base state directly.  Only the own fields init here.
	lightState  = 0;					// @0x1D8
	requestedOn = 0;					// @0x1E0
	stateAlarm.Initialize(2);			// FUN_0041b9ec(this+0x1E4, 2)
	commandedOn = subsystem_resource->segmentIndex;		// @0x1DC <- inherited resource +0x28

	// GATE FIX (the functional bug the de-shim unblocks): the binary reads OWNER
	// simulationFlags (param_2+0x28, raw part_013.c:6012), NOT the old shadow
	// `segmentFlags` -- which was seeded 0, so the gate was ALWAYS FALSE and
	// SearchlightSimulation NEVER installed.  A live master segment arms it; a
	// copy/replicant gets DontExecute (flag 2).  Mirrors torso.cpp:181-182.
	if (((owner->simulationFlags & 0xC) == 0) &&
		((owner->simulationFlags & 0x100) != 0))
	{
		SetPerformance(&Searchlight::SearchlightSimulation);	// this[7..9] <- PTR_FUN_00511200
	}
	else
	{
		SetInstanceFlag(2);				// this[10] |= 2 (no active performance)
	}

	Check_Fpu();
}

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

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

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

//###########################################################################
//					ToggleLamp -- @004b860c   ("ToggleLamp" message handler)
//
// Bound by the shared-data message table @00511210.  On a positive command
// (message arg @0xC > 0) and when the host mech's controls allow it
// (mech +0xD0 -> +0x190 -> +0x25C != 0), flips commandedOn (@0x1DC).
//
Logical
	Searchlight::ToggleLamp(Message &message)
{
	if ((0 < MessageArg(message)) &&			// *(message + 0xC)
		ControlsAllowLights())					// *(*(owner +0xD0)+0x190)+0x25C
	{
		commandedOn = (commandedOn == 0);		// toggle @0x1DC
	}
	return True;
}

//###########################################################################
//					SearchlightSimulation -- @004b841c (Performance)
//
// Gates the reported light state by power and heat:
//   * base tick (FUN_004b181c).
//   * if the host is shut down (this[0x40] == 1) the light is forced off,
//     else it follows requestedOn (@0x1E0).
//   * the watched-voltage alarm level (@0x198) must read Ready(4) for the
//     light to stay lit; otherwise it blanks.
//   * the heat-state level (@0x140): <2 keeps the light, ==2 blanks it.
//   * pushes the resulting on/off into the state AlarmIndicator (@0x1E4) and,
//     if it changed, raises the subsystem "graphics dirty" bit (this[0x18] |= 1).
//
void
	Searchlight::SearchlightSimulation(Scalar time_slice)
{
	Check(this);

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

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

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

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

	stateAlarm.SetLevel(lightState);	// FUN_0041bbd8(this+0x1E4, lightState)

	if (previous != lightState)
		SetGraphicsDirty();				// this[0x18] |= 1

	Check_Fpu();
}

//###########################################################################
//					ReadUpdate / WriteUpdate -- network state replication
//
// slot 6 @004b83b8: chain base ReadUpdate (FUN_0041bd34), then take the
//   replicated on/off (packet +0x10) as lightState (@0x1D8) and drive the
//   alarm (@0x1E4).
// slot 7 @004b83f0: chain base WriteUpdate (FUN_0041c500), then stamp the
//   record kind 0x14 and write lightState into record field [4].
//
// The decomp's NetworkPacket.value / NetworkRecord{kind,value} do not exist on
// the engine UpdateRecord (SIMULATE.h).  The replicated on/off maps to
// UpdateRecord::simulationState and the 0x14 record tag to ::recordID.
//
void
	Searchlight::ReadUpdateRecord(UpdateRecord *message)
{
	PowerWatcher::ReadUpdateRecord(message);	// inherited Simulation::ReadUpdateRecord
	lightState = (int)message->simulationState;	// @0x1D8 <- replicated on/off
	stateAlarm.SetLevel(lightState);			// FUN_0041bbd8(this+0x1E4, lightState)
}

void
	Searchlight::WriteUpdateRecord(UpdateRecord *message, int update_model)
{
	PowerWatcher::WriteUpdateRecord(message, update_model);	// Subsystem::WriteUpdateRecord
	message->recordID        = 0x14;			// record kind tag
	message->simulationState = lightState;		// replicated on/off <- @0x1D8
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem -- Searchlight  @004b85a8
//
// Chains to PowerWatcher::CreateStreamedSubsystem (FUN_004b198c) then stamps
// classID 0x0BD8 (+0x20) and model size 0xF4 (+0x24).  No searchlight-specific
// fields are read (empty resource extension).
//
int
	Searchlight::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::SearchlightClassID;	// 0x0BD8, +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 Searchlight at a raw offset past its
//   own fields, and the shared BT_LOCAL_ALARM_SHIM stays 4 bytes because
//   thermalsight.hpp shares that guard.)
//===========================================================================//
struct SearchlightLayoutCheck
{
	static_assert(offsetof(Searchlight, lightState)  == 0x1D8, "Searchlight lightState @0x1D8 (PowerWatcher ends 0x1D8)");
	static_assert(offsetof(Searchlight, commandedOn) == 0x1DC, "Searchlight commandedOn @0x1DC (res+0x28)");
	static_assert(offsetof(Searchlight, requestedOn) == 0x1E0, "Searchlight requestedOn @0x1E0");
	static_assert(offsetof(Searchlight, stateAlarm)  == 0x1E4, "Searchlight stateAlarm @0x1E4 (proves base ends 0x1D8)");
	static_assert(sizeof(Searchlight) <= 0x238, "sizeof(Searchlight) must fit the factory Memory::Allocate(0x238)");
};


//===========================================================================//
// WAVE 4 factory bridge -- Searchlight (factory case 0xBD8, "LegSubsystem" label).
//   The real class at 0xBD8 (ctor @004b84dc) is Searchlight; the factory built a
//   LegSubsystem RECON_SUBSYS stub.  No cache write; not a weapon (raw
//   part_012.c:10166-10175 stores roster-only).
//===========================================================================//
Subsystem *CreateSearchlightSubsystem(Mech *owner, int id, void *seg)
{
	return (Subsystem *) new (Memory::Allocate(0x238))
		Searchlight(owner, id, (Searchlight::SubsystemResource *)seg, Searchlight::DefaultData);
}
