class CatchingContextFishingRodAction : CatchingContextFishingBase
{
	protected PlayerBase m_Player;
	//chances + modifiers
	protected float m_BaitLossChanceMod;
	protected float m_HookLossChanceMod;
	//times
	protected float m_SignalDurationMin; //seconds
	protected float m_SignalDurationMax; //seconds
	protected float m_SignalStartTimeMin; //seconds
	protected float m_SignalStartTimeMax; //seconds
	//signal targets
	protected float m_SignalCycleTarget;
	protected float m_SignalCycleEndTarget;
	protected float m_SignalCycleTargetAdjustment;
	protected float m_SignalCycleTargetEndAdjustment;
	//signal targets - constant
	protected float m_SignalTargetProbability;
	protected float m_SignalTargetEndProbability;
	//misc
	protected int m_SignalCurrent;
	
	//important items
	protected EntityAI m_Hook;
	protected EntityAI m_Bait;
	protected EntityAI m_Rod;
	
	override protected void Init(Param par)
	{
		super.Init(par);
		
		m_Rod = m_MainItem; //only stable one, rest initialized on 'InitItemValues' periodically
		m_Player = PlayerBase.Cast(m_MainItem.GetHierarchyRootPlayer());
	}
	
	override protected void InitCatchMethodMask()
	{
		m_MethodMask = AnimalCatchingConstants.MASK_METHOD_ROD;
	}
	
	override protected void InitCatchEnviroMask()
	{
		if (m_IsSea)
			m_EnviroMask = AnimalCatchingConstants.MASK_ENVIRO_SEA;
		else
			m_EnviroMask = AnimalCatchingConstants.MASK_ENVIRO_POND;
	}
	
	override protected void CreateResultDataStructure()
	{
		m_Result = new CarchingResultFishingAction(m_MainItem);
		
		super.CreateResultDataStructure();
	}
	
	//! only clear stuff you need to update
	override protected void ClearCatchingItemData()
	{
		super.ClearCatchingItemData();
		
		ResetSignalCounter();
		m_HookLossChanceMod = 0.0;
		m_BaitLossChanceMod = 0.0;
		m_SignalDurationMin = -1;
		m_SignalDurationMax = -1;
		m_SignalStartTimeMin = -1;
		m_SignalStartTimeMax = -1;
		m_SignalCycleTarget = UAFishingConstants.SIGNAL_CYCLE_MEAN_DEFAULT - 1; //lowered by '1', configured value is count
		m_SignalCycleEndTarget = UAFishingConstants.SIGNAL_CYCLE_HARD_TARGET_DEFAULT - 1; //lowered by '1', configured value is count
		m_SignalTargetProbability = UAFishingConstants.SIGNAL_MEAN_CHANCE_DEFAULT;
		m_SignalTargetEndProbability = UAFishingConstants.SIGNAL_HARD_TARGET_CHANCE_DEFAULT;
		m_SignalCycleTargetAdjustment = 0.0;
		m_SignalCycleTargetEndAdjustment = 0.0;
	}
	
