/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***                            Confidential - Westwood Studios                              ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : Commando                                                     *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Commando/clienthintmanager.cpp                    $*
 *                                                                                             *
 *                      $Author:: Steve_t                                                     $*
 *                                                                                             *
 *                     $Modtime:: 12/09/01 6:40p                                              $*
 *                                                                                             *
 *                    $Revision:: 6                                                           $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "clienthintmanager.h"

#include "cshint.h"
#include "cnetwork.h"
#include "combat.h"
#include "gameobjmanager.h"
#include "pscene.h"
#include "apppackettypes.h"
#include "vistable.h"
#include "useroptions.h"
#include "priority.h"
#include "apppacketstats.h"

//
// Class statics
//


//-----------------------------------------------------------------------------
void
cClientHintManager::Think
(
	void
)
{
	if (cNetwork::I_Am_Server() ||
		COMBAT_SCENE == NULL ||
		cUserOptions::ClientHintFactor.Get() < 1)
	{
		//
		// Bail:
		// Only dedicated clients send hints.
		// Also, ClientHintFactor is disabled by using any value < 1.
		//
		return;
	}

	WWASSERT(cNetwork::I_Am_Only_Client());

   SoldierGameObj * p_my_soldier = GameObjManager::Find_Soldier_Of_Client_ID(cNetwork::Get_My_Id());

	if (p_my_soldier == NULL)
	{
		//
		// Bail...
		//
		return;
	}

	//
	// Do not allow hints to go out at too high a frequency.
	//
	static DWORD last_hint_time_ms = 0;
	DWORD	time_now_ms	= TIMEGETTIME();
	const DWORD MIN_HINT_DELAY_MS = 1000;
	if (time_now_ms - last_hint_time_ms < MIN_HINT_DELAY_MS)
	{
		//
		// Bail...
		//
		return;
	}

	//
	// Examine all the vis-visible soldiers and vehicles. If we find one that hasn't been
	// updated for a suspiciously long time, send a hint to the server, He'll follow
	// up with an update.
	//

	Vector3 my_position;
	p_my_soldier->Get_Position(&my_position);
	my_position.Z += 1.5;

	VisTableClass * pvs = COMBAT_SCENE->Get_Vis_Table(my_position);

	int		num_objects					= 0;
	//ULONG		total_delay_ms				= 0;
	//ULONG		maximum_delay_ms			= 0;
	//int		longest_delayed_index	= -1;

	int count = NetworkObjectMgrClass::Get_Object_Count();

	NetworkObjectClass **object_list = (NetworkObjectClass **) _alloca((count * sizeof(NetworkObjectClass*)) + 128);
	WWASSERT(object_list != NULL);
	SmartGameObj * player_ptr = GameObjManager::Find_Soldier_Of_Client_ID(cNetwork::Get_My_Id());
	WWASSERT(player_ptr != NULL);

	//
	// Traverse the vehicles and soldiers and gather data about update rates.
	//
	for (int index = 0; index < count; index ++)
	{
		NetworkObjectClass * p_object = NetworkObjectMgrClass::Get_Object(index);
		WWASSERT(p_object != NULL);

		BYTE type = p_object->Get_App_Packet_Type();

		if (type == APPPACKETTYPE_SOLDIER || type == APPPACKETTYPE_VEHICLE)
		{

			if (p_object->Get_Clientside_Update_Frequency()) {

				int vis_id = p_object->Get_Vis_ID();

				//
				// If there is no vis data, proceed.
				// If vis data does exist then only proceed with this object if it is vis-visible.
				//

				if (pvs == NULL || vis_id == -1 || pvs->Get_Bit(vis_id))
				{
					// Just add the object to the list on this pass.
					object_list[num_objects++] = p_object;

					// Work out the priority of this object from the clients perspective.
					if (player_ptr) {
						Vector3 position;
						player_ptr->Get_Position(&position);
						float priority = cPriority::Compute_Object_Priority(cNetwork::Get_My_Id(), position, p_object, true);
						p_object->Set_Cached_Priority(priority);
					}

#if (0)
					ULONG delay_ms = time_now_ms - p_object->Get_Last_Clientside_Update_Time();

					total_delay_ms += delay_ms;
					num_objects++;

					if (delay_ms > maximum_delay_ms)
					{
						maximum_delay_ms = delay_ms;
						longest_delayed_index = index;
					}
#endif //(0)
				}
			}
		}
	}

	if (num_objects < 2) {
		return;
	}

	//
	// Sort the object list. Lowest priority first.
	//
	qsort(object_list, num_objects, sizeof(unsigned long), (int (__cdecl *)(const void *,const void *)) &Priority_Compare);


	//
	// Look for an object that has a higher priority than it's neighbor but is getting updated much less frequently
	//
	// Ignore the worst priority object. This might have to change....?
	//
	int most_broken_object_index = -1;
	int worst_percentage = 0;
	unsigned long time = TIMEGETTIME();
	for (int i=1 ; i<num_objects ; i++) {
		int higher_priority_rate = object_list[i]->Get_Clientside_Update_Frequency();

		//
		// Don't hint for objects with a recent updates. This should prevent us from hinting about objects we just hinted about.
		//
		if (time - object_list[i]->Get_Last_Clientside_Update_Time() > 1500) {

			int lower_priority_rate = object_list[i-1]->Get_Clientside_Update_Frequency();
			if (lower_priority_rate && higher_priority_rate) {

				// Lower priority rate should be higher in terms of milliseconds.
				if (lower_priority_rate < higher_priority_rate) {

					// The higher priority object is being updated by the server less frequently. That doesn't seem right.
					int percentage = (higher_priority_rate * 100) / lower_priority_rate;
					if (percentage > worst_percentage) {
						worst_percentage = percentage;
						most_broken_object_index = i;
					}
				}
			}
		}
	}


	//
	// Calculate the average delay for this set of soldiers and vehicles
	//
	//float average_delay_ms = 0;
	//if (num_objects > 0)
	//{
	//	average_delay_ms = total_delay_ms / (float) num_objects;
	//}

	//
	// If the most OOD object is much more OOD than the average object, then request an update.
	//
	//if ((longest_delayed_index != -1) &&
	//	 (maximum_delay_ms > cUserOptions::ClientHintFactor.Get() * average_delay_ms))
	//{
	float hint_factor = cUserOptions::ClientHintFactor.Get();
	if (most_broken_object_index != -1 && worst_percentage > 100 + (10.0f * hint_factor)) {
		//NetworkObjectClass * p_object = NetworkObjectMgrClass::Get_Object(longest_delayed_index);
		NetworkObjectClass * p_object = object_list[most_broken_object_index];
		WWASSERT(p_object != NULL);

		cCsHint * p_hint = new cCsHint;
		p_hint->Init(p_object->Get_Network_ID());

		last_hint_time_ms = time_now_ms;

		//WWDEBUG_SAY(("cClientHintManager::Think, requesting hint for object id %d, avg = %5.2f, max = %d\n",
		//	p_object->Get_Network_ID(), average_delay_ms, maximum_delay_ms));
		WWDEBUG_SAY(("cClientHintManager::Think, requesting hint for object id %d (%s), priority = %5.2f, ave update rate = %dms\n",
			p_object->Get_Network_ID(),
			cAppPacketStats::Interpret_Type(p_object->Get_App_Packet_Type()),
			p_object->Get_Cached_Priority(),
			p_object->Get_Clientside_Update_Frequency()));
		WWDEBUG_SAY(("      Compared to object id %d with priority %5.2f, avg update rate %dms\n",
			object_list[most_broken_object_index-1]->Get_Network_ID(),
			object_list[most_broken_object_index-1]->Get_Cached_Priority(),
			object_list[most_broken_object_index-1]->Get_Clientside_Update_Frequency()));
		WWDEBUG_SAY(("      Client hint factor = %5.2f\n", hint_factor));
	}
}










//
// Qsort compare function for array of object pointers.
//
int __cdecl cClientHintManager::Priority_Compare(const void **object1, const void **object2)
{
	WWASSERT(object1 != NULL);
	WWASSERT(object2 != NULL);

	NetworkObjectClass *n1 = (NetworkObjectClass*) *object1;
	NetworkObjectClass *n2 = (NetworkObjectClass*) *object2;

	float p1 = n1->Get_Cached_Priority();
	float p2 = n2->Get_Cached_Priority();

	if (p1 == p2) {
		return(0);
	}

	if (p1 < p2) {
		return(-1);
	}

	return(1);
}