/*
**	Command & Conquer Generals(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/>.
*/

////////////////////////////////////////////////////////////////////////////////
//																																						//
//  (c) 2001-2003 Electronic Arts Inc.																				//
//																																						//
////////////////////////////////////////////////////////////////////////////////

#include "PreRTS.h"
#include "GameClient/SelectionInfo.h"

#include "Common/ActionManager.h"
#include "GameLogic/Damage.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ThingTemplate.h"

#include "GameClient/CommandXlat.h"
#include "GameClient/ControlBar.h"
#include "GameClient/Drawable.h"
#include "GameClient/GameClient.h"
#include "GameClient/KeyDefs.h"

#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif

//-------------------------------------------------------------------------------------------------
SelectionInfo::SelectionInfo() :
	currentCountEnemies(0),
	currentCountCivilians(0),
	currentCountMine(0),
	currentCountMineInfantry(0),
	currentCountMineBuildings(0),
	currentCountFriends(0),
	newCountEnemies(0),
	newCountCivilians(0),
	newCountCrates(0),
	newCountMine(0),
	newCountMineBuildings(0),
	newCountFriends(0),
	newCountGarrisonableBuildings(0),
	selectEnemies(FALSE),
	selectCivilians(FALSE),
	selectMine(FALSE),
	selectMineBuildings(FALSE),
	selectFriends(FALSE)
{ }

//-------------------------------------------------------------------------------------------------
PickDrawableStruct::PickDrawableStruct() : drawableListToFill(NULL)
{
	//Added By Sadullah Nader
	//Initializations inserted
	drawableListToFill = FALSE;
	//
	forceAttackMode = TheInGameUI->isInForceAttackMode();
	UnsignedInt pickType = getPickTypesForContext(forceAttackMode);
	translatePickTypesToKindof(pickType, kindofsToMatch);
	if (!forceAttackMode)
	{
		kindofsToMatch.set(KINDOF_ALWAYS_SELECTABLE);
	}
}

//-------------------------------------------------------------------------------------------------
/**
 * Given a list of currently selected things and a list of things that are currently under 
 * the selection (pointer or drag), generate some useful information about each.
 */