	override void InitCatchingItemData()
	{
		super.InitCatchingItemData();
		
		//sanitize fishing values
		if (m_SignalDurationMin == -1) //if not set by any item
			m_SignalDurationMin = UAFishingConstants.SIGNAL_DURATION_MIN_BASE;
		else
			m_SignalDurationMin = Math.Clamp(m_SignalDurationMin,UAFishingConstants.SIGNAL_DURATION_MIN_BASE,UAFishingConstants.SIGNAL_DURATION_MAX_BASE);
		
		if (m_SignalDurationMax == -1) //if not set by any item
			m_SignalDurationMax = UAFishingConstants.SIGNAL_DURATION_MAX_BASE;
		else
			m_SignalDurationMax = Math.Clamp(m_SignalDurationMax,UAFishingConstants.SIGNAL_DURATION_MIN_BASE,UAFishingConstants.SIGNAL_DURATION_MAX_BASE);
		
		if (m_SignalStartTimeMin == -1) //if not set by any item (else already clamped)
			m_SignalStartTimeMin = UAFishingConstants.SIGNAL_START_TIME_MIN_BASE;
		
		if (m_SignalStartTimeMax == -1) //if not set by any item (else already clamped)
			m_SignalStartTimeMax = UAFishingConstants.SIGNAL_START_TIME_MAX_BASE;
		
		m_SignalCycleTarget = Math.Clamp((m_SignalCycleTarget + m_SignalCycleTargetAdjustment),0,float.MAX);
		m_SignalCycleEndTarget = Math.Clamp((m_SignalCycleEndTarget + m_SignalCycleTargetEndAdjustment),(m_SignalCycleTarget + 1),float.MAX);
		
		#ifdef DEVELOPER
		if (IsCLIParam("fishingLogs"))
		{
			Debug.Log("---InitCatchingItemData---","Fishing");
			Debug.Log("m_SignalCycleTarget (adjusted): " + m_SignalCycleTarget,"Fishing");
			Debug.Log("m_SignalCycleTargetAdjustment: " + m_SignalCycleTargetAdjustment,"Fishing");
			Debug.Log("m_SignalTargetProbability: " + m_SignalTargetProbability,"Fishing");
			Debug.Log("m_SignalCycleEndTarget (adjusted): " + m_SignalCycleEndTarget,"Fishing");
			Debug.Log("m_SignalCycleTargetEndAdjustment: " + m_SignalCycleTargetEndAdjustment,"Fishing");
			Debug.Log("m_SignalTargetEndProbability: " + m_SignalTargetEndProbability,"Fishing");
			Debug.Log("m_SignalDurationMin: " + m_SignalDurationMin,"Fishing");
			Debug.Log("m_SignalDurationMax: " + m_SignalDurationMax,"Fishing");
			Debug.Log("m_SignalStartTimeMin: " + m_SignalStartTimeMin,"Fishing");
			Debug.Log("m_SignalStartTimeMax: " + m_SignalStartTimeMax,"Fishing");
		}
		#endif
	}
	
	override protected void InitItemValues(EntityAI item)
	{
		//skip ruined or deleted items entirely
		if (item.IsRuined() || item.IsSetForDeletion())
			return;
		
		string path = "" + CFG_VEHICLESPATH + " " + item.GetType() + " Fishing";
		if (g_Game.ConfigIsExisting(path))
		{
			if (g_Game.ConfigIsExisting(path + " resultQuantityBaseMod"))
				m_QualityBaseMod += g_Game.ConfigGetFloat(path + " resultQuantityBaseMod");
			if (g_Game.ConfigIsExisting(path + " resultQuantityDispersionMin"))
				m_QualityDispersionMinMod += g_Game.ConfigGetFloat(path + " resultQuantityDispersionMin");
			if (g_Game.ConfigIsExisting(path + " resultQuantityDispersionMax"))
				m_QualityDispersionMaxMod += g_Game.ConfigGetFloat(path + " resultQuantityDispersionMax");
			if (g_Game.ConfigIsExisting(path + " hookLossChanceMod"))
				m_HookLossChanceMod += g_Game.ConfigGetFloat(path + " hookLossChanceMod");
			if (g_Game.ConfigIsExisting(path + " baitLossChanceMod"))
				m_BaitLossChanceMod += g_Game.ConfigGetFloat(path + " baitLossChanceMod");
			
			if (g_Game.ConfigIsExisting(path + " signalDurationMin"))
			{
				if (m_SignalDurationMin == -1)
					m_SignalDurationMin = 0;
				m_SignalDurationMin += g_Game.ConfigGetFloat(path + " signalDurationMin");
			}
			if (g_Game.ConfigIsExisting(path + " signalDurationMax"))
			{
				if (m_SignalDurationMax == -1)
					m_SignalDurationMax = 0;
				m_SignalDurationMax += g_Game.ConfigGetFloat(path + " signalDurationMax");
			}
			
			if (g_Game.ConfigIsExisting(path + " signalCycleTargetAdjustment"))
				m_SignalCycleTargetAdjustment += g_Game.ConfigGetFloat(path + " signalCycleTargetAdjustment");
			if (g_Game.ConfigIsExisting(path + " signalCycleTargetEndAdjustment"))
				m_SignalCycleTargetEndAdjustment += g_Game.ConfigGetFloat(path + " signalCycleTargetEndAdjustment");
			
			int slotID;
			string slotName;
			if (item.GetInventory().GetCurrentAttachmentSlotInfo(slotID,slotName))
			{
				switch (slotName)
				{
					case "Bait":
						m_Bait = item;
						break;
					
					case "Hook":
						m_Hook = item;
						break;
				}
			}
		}
	}
	
