/*
**	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.																				//
//																																						//
////////////////////////////////////////////////////////////////////////////////

// WeaponSet.h

#pragma once

#ifndef _WeaponSet_H_
#define _WeaponSet_H_

#include "Lib/BaseType.h"
#include "Common/GameType.h"
#include "Common/KindOf.h"
#include "Common/ModelState.h"
#include "Common/SparseMatchFinder.h"
#include "Common/Snapshot.h"

//-------------------------------------------------------------------------------------------------
class INI;
class Object;
class Weapon;
class WeaponTemplate;

enum CommandSourceType;
enum DamageType;

// for WeaponSetType. Part of detangling.
#include "GameLogic/WeaponSetType.h"

//-------------------------------------------------------------------------------------------------
// for WeaponSetFlags. Part of detangling.
#include "GameLogic/WeaponSetFlags.h"

#ifdef DEFINE_WEAPONSLOTTYPE_NAMES
static char *TheWeaponSlotTypeNames[] = 
{
	"PRIMARY",
	"SECONDARY",
	"TERTIARY",

	NULL
};

static const LookupListRec TheWeaponSlotTypeNamesLookupList[] = 
{
	{ "PRIMARY",		PRIMARY_WEAPON },
	{ "SECONDARY",	SECONDARY_WEAPON },
	{ "TERTIARY",		TERTIARY_WEAPON },
	
	{ NULL, 0	}// keep this last!
};

#endif  

//-------------------------------------------------------------------------------------------------
#ifdef DEFINE_WEAPONCONDITIONMAP
static const ModelConditionFlagType TheWeaponSetTypeToModelConditionTypeMap[WEAPONSET_COUNT] =
{
	MODELCONDITION_WEAPONSET_VETERAN,
	MODELCONDITION_WEAPONSET_ELITE,
	MODELCONDITION_WEAPONSET_HERO,
	MODELCONDITION_WEAPONSET_PLAYER_UPGRADE,
	MODELCONDITION_WEAPONSET_CRATEUPGRADE_ONE,
	MODELCONDITION_WEAPONSET_CRATEUPGRADE_TWO
};
#endif

//-------------------------------------------------------------------------------------------------
enum WeaponSetConditionType
{
	WSF_INVALID = -1,

	WSF_NONE = 0,
	WSF_FIRING,
	WSF_BETWEEN,
	WSF_RELOADING,
	WSF_PREATTACK,

	WSF_COUNT
};

//-------------------------------------------------------------------------------------------------
class WeaponTemplateSet
{
private:
	const ThingTemplate*		m_thingTemplate;	// needed for save/load
	WeaponSetFlags					m_types;
	const WeaponTemplate*		m_template[WEAPONSLOT_COUNT];
	UnsignedInt							m_autoChooseMask[WEAPONSLOT_COUNT];
	KindOfMaskType					m_preferredAgainst[WEAPONSLOT_COUNT];
	Bool										m_isReloadTimeShared;
	Bool										m_isWeaponLockSharedAcrossSets; ///< A weapon set so similar that it is safe to hold locks across

	static void parseWeapon(INI* ini, void *instance, void *store, const void* userData);
	static void parseAutoChoose(INI* ini, void *instance, void *store, const void* userData);
	static void parsePreferredAgainst(INI* ini, void *instance, void *store, const void* userData);

public:
	inline WeaponTemplateSet()
	{
		clear();
	}

	const ThingTemplate* friend_getThingTemplate() const { return m_thingTemplate; }	// only for WeaponSet::xfer
	const WeaponSetFlags& friend_getWeaponSetFlags() const { return m_types; }	// only for WeaponSet::xfer

	void clear();
	void parseWeaponTemplateSet( INI* ini, const ThingTemplate* tt );
	Bool testWeaponSetFlag( WeaponSetType wst ) const;
	Bool isSharedReloadTime( void ) const { return m_isReloadTimeShared; }
	Bool isWeaponLockSharedAcrossSets() const {return m_isWeaponLockSharedAcrossSets; }

	Bool hasAnyWeapons() const;
	inline const WeaponTemplate* getNth(WeaponSlotType n) const { return m_template[n]; } 
	inline UnsignedInt getNthCommandSourceMask(WeaponSlotType n) const { return m_autoChooseMask[n]; } 
	inline const KindOfMaskType& getNthPreferredAgainstMask(WeaponSlotType n) const { return m_preferredAgainst[n]; } 

	inline Int getConditionsYesCount() const { return 1; }
	inline const WeaponSetFlags& getNthConditionsYes(Int i) const { return m_types; }
#if defined(_DEBUG) || defined(_INTERNAL)
	inline AsciiString getDescription() const { return AsciiString("ArmorTemplateSet"); }
#endif
};

//-------------------------------------------------------------------------------------------------
typedef std::vector<WeaponTemplateSet> WeaponTemplateSetVector;

//-------------------------------------------------------------------------------------------------
typedef SparseMatchFinder<WeaponTemplateSet, WeaponSetFlags> WeaponTemplateSetFinder;

//-------------------------------------------------------------------------------------------------
enum WeaponChoiceCriteria
{
	PREFER_MOST_DAMAGE,		///< choose the weapon that will do the most damage
	PREFER_LONGEST_RANGE	///< choose the weapon with the longest range (that will do nonzero damage)
};

//-------------------------------------------------------------------------------------------------
enum WeaponLockType
{
	NOT_LOCKED,							///< Weapon is not locked
	LOCKED_TEMPORARILY,			///< Weapon is locked until clip is empty, or current "attack" state exits
	LOCKED_PERMANENTLY			///< Weapon is locked until explicitly unlocked or lock is changed to another weapon
};

//-------------------------------------------------------------------------------------------------
enum CanAttackResult
{
	//Worst scenario to best scenario -- These must be done this way now!
	ATTACKRESULT_NOT_POSSIBLE,					//Can't possibly attack target.
	ATTACKRESULT_INVALID_SHOT,					//Not a clear shot
	ATTACKRESULT_POSSIBLE_AFTER_MOVING, //I can attack, but after moving closer.
	ATTACKRESULT_POSSIBLE,							//I can attack now.
};

//-------------------------------------------------------------------------------------------------
class WeaponSet : public Snapshot
{
private:
	const WeaponTemplateSet*	m_curWeaponTemplateSet;
	Weapon*										m_weapons[WEAPONSLOT_COUNT];
	WeaponSlotType						m_curWeapon;
	WeaponLockType						m_curWeaponLockedStatus;
	UnsignedInt								m_filledWeaponSlotMask;
	Int												m_totalAntiMask;						///< anti mask of all current weapons
	UnsignedInt								m_totalDamageTypeMask;			///< damagetype mask of all current weapons
	Bool											m_hasPitchLimit;
	Bool											m_hasDamageWeapon;

	Bool isAnyWithinTargetPitch(const Object* obj, const Object* victim) const;

protected:
	// snapshot methods
	virtual void crc( Xfer *xfer );
	virtual void xfer( Xfer *xfer );
	virtual void loadPostProcess( void );

public:

	WeaponSet();
	~WeaponSet();

	void updateWeaponSet(const Object* obj);
	void reloadAllAmmo(const Object *obj, Bool now);
	Bool isOutOfAmmo() const;
	Bool hasAnyWeapon() const { return m_filledWeaponSlotMask != 0; }
	Bool hasAnyDamageWeapon() const { return m_hasDamageWeapon; }
	Bool hasWeaponToDealDamageType(DamageType typeToDeal) const { return (m_totalDamageTypeMask & (1 << typeToDeal)) != 0; }
	Bool hasSingleDamageType(DamageType typeToDeal) const { return m_totalDamageTypeMask == (1 << typeToDeal); }
	Bool isCurWeaponLocked() const { return m_curWeaponLockedStatus != NOT_LOCKED; }
	Weapon* getCurWeapon() { return m_weapons[m_curWeapon]; }
	const Weapon* getCurWeapon() const { return m_weapons[m_curWeapon]; }
	WeaponSlotType getCurWeaponSlot() const { return m_curWeapon; }
	Weapon* findWaypointFollowingCapableWeapon();
	const Weapon* findAmmoPipShowingWeapon() const;

	Bool setWeaponLock( WeaponSlotType weaponSlot, WeaponLockType lockType );
	void releaseWeaponLock(WeaponLockType lockType);
	Bool isSharedReloadTime() const;

	//When an AIAttackState is over, it needs to clean up any weapons that might be in leech range mode
	//or else those weapons will have unlimited range!
	void clearLeechRangeModeForAllWeapons();

	/**
		Determines if the unit has any weapon that could conceivably
		harm the victim. this does not take range, ammo, etc. into 
		account, but immutable weapon properties, such as "can you
		target airborne victims".
	*/
	CanAttackResult getAbleToAttackSpecificObject( AbleToAttackType t, const Object* obj, const Object* victim, CommandSourceType commandSource ) const;

	//When calling this function, all conditions must be validated to the point where we have decided that we wish to attack the object (faction checks, etc).
	//Now, we are determining if the attack itself is able to be performed!
	CanAttackResult getAbleToUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *source, const Object *victim, const Coord3D *pos, CommandSourceType commandSource ) const;

	/**
		Selects the best weapon for the given target, and sets it as the current weapon.
		If there is no weapon that can damage the target, false is returned (and the current-weapon is unchanged).
		Note that this DOES take weapon attack range into account.
	*/
	Bool chooseBestWeaponForTarget(const Object* obj, const Object* victim, WeaponChoiceCriteria criteria, CommandSourceType cmdSource);

	Weapon* getWeaponInWeaponSlot(WeaponSlotType wslot) const;

	static ModelConditionFlags getModelConditionForWeaponSlot(WeaponSlotType wslot, WeaponSetConditionType a);
};

#endif	// _WeaponSet_H_