extern Bool contextCommandForNewSelection(const DrawableList *currentlySelectedDrawables, 
																					const DrawableList *newlySelectedDrawables,
																					SelectionInfo *outSelectionInfo,
																					Bool selectionIsPoint)
{
	if (!(currentlySelectedDrawables && newlySelectedDrawables && outSelectionInfo))
		return FALSE;

	Bool forceFire = TheInGameUI->isInForceAttackMode();
	Bool forceMove = TheInGameUI->isInForceMoveToMode();

	if (forceFire || forceMove) {
		return FALSE;
	}


	Player *localPlayer = ThePlayerList->getLocalPlayer();
	DrawableListCIt it;
	for (it = currentlySelectedDrawables->begin(); it != currentlySelectedDrawables->end(); ++it) {
		if (!(*it)) {
			continue;
		}

		Object *obj = (*it)->getObject();
		if (!obj) {
			continue;
		}

		if (obj->isLocallyControlled()) {
			++outSelectionInfo->currentCountMine;
			if (obj->isKindOf(KINDOF_INFANTRY)) {
				++outSelectionInfo->currentCountMineInfantry;
			} else if (obj->isKindOf(KINDOF_STRUCTURE)) {
				++outSelectionInfo->currentCountMineBuildings;
			}
		} else {
			Relationship rel = localPlayer->getRelationship(obj->getTeam());
			if (rel == ALLIES) {
				++outSelectionInfo->currentCountFriends;
			} else if (rel == ENEMIES) {
				++outSelectionInfo->currentCountEnemies;
			} else if (rel == NEUTRAL) {
				++outSelectionInfo->currentCountCivilians;
			}
		}
	}

	Drawable *newMine = NULL;
	Drawable *newFriendly = NULL;
	Drawable *newEnemy = NULL;
	Drawable *newCivilian = NULL;

	for (it = newlySelectedDrawables->begin(); it != newlySelectedDrawables->end(); ++it) {
		if (!(*it)) {
			continue;
		}

		Object *obj = (*it)->getObject();
		if (!obj) {
			continue;
		}

		if (TheActionManager->canPlayerGarrison(localPlayer, obj, CMD_FROM_PLAYER)) {
			++outSelectionInfo->newCountGarrisonableBuildings;
		}
		if (obj->isKindOf(KINDOF_CRATE)) {
			++outSelectionInfo->newCountCrates;
		}

		if (obj->isLocallyControlled()) {
			++outSelectionInfo->newCountMine;	
			newMine = *it;
			if (obj->isKindOf(KINDOF_STRUCTURE)) {
				++outSelectionInfo->newCountMineBuildings;
			}
		} else {
			Relationship rel = localPlayer->getRelationship(obj->getTeam());
			if (rel == ALLIES) {
				newFriendly = *it;
				++outSelectionInfo->newCountFriends;
			} else if (rel == ENEMIES) {
				newEnemy = *it;
				++outSelectionInfo->newCountEnemies;
			} else if (rel == NEUTRAL) {
				newCivilian = *it;
				++outSelectionInfo->newCountCivilians;
			}
		}
	}

	DEBUG_ASSERTCRASH(outSelectionInfo->currentCountEnemies <= 1, ("Selection bug. jkmcd"));
	DEBUG_ASSERTCRASH(outSelectionInfo->currentCountFriends <= 1, ("Selection bug. jkmcd"));
	DEBUG_ASSERTCRASH(outSelectionInfo->currentCountCivilians <= 1, ("Selection bug. jkmcd"));

	if (outSelectionInfo->currentCountEnemies > 0) {
		// If we have an enemy selected, there are no context sensitive commands
		return FALSE;
	}

	if (outSelectionInfo->currentCountFriends > 0) {
		return FALSE;
	}

	if (outSelectionInfo->currentCountCivilians > 0) {
		return FALSE;
	}

	if (TheGlobalData->m_useAlternateMouse) {
		// context sensitive commands never apply when selecting in alternate mouse mode
		return FALSE;
	}

	if (outSelectionInfo->currentCountMine > 0) {
		if (outSelectionInfo->newCountEnemies > 0) {
			if (outSelectionInfo->newCountEnemies == 1 && selectionIsPoint) {
				return TheGameClient->evaluateContextCommand(newEnemy, newEnemy->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
			}

			return selectionIsPoint;
		}

		if (outSelectionInfo->newCountMine > 0) {
			if (outSelectionInfo->newCountMine == 1 && selectionIsPoint && !TheInGameUI->isInPreferSelectionMode()) {
				return TheGameClient->evaluateContextCommand(newMine, newMine->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
			}

			return FALSE;
		}

		if (outSelectionInfo->newCountFriends > 0) {
			if (outSelectionInfo->newCountFriends == 1 && selectionIsPoint) {
				return TheGameClient->evaluateContextCommand(newFriendly, newFriendly->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
			}
			return FALSE;
		}

		if (outSelectionInfo->currentCountMineInfantry > 0 && outSelectionInfo->newCountGarrisonableBuildings == 1) {
			return TRUE;
		}

		if (outSelectionInfo->newCountCivilians > 0) {
			if (outSelectionInfo->newCountCivilians == 1 && selectionIsPoint) {
				return TheGameClient->evaluateContextCommand(newCivilian, newCivilian->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
			}
			return FALSE;
		}

		if (outSelectionInfo->newCountCrates > 0) {
			return (outSelectionInfo->newCountCrates == 1 && selectionIsPoint);
		}
	}

	if (outSelectionInfo->currentCountMine == 0) {
		return FALSE;
	}

	return selectionIsPoint;
}

//-------------------------------------------------------------------------------------------------
UnsignedInt getPickTypesForContext( Bool forceAttackMode )
{
	UnsignedInt types = PICK_TYPE_SELECTABLE;

	if (forceAttackMode)
		types |= PICK_TYPE_FORCEATTACKABLE;

	//
	// if we have a gui context command that allows for a shrubbery target then we want to
	// pick that type too (generally shrubbery aren't pickable cause it would get in
	// the way with movement and general selection)
	//
	const CommandButton *command = TheInGameUI->getGUICommand();

	if (command != NULL) {
		if (BitTest( command->getOptions(), ALLOW_MINE_TARGET)) {
			types |= PICK_TYPE_MINES;
		}

		if (BitTest( command->getOptions(), ALLOW_SHRUBBERY_TARGET ) ) {
			types |= PICK_TYPE_SHRUBBERY;
		}
	} else {
		types |= getPickTypesForCurrentSelection(forceAttackMode);
	}

	return types;

}  // end getPickTypesForContext

//-------------------------------------------------------------------------------------------------
UnsignedInt getPickTypesForCurrentSelection( Bool forceAttackMode )
{
	UnsignedInt retVal = 0;
	if (!TheInGameUI->areSelectedObjectsControllable()) {
		return retVal;
	}

	const DrawableList *allSelectedDrawables = TheInGameUI->getAllSelectedDrawables();

	for (DrawableListCIt cit = allSelectedDrawables->begin(); cit != allSelectedDrawables->end(); ++cit) {
		Drawable *draw = *cit;
		if (!draw) {
			continue;
		}

		Object *obj = draw->getObject();
		if (!obj) {
			continue;
		}

// srj sez: thanks to new, area-effect disarming, we NO LONGER want to do this...
//		if (obj->hasWeaponToDealDamageType(DAMAGE_DISARM)) {
//			retVal |= PICK_TYPE_MINES;
//		}

		if (obj->hasWeaponToDealDamageType(DAMAGE_FLAME) && forceAttackMode ) {
			retVal |= PICK_TYPE_SHRUBBERY;
		}

		// For efficiency.
		if (BitTest(retVal, PICK_TYPE_MINES | PICK_TYPE_SHRUBBERY)) {
			break;
		}
	}

	return retVal;
		
}

//-------------------------------------------------------------------------------------------------
void translatePickTypesToKindof(UnsignedInt pickTypes, KindOfMaskType& outMask)
{
	if (BitTest(pickTypes, PICK_TYPE_SELECTABLE)) {
		outMask.set(KINDOF_SELECTABLE);
	}

	if (BitTest(pickTypes, PICK_TYPE_SHRUBBERY)) {
		outMask.set(KINDOF_SHRUBBERY);
	}

	if (BitTest(pickTypes, PICK_TYPE_MINES)) {
		outMask.set(KINDOF_MINE);
	}

	if (BitTest(pickTypes, PICK_TYPE_FORCEATTACKABLE)) {
		outMask.set(KINDOF_FORCEATTACKABLE);
	}	
}

//-------------------------------------------------------------------------------------------------
// Given a drawable, add it to an stl list specified by userData.
// Useful for iterateDrawablesInRegion.
Bool addDrawableToList( Drawable *draw, void *userData )
{
	PickDrawableStruct *pds = (PickDrawableStruct *) userData;
#if defined(_DEBUG) || defined(_INTERNAL)
	if (TheGlobalData->m_allowUnselectableSelection) {
		pds->drawableListToFill->push_back(draw);
		return TRUE;
	}
#endif

	if (!pds->drawableListToFill)
		return FALSE;

	if (!draw->getTemplate()->isAnyKindOf(pds->kindofsToMatch))
		return FALSE;

	if (!draw->isSelectable())
		return FALSE;

	pds->drawableListToFill->push_back(draw);
	return TRUE;
}