	override bool ModifySignalProbability(inout float probability)
	{
		float easingTime;
		if ((float)m_SignalCurrent < m_SignalCycleTarget)
		{
			easingTime = Math.InverseLerp(0,m_SignalCycleTarget,(float)m_SignalCurrent);
			probability = Easing.EaseInExpo(easingTime) * m_SignalTargetProbability * GetChanceCoef();
		}
		else
		{
			easingTime = Math.InverseLerp(m_SignalCycleTarget,m_SignalCycleEndTarget,(float)m_SignalCurrent);
			probability = (m_SignalTargetProbability + (Easing.EaseInExpo(easingTime) * (m_SignalTargetEndProbability - m_SignalTargetProbability))) * GetChanceCoef();
		}
		
		#ifdef DEVELOPER
		// Needs to be revisited, won't work when the autotests run inside of workbench
		if (IsCLIParam("autotest"))
			probability = UAFishingConstants.DEBUG_FISHING_CHANCE_PROBABILITY;
		
		if (IsCLIParam("fishingLogs"))
		{
			Debug.Log("---ModifySignalProbability---","Fishing");
			Debug.Log("m_SignalCurrent: " + m_SignalCurrent,"Fishing");
			Debug.Log("easingTime: " + easingTime,"Fishing");
			Debug.Log("probability: " + probability,"Fishing");
		}
		#endif
		
		return true;
	}
	
	float GetChanceCoef()
	{
		return UAFishingConstants.SIGNAL_FISHING_CHANCE_COEF;
	}
	
	override bool RollCatch()
	{
		bool ret = super.RollCatch();
		m_SignalCurrent++;
		return ret;
	}
	
	protected void ResetSignalCounter()
	{
		m_SignalCurrent = 0.0;
	}
	
	//! done locally on both sides, needs a synced random
	override void GenerateResult()
	{
		YieldItemBase yItem;
		int idx = m_Player.GetRandomGeneratorSyncManager().GetRandomInRange(RandomGeneratorSyncUsage.RGSAnimalCatching,0,m_ProbabilityArray.Count() - 1);
		Class.CastTo(yItem,m_YieldsMapAll.Get(m_ProbabilityArray[idx]));
		m_Result.SetYieldItem(yItem);
	}
	
	int GetResultParticleId()
	{
		if (m_Result)
			return m_Result.GetYieldItemParticleId();
		
		return ParticleList.INVALID;
	}
	
	override protected void RecalculateProcessingData()
	{
		m_Result.UpdateCatchChance(this);
	}
	
	float GetHookLossChanceModifierClamped()
	{
		return Math.Clamp(m_HookLossChanceMod,0.001,1);
	}
	
	float GetBaitLossChanceModifierClamped()
	{
		return Math.Clamp(m_BaitLossChanceMod,0,1);
	}
	
	float GetActionCycleTime()
	{
		return CarchingResultFishingAction.Cast(m_Result).GetCurrentCycleTime(this);
	}
	
