//===========================================================================//
// File:	mechtech.cpp                                                        //
// Project: BattleTech           Brick: Entity Manager                        //
// Contents: MechTech -- the mech's subsystem-monitor / diagnostics subsystem //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
//                      PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary.  Behaviour follows the Ghidra
// pseudo-C for the @004ad124-@004ad6c9 cluster; the public interface and the
// member / type names are GROUND TRUTH from the surviving MECHTECH.HPP.
// Each non-trivial method cites its originating @ADDR.
//
// What MechTech does:
//   At construction it walks the owning Mech's segment roster and, for every
//   damageable MechSubsystem (those flagged with segment-bit 0x8), allocates a
//   SubsystemMonitor and threads it onto the `subsystemMonitors` chain.  Each
//   frame the registered Performance (TechnicalAssistance @004ad33c) iterates
//   that chain, samples each watched subsystem's status flags, and -- whenever
//   a status bit changes -- posts an "alert" / "clear" message into the
//   cockpit status / MFD message sink.  Set alerts carry the `alarmModel`
//   resource; the per-cell wooHoo timer latches an alert for a duration so the
//   readout doesn't flicker.
//
// Binary identity (mech.cpp's switch mislabelled these -- corrected here):
//      MechTech  ==  ClassID 0xBDC,  ctor @004ad228,  size 0x104,
//                    vtable @0050e3a0.
//      (mech.cpp's "MechTech" @004b7f94 / 0xBD6 is actually the HUD
//       torso-horizon gimbal display and is NOT this class.)
//
// Helper-function name mapping (engine internals referenced by the decomp):
//      FUN_0041c52c  Subsystem base constructor
//      FUN_0041c684  Subsystem::CreateStreamedSubsystem
//      FUN_0041c648  Subsystem destructor
//      FUN_0041a1a4  IsDerivedFrom(classDerivations)
//      FUN_004022d0  operator delete (global heap)
//      FUN_00415e90  Plug constructor          FUN_00415ed8  Plug destructor
//      FUN_004de998  array-construct N elements (count, stride, ..., elem-init)
//      FUN_004ad619  trivial element-init thunk (returns its arg)
//      FUN_00402f74  MemoryBlock::New(&block)  FUN_00402f98  MemoryBlock::Delete(&block,p)
//      FUN_00417be0 / 00417c0c / 00417d00 / 00417d28 / 00417d54
//                    ChainOf<> / chain-iterator internals (ctor/dtor/iter)
//      FUN_004ad621  ChainOf<SubsystemMonitor*> ctor (vtable @0050e398)
//      FUN_004ad640  ChainOf<>  dtor
//      FUN_004ad68b  chain iterator ctor (vtable @0050e348)
//      FUN_004ad6c9  chain iterator dtor
//      FUN_00402f74(0x50e248) ... SubsystemMonitor::operator new
//      FUN_00404088  NotationFile::Read(section,key,&string)
//      FUN_00406ff8  ResourceFile::ResolveModel(name,...) -> ResourceID*
//      FUN_004dbb24  DebugStream << (error reporting)
//      FUN_004366b8  build "status cleared" cockpit message
//      FUN_00436688  build "status set" cockpit message (carries alarmModel)
//      FUN_004364e4  dispatch cockpit/MFD message to the status sink
//      DAT_004efc94  the running Application/World;  (+0x38) == status sink
//

#include <bt.hpp>
#pragma hdrstop

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


//###########################################################################
//###########################################################################
//					MechTech::SubsystemMonitor
//###########################################################################
//###########################################################################

//#############################################################################
// Memory Allocation Support
//
// Static MemoryBlock @0050e248 (zero-filled in .data, primed at runtime).
//
MemoryBlock
	MechTech::SubsystemMonitor::AllocatedMemory(
		sizeof(MechTech__SubsystemMonitor),		// record size (0x80)
		8,										// initial record count
		8,										// growth delta
		"MechTech::SubsystemMonitor"
	);


//#############################################################################
// Construction / Destruction
//
// @004ad124 -- construct a monitor bound to one watched MechSubsystem.
//
//   FUN_00415e90(this,1)                       -> Plug base ctor
//   *this = &PTR_FUN_0050e3d0                   -> SubsystemMonitor vtable
//   FUN_004de998(this+0xC, 0x10, 7, 1, ...)     -> placement-build statusArray[7]
//   this[0x7C] = subsystem                      -> monitoredSubsystem
//   loop x7: zero {currentStatus,wooHooed,startTime,wooHooEnd}
//
MechTech__SubsystemMonitor::MechTech__SubsystemMonitor(MechSubsystem *subsystem)
	:
	Plug()
{
	monitoredSubsystem = subsystem;

	for (int i = 0; i < MechSubsystem::TechStatusTypeCount; ++i)	// 7
	{
		statusArray[i].currentStatus = 0;
		statusArray[i].wooHooed      = 0;
		statusArray[i].startTime     = 0L;	// Time::operator=(long)
		statusArray[i].wooHooEnd     = 0L;	// Time::operator=(long)
	}
}