	float RandomizeSignalDuration()
	{
		float res = m_Player.GetRandomGeneratorSyncManager().GetRandomInRange(RandomGeneratorSyncUsage.RGSAnimalCatching,m_SignalDurationMin,m_SignalDurationMax);
		#ifdef DEVELOPER
		if (IsCLIParam("fishingLogs"))
		{
			Debug.Log("---RandomizeSignalDuration---","Fishing");
			Debug.Log("next signal duration: " + res,"Fishing");
		}
		#endif
		
		return res;
	}
	
	float RandomizeSignalStartTime()
	{
		float res = m_Player.GetRandomGeneratorSyncManager().GetRandomInRange(RandomGeneratorSyncUsage.RGSAnimalCatching,m_SignalStartTimeMin,m_SignalStartTimeMax);
		#ifdef DEVELOPER
		if (IsCLIParam("fishingLogs"))
		{
			Debug.Log("---RandomizeSignalStartTime---","Fishing");
			Debug.Log("next signal start time: " + res,"Fishing");
		}
		#endif
		return res;
	}
	
	protected void TryHookLoss()
	{
		if (m_Hook && !m_Hook.IsSetForDeletion())
		{
			float lossChance = GetHookLossChanceModifierClamped();
			if (lossChance <= 0)
				return;
			
			float roll = m_Player.GetRandomGeneratorSyncManager().GetRandom01(RandomGeneratorSyncUsage.RGSAnimalCatching);
			
			if (lossChance >= 1 || roll < lossChance)
			{
				RemoveItemSafe(m_Hook);
			}
		}
	}
	
	protected void RemoveItemSafe(EntityAI item)
	{
		if (item && !m_Player.IsQuickFishing())
		{
			item.SetPrepareToDelete(); //should probably flag the bait too, but the action terminates anyway
			item.DeleteSafe();
		}
	}
	
	protected void TryBaitLoss()
	{
		if (m_Bait && !m_Bait.IsSetForDeletion())
		{
			float lossChance = GetBaitLossChanceModifierClamped();
			if (lossChance <= 0)
				return;
			
			float roll = m_Player.GetRandomGeneratorSyncManager().GetRandom01(RandomGeneratorSyncUsage.RGSAnimalCatching);
			
			if (lossChance >= 1 || roll < lossChance)
			{
				RemoveItemSafe(m_Bait);
			}
		}
	}
	
	protected void TryDamageItems()
	{
		if (!g_Game.IsMultiplayer() || g_Game.IsDedicatedServer())
		{
			if (m_Hook && !m_Hook.IsSetForDeletion())
				m_Hook.AddHealth("","Health",-UAFishingConstants.DAMAGE_HOOK);
		}
	}
	
	override EntityAI SpawnAndSetupCatch(out int yItemIdx, vector v = vector.Zero)
	{
		//hook check on catch
		if (m_Hook && !m_Hook.IsSetForDeletion())
			return super.SpawnAndSetupCatch(yItemIdx,v);
		return null;
	}
	
	//events
	void OnBeforeSpawnSignalHit()
	{
		TryHookLoss();
	}
	
	void OnAfterSpawnSignalHit()
	{
		RemoveItemSafe(m_Bait);
		TryDamageItems();
	}
	
	//! release without signal
	void OnSignalMiss()
	{
		TryHookLoss();
		TryBaitLoss();
	}
	
	void OnSignalPass()
	{
		RemoveItemSafe(m_Bait);
		TryDamageItems();
		UpdateCatchingItemData();
	}
	
	///////////////////////////////
	//Fish pen of deprecated code//
	///////////////////////////////
	protected float m_SignalPoissonMean = AnimalCatchingConstants.POISSON_CYCLE_MEAN_DEFAULT; //!Deprecated, left here due to inheritance change
	float GetSignalPoissonMean() {return m_SignalPoissonMean;} //!Deprecated, left here due to inheritance change
	int GetSignalMax() {return Math.Ceil(m_SignalPoissonMean) + Math.Ceil(m_SignalPoissonMean/5);} //!Deprecated, left here due to inheritance change
}