//
// @004ad180 -- destructor + deleting-destructor thunk.  Re-seats the vtable,
// runs the Plug base teardown (FUN_00415ed8) and, when invoked as the
// deleting destructor (flag bit 0), returns the storage to the MemoryBlock.
//
MechTech__SubsystemMonitor::~MechTech__SubsystemMonitor()
{
	// Plug base + AllocatedMemory.Delete(this) handled by the generated
	// deleting-destructor thunk (FUN_00402f98(&AllocatedMemory, this)).
}


//###########################################################################
//###########################################################################
//					MechTech
//###########################################################################
//###########################################################################

//#############################################################################
// Shared Data Support
//
// DefaultData  @0050e270        ClassDerivations @0050e280
//
Derivation
	MechTech::ClassDerivations(
		Subsystem::GetClassDerivations(),	// MUNGA Subsystem exposes a Derivation* getter
		"MechTech"
	);

Receiver::MessageHandlerSet
	MechTech::MessageHandlers;

MechTech::AttributeIndexSet
	MechTech::AttributeIndex;

MechTech::SharedData
	MechTech::DefaultData(
		&MechTech::ClassDerivations,
		MechTech::MessageHandlers,
		MechTech::AttributeIndex,
		MechTech::StateCount
	);


//#############################################################################
// Construction / Destruction
//
// @004ad228  (mech.cpp allocates 0x104 bytes and calls this for ClassID 0xBDC).
//
//   FUN_0041c52c(...)                 -> Subsystem base ctor
//   *this = &PTR_FUN_0050e3a0         -> MechTech vtable
//   FUN_004ad621(this+0xE4, 0)        -> subsystemMonitors chain ctor
//
// A live (non-copy / non-destroyed) instance -- (entity flags & 0xC) == 0 --
// installs the per-frame Performance and builds its monitor roster; a copy
// just flags itself dormant (this[10] |= 2).
//
MechTech::MechTech(
	Mech				*entity,
	int					subsystem_id,
	SubsystemResource	*model,
	SharedData			&shared_data
):
	Subsystem(entity, subsystem_id, model, shared_data),
	subsystemMonitors(0)		// FUN_004ad621(this+0xE4, 0) -- Chain(Node* == 0)
{
	Check(entity);
	Check_Pointer(model);

	segmentFlags = 0;

	// (entity+0x28 & 0xC) == 0  -- owner is a live primary, not a copy/template.
	// CROSS-FAMILY: the original reads the owning Mech's per-segment flags
	// (Mech::GetSegmentFlags); mapped here to the entity's simulationFlags.
	if ((entity->simulationFlags & SegmentCopyMask) == 0)
	{
		SetPerformance(&MechTech::TechnicalAssistance);			// this[7..9] = {&TechnicalAssistance,0,0} @0050e2b0

		alarmModel            = model->alarmModel;				// res +0x30 -> this+0x100
		wooHooMinimumDuration = model->wooHooMinimumDuration;	// res +0x34 -> this+0xF4
		wooHooDurationRange   = model->wooHooDurationRange;		// res +0x38 -> this+0xF8
		wooHooChance          = model->wooHooChance;			// res +0x3C -> this+0xFC

		//
		// Walk the owner's subsystem roster and attach a monitor to every
		// damageable MechSubsystem.  (Binary walked a Mech-specific segment
		// table at owner+0x124/+0x128 whose entries are these subsystem
		// objects; the engine exposes the same roster via Entity::GetSubsystem.)
		//
		for (int seg = entity->GetSubsystemCount() - 1; seg >= 0; --seg)
		{
			Subsystem *sub = entity->GetSubsystem(seg);
			if (
				sub != 0
				&& sub->IsDerivedFrom(MechSubsystem::ClassDerivations)	// FUN_0041a1a4(**sub[3], 0x50de2c)
				&& sub->damageZone != 0									// damageable segment (segment bit 0x8)
			)
			{
				SubsystemMonitor *monitor =
					new SubsystemMonitor((MechSubsystem *)sub);		// MemoryBlock @0050e248
				subsystemMonitors.Add(monitor);						// (*this[0x39].vt[1])(...)
			}
		}
	}
	else
	{
		segmentFlags |= DormantInstanceFlag;	// this[10] |= 2  -- dormant (copy / template instance)
	}

	Check_Fpu();
}

//
// @004ad1b8 -- destructor + deleting-destructor thunk.
//
//   *this = &PTR_FUN_0050e3a0
//   FUN_004ad68b/FUN_004ad6c9 ... walk + tear down the monitor chain,
//   FUN_004ad640(this+0xE4, 2)   -> subsystemMonitors dtor,
//   FUN_0041c648(this, 0)        -> ~Subsystem.
//
MechTech::~MechTech()
{
	Check(this);

	//
	// Release every SubsystemMonitor still on the chain, then the chain
	// itself; ~Subsystem finishes the base teardown.
	//
	ChainIteratorOf<SubsystemMonitor*> iterator(&subsystemMonitors);
	for (
		iterator.First();
		SubsystemMonitor *monitor = iterator.GetCurrent();
		/* Remove() re-seats currentLink */
	)
	{
		iterator.Remove();
		delete monitor;
	}

	Check_Fpu();
}

//###########################################################################
//					TestInstance -- MechTech
//
// @004ad470 -> FUN_0041a1a4(**this[3], 0x50e280)
//
Logical
	MechTech::TestInstance() const
{
	return IsDerivedFrom(ClassDerivations);
}


//#############################################################################
// Per-frame Performance
//

//
// @004ad33c -- TechnicalAssistance: poll every monitored subsystem's status
// flags and emit cockpit/MFD status messages on change.
//
// Decomp shape:
//   sink = *(DAT_004efc94 + 0x38);              // the status / MFD message sink
//   for (monitor in subsystemMonitors)          // iterator FUN_004ad68b
//   {
//       sub   = monitor->monitoredSubsystem;    // monitor[0x7C]
//       flags = sub->GetStatusFlags();          // vtable slot @+0x30
//       for (type = 0; type < 7; ++type)        // TechStatusTypeCount
//       {
//           bit  = flags & 1;
//           cell = &monitor->statusArray[type];
//           if (cell->wooHooed)                  // an alert is latched
//           {
//               if (cell->wooHooEnd < GetTime()) cell->wooHooed = 0;   // expired
//               else                             bit = cell->currentStatus; // hold
//           }
//           if (bit != cell->currentStatus)      // status changed -> report
//           {
//               Mech *owner = GetEntity();
//               if (bit == 0)  // cleared
//                   FUN_004364e4(sink, owner, BuildClear(owner, sub, type));
//               else           // set (carries alarmModel)
//                   FUN_004364e4(sink, owner, BuildSet(owner, sub, type, alarmModel));
//           }
//           cell->currentStatus = bit;
//           flags >>= 1;
//       }
//   }
//
// NOTE (best-effort): the actual latch -- setting cell->wooHooed /
// cell->wooHooEnd from wooHooChance / wooHooMinimumDuration /
// wooHooDurationRange -- lives inside the message-builder helpers
// (FUN_00436688 et al.), which are part of the HUD/message brick and were
// not captured in this window.  TechnicalAssistance only *clears* expired
// latches and detects edges.
//
void
	MechTech::TechnicalAssistance(Scalar /*time_slice*/)
{
	Check(this);

	Mech *owner = GetEntity();
	void *sink  = StatusMessageSink();		// *(DAT_004efc94 + 0x38)

	ChainIteratorOf<SubsystemMonitor*> it(&subsystemMonitors);		// FUN_004ad68b
	SubsystemMonitor *monitor;
	for (
		it.First();
		(monitor = it.GetCurrent()) != 0;
		it.Next()
	)
	{
		MechSubsystem *sub = monitor->monitoredSubsystem;
		LWord flags = sub->GetStatusFlags();						// vt[+0x30]

		for (int type = 0; type < MechSubsystem::TechStatusTypeCount; ++type)
		{
			int bit = (int)(flags & 1);
			StatusInfo &cell = monitor->statusArray[type];

			if (cell.wooHooed != 0)
			{
				if (cell.wooHooEnd.ticks < GetTime().ticks)	// this+0x10 == current time (integer tick compare)
				{
					cell.wooHooed = 0;					// latch expired
				}
				else
				{
					bit = cell.currentStatus;			// hold the latched state
				}
			}

			if (bit != cell.currentStatus)
			{
				if (bit == 0)
				{
					ReportStatusCleared(sink, owner, sub, type);		// FUN_004366b8 + FUN_004364e4
				}
				else
				{
					ReportStatusSet(sink, owner, sub, type, alarmModel);// FUN_00436688 + FUN_004364e4
				}
			}

			cell.currentStatus = bit;
			flags >>= 1;
		}
	}

	Check_Fpu();
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem -- MechTech
//
// @004ad48c
//
//   FUN_0041c684(...)                 -> Subsystem::CreateStreamedSubsystem
//   resource->subsystemModelSize = 0x40       (res[0x24] = 0x40)
//   resource->classID            = 0xBDC      (res[0x20] = 0x0BDC)
//   resource->alarmModel            = -1      (res+0x30)
//   resource->wooHoo{Min,Range,Chance} = 0    (res+0x34,+0x38,+0x3C)
//
//   "AlarmModel" is optional: if absent (and alarmModel still -1) the parse
//   succeeds with no alarm.  If present, the named model is resolved against
//   the resource file; failure reports "<name> couldn't locate <name>.mod".
//
Logical
	MechTech::CreateStreamedSubsystem(
		NotationFile				*model_file,
		const char					*model_name,
		const char					*subsystem_name,
		SubsystemResource			*subsystem_resource,
		NotationFile				*subsystem_file,
		const ResourceDirectories	*directories,
		ResourceFile				*resource_file
	)
{
	if (
		!Subsystem::CreateStreamedSubsystem(			// FUN_0041c684
			model_file, model_name, subsystem_name,
			subsystem_resource, subsystem_file, directories
		)
	)
	{
		return False;
	}

	subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);	// 0x40
	subsystem_resource->classID            = RegisteredClass::MechTechClassID;	// 0xBDC

	subsystem_resource->alarmModel            = -1;
	subsystem_resource->wooHooMinimumDuration =  0.0f;
	subsystem_resource->wooHooDurationRange   =  0.0f;
	subsystem_resource->wooHooChance          =  0.0f;

	//
	// "AlarmModel" -- optional model name resolved to a resource id.
	//
	const char *alarmModelName = 0;
	if (
		subsystem_resource->alarmModel == -1
		&& !subsystem_file->GetEntry(subsystem_name, "AlarmModel", &alarmModelName)	// FUN_00404088(param_5,param_3,"AlarmModel",&local_8)
	)
	{
		return True;		// no alarm model specified -- fine
	}

	//
	// Resolve the named model to a resource id.  The binary calls
	// FUN_00406ff8(resource_file, name, 1, -1) ("ResolveModel"); the engine
	// ResourceFile exposes FindResourceDescription instead.
	// TODO(verify): confirm the resource type used for the alarm .mod.
	//
	ResourceDescription *description =
		resource_file->FindResourceDescription(
			alarmModelName,
			ResourceDescription::GameModelResourceType
		);

	if (description == 0)
	{
		// @004ad48c: "<subsystem_name> couldn't locate <name>.mod"
		// (original logged via DebugStream / FUN_004dbb24 -- HUD/diag brick).
		return False;
	}

	subsystem_resource->alarmModel = description->resourceID;
	Check_Fpu();
	return True;
}


//#############################################################################
// Status reporting support
//
// CROSS-FAMILY stubs.  The shipped code dispatches cockpit/MFD status messages
// through the running Application/World status sink (DAT_004efc94 + 0x38) using
// the HUD message builders FUN_004366b8 ("status cleared") / FUN_00436688
// ("status set", carries alarmModel) and dispatcher FUN_004364e4.  Those live
// in the cockpit-message brick (btl4gaug / app), not reconstructed here, so the
// bodies are stubs that satisfy the call sites in TechnicalAssistance.
//

//
// @0050e248-era global: the status / MFD message sink == *(DAT_004efc94 + 0x38).
//
void*
	MechTech::StatusMessageSink()
{
	return 0;	// TODO(cross-family): *(theApplication + 0x38) -- HUD status sink
}

//
// Engine current time (binary read this+0x10; the WinTesla clock is Now()).
//
Time
	MechTech::GetTime() const
{
	return Now();
}

//
// FUN_004366b8 (build "status cleared" message) + FUN_004364e4 (dispatch).
//
void
	MechTech::ReportStatusCleared(
		void			* /*sink*/,
		Mech			* /*owner*/,
		MechSubsystem	* /*sub*/,
		int				  /*type*/
	)
{
	// TODO(cross-family): build + dispatch the cockpit "status cleared" message.
}

//
// FUN_00436688 (build "status set" message, carries alarmModel) + FUN_004364e4.
//
void
	MechTech::ReportStatusSet(
		void			* /*sink*/,
		Mech			* /*owner*/,
		MechSubsystem	* /*sub*/,
		int				  /*type*/,
		ResourceDescription::ResourceID /*alarm_model*/
	)
{
	// TODO(cross-family): build + dispatch the cockpit "status set" message.
}

//===========================================================================//
// WAVE 2 factory bridge -- MechTech (factory case 0xBDC, "HeatableSubsystem"
// label; the case currently builds the heat base + discards the pointer).
//===========================================================================//
Subsystem *CreateMechTechSubsystem(Mech *owner, int id, void *seg)
{
	Check(sizeof(MechTech) <= 0x140);
	return (Subsystem *) new (Memory::Allocate(0x140))
		MechTech(owner, id, (MechTech::SubsystemResource *)seg, MechTech::DefaultData);
}
