using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using RoR2.UI;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

namespace RoR2;

public class HoldoutZoneController : BaseZoneBehavior
{
	public enum HoldoutZoneShape
	{
		Sphere,
		VerticalTube,
		Count
	}

	public delegate void CalcRadiusDelegate(ref float radius);

	public delegate void CalcChargeRateDelegate(ref float rate);

	public delegate void CalcAccumulatedChargeDelegate(ref float charge);

	public delegate void CalcColorDelegate(ref Color color);

	[Serializable]
	public class HoldoutZoneControllerChargedUnityEvent : UnityEvent<HoldoutZoneController>
	{
	}

	private class ChargeHoldoutZoneObjectiveTracker : ObjectivePanelController.ObjectiveTracker
	{
		private int lastPercent = -1;

		private HoldoutZoneController holdoutZoneController => (HoldoutZoneController)sourceDescriptor.source;

		private bool ShouldBeFlashing()
		{
			bool flag = true;
			if ((bool)sourceDescriptor.master && (bool)holdoutZoneController)
			{
				flag = holdoutZoneController.IsBodyInChargingRadius(sourceDescriptor.master.GetBody());
			}
			return !flag;
		}

		protected override string GenerateString()
		{
			lastPercent = holdoutZoneController.displayChargePercent;
			string text = string.Format(Language.GetString(holdoutZoneController.inBoundsObjectiveToken), lastPercent);
			if (ShouldBeFlashing())
			{
				text = string.Format(Language.GetString(holdoutZoneController.outOfBoundsObjectiveToken), lastPercent);
				if ((int)(Time.time * 12f) % 2 == 0)
				{
					text = $"<style=cDeath>{text}</style>";
				}
			}
			return text;
		}

		protected override bool IsDirty()
		{
			return true;
		}
	}

	private class FocusConvergenceController : MonoBehaviour
	{
		private static readonly float convergenceRadiusDivisor = 2f;

		private static readonly float convergenceChargeRateBonus = 0.3f;

		private static readonly Color convergenceMaterialColor = new Color(0f, 3.9411764f, 5f, 1f);

		private static readonly float rampUpTime = 5f;

		private static readonly float startupDelay = 3f;

		private static readonly int cap = 3;

		private float currentValue;

		private HoldoutZoneController holdoutZoneController;

		private int currentFocusConvergenceCount;

		private Run.FixedTimeStamp enabledTime;

		private static readonly AnimationCurve colorCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);

		private void Awake()
		{
			holdoutZoneController = GetComponent<HoldoutZoneController>();
		}

		private void OnEnable()
		{
			enabledTime = Run.FixedTimeStamp.now;
			holdoutZoneController.calcRadius += ApplyRadius;
			holdoutZoneController.calcChargeRate += ApplyRate;
			holdoutZoneController.calcColor += ApplyColor;
		}

		private void OnDisable()
		{
			holdoutZoneController.calcColor -= ApplyColor;
			holdoutZoneController.calcChargeRate -= ApplyRate;
			holdoutZoneController.calcRadius -= ApplyRadius;
		}

		private void ApplyRadius(ref float radius)
		{
			if (currentFocusConvergenceCount > 0)
			{
				radius /= convergenceRadiusDivisor * (float)currentFocusConvergenceCount;
			}
		}

		private void ApplyColor(ref Color color)
		{
			color = Color.Lerp(color, convergenceMaterialColor, colorCurve.Evaluate(currentValue));
		}

		private void ApplyRate(ref float rate)
		{
			if (currentFocusConvergenceCount > 0)
			{
				rate *= 1f + convergenceChargeRateBonus * (float)currentFocusConvergenceCount;
			}
		}

		private void Update()
		{
			DoUpdate(Time.deltaTime);
		}

		private void DoUpdate(float deltaTime)
		{
			currentFocusConvergenceCount = Util.GetItemCountForTeam(holdoutZoneController.chargingTeam, RoR2Content.Items.FocusConvergence.itemIndex, requiresAlive: true, requiresConnected: false);
			if (enabledTime.timeSince < startupDelay)
			{
				currentFocusConvergenceCount = 0;
			}
			currentFocusConvergenceCount = Mathf.Min(currentFocusConvergenceCount, cap);
			float target = (((float)currentFocusConvergenceCount > 0f) ? 1f : 0f);
			float num = Mathf.MoveTowards(currentValue, target, rampUpTime * deltaTime);
			if (currentValue <= 0f && num > 0f)
			{
				Util.PlaySound("Play_item_lunar_focusedConvergence", base.gameObject);
			}
			currentValue = num;
		}
	}

	public HoldoutZoneShape holdoutZoneShape;

	[Tooltip("The base radius of this charging sphere. Players must be within this radius to charge this zone.")]
	public float baseRadius;

	[Tooltip("No modifiers can reduce the radius below this size")]
	public float minimumRadius;

	[Tooltip("The overall change to the radius from 0% charge to 100% charge.")]
	public float chargeRadiusDelta;

	[Tooltip("How long it takes for this zone to finish charging without any modifiers.")]
	public float baseChargeDuration;

	[Tooltip("Approximately how long it should take to change from any given radius to the desired one.")]
	public float radiusSmoothTime;

	[Tooltip("An object instance which will be used to represent the clear radius.")]
	public Renderer radiusIndicator;

	[Tooltip("The child object to enable when healing nova should be active.")]
	public GameObject healingNovaItemEffect;

	public Transform healingNovaRoot;

	public string inBoundsObjectiveToken = "OBJECTIVE_CHARGE_TELEPORTER";

	public string outOfBoundsObjectiveToken = "OBJECTIVE_CHARGE_TELEPORTER_OOB";

	public bool showObjective = true;

	public bool applyFocusConvergence;

	public bool applyHealingNova = true;

	public bool applyDevotedEvolution;

	public bool applyDelusionResetChests;

	[Range(0f, float.MaxValue)]
	public float playerCountScaling = 1f;

	[Tooltip("If the zone is empty, this is the rate at which the charge decreases (a negative value will increase charge)")]
	public float dischargeRate;

	public HoldoutZoneControllerChargedUnityEvent onCharged;

	private BuffWard buffWard;

	private static MaterialPropertyBlock sharedColorPropertyBlock;

	private Color baseIndicatorColor;

	private float radiusVelocity;

	private bool wasCharged;

	private GameObject[] healingNovaGeneratorsByTeam = new GameObject[5];

	[SyncVar]
	private float _charge;

	public float currentRadius { get; private set; }

	public bool isAnyoneCharging { get; private set; }

	public TeamIndex chargingTeam { get; set; } = TeamIndex.Player;


	public int displayChargePercent => Mathf.Clamp(Mathf.FloorToInt(charge * 99f), 0, 99);

	public float charge
	{
		get
		{
			return _charge;
		}
		private set
		{
			Network_charge = value;
		}
	}

	public float Network_charge
	{
		get
		{
			return _charge;
		}
		[param: In]
		set
		{
			SetSyncVar(value, ref _charge, 1u);
		}
	}

	public event CalcRadiusDelegate calcRadius;

	public event CalcChargeRateDelegate calcChargeRate;

	public event CalcAccumulatedChargeDelegate calcAccumulatedCharge;

	public event CalcColorDelegate calcColor;

	[InitDuringStartup]
	private static void Init()
	{
		sharedColorPropertyBlock = new MaterialPropertyBlock();
		ObjectivePanelController.collectObjectiveSources += OnCollectObjectiveSources;
	}

	private void Awake()
	{
		if ((bool)radiusIndicator)
		{
			baseIndicatorColor = radiusIndicator.sharedMaterial.GetColor("_TintColor");
		}
		buffWard = GetComponent<BuffWard>();
	}

	private void Start()
	{
		if (applyFocusConvergence)
		{
			base.gameObject.AddComponent<FocusConvergenceController>();
		}
	}

	private void OnEnable()
	{
		if ((bool)radiusIndicator)
		{
			radiusIndicator.enabled = true;
			radiusIndicator.gameObject.SetActive(value: true);
		}
		currentRadius = 0f;
		InstanceTracker.Add(this);
	}

	private void OnDisable()
	{
		InstanceTracker.Remove(this);
		currentRadius = 0f;
		if ((bool)radiusIndicator)
		{
			radiusIndicator.enabled = false;
			radiusIndicator.gameObject.SetActive(value: false);
		}
	}

	private void UpdateHealingNovas(bool isCharging)
	{
		if (!applyHealingNova)
		{
			return;
		}
		bool flag = false;
		for (TeamIndex teamIndex = TeamIndex.Neutral; teamIndex < TeamIndex.Count; teamIndex++)
		{
			bool flag2 = Util.GetItemCountForTeam(teamIndex, RoR2Content.Items.TPHealingNova.itemIndex, requiresAlive: false) > 0 && isCharging;
			flag = flag || flag2;
			if (NetworkServer.active)
			{
				ref GameObject reference = ref healingNovaGeneratorsByTeam[(int)teamIndex];
				if (flag2 != (bool)reference)
				{
					if (flag2)
					{
						reference = UnityEngine.Object.Instantiate(LegacyResourcesAPI.Load<GameObject>("Prefabs/NetworkedObjects/TeleporterHealNovaGenerator"), healingNovaRoot ?? base.transform);
						reference.GetComponent<TeamFilter>().teamIndex = teamIndex;
						NetworkServer.Spawn(reference);
					}
					else
					{
						UnityEngine.Object.Destroy(reference);
						reference = null;
					}
				}
			}
		}
		if ((bool)healingNovaItemEffect)
		{
			healingNovaItemEffect.SetActive(flag);
		}
	}

	private void Update()
	{
		DoUpdate(Time.deltaTime);
	}

	private void DoUpdate(float deltaTime)
	{
		int num = CountLivingPlayers(chargingTeam);
		int num2 = CountPlayersInRadius(this, base.transform.position, currentRadius * currentRadius, chargingTeam);
		isAnyoneCharging = num2 > 0;
		if ((bool)Run.instance)
		{
			float radius = baseRadius + charge * chargeRadiusDelta;
			if (Run.instance.selectedDifficulty >= DifficultyIndex.Eclipse2)
			{
				radius *= 0.5f;
			}
			this.calcRadius?.Invoke(ref radius);
			currentRadius = Mathf.Max(Mathf.SmoothDamp(currentRadius, radius, ref radiusVelocity, radiusSmoothTime, float.PositiveInfinity, deltaTime), minimumRadius);
		}
		if ((bool)radiusIndicator)
		{
			float num3 = 2f * currentRadius;
			radiusIndicator.transform.localScale = new Vector3(num3, num3, num3);
		}
		if (NetworkServer.active && (bool)buffWard)
		{
			buffWard.Networkradius = currentRadius;
		}
		if (NetworkServer.active)
		{
			float num4 = baseChargeDuration;
			float rate = ((!isAnyoneCharging || num <= 0) ? (0f - dischargeRate) : (Mathf.Pow((float)num2 / (float)num, playerCountScaling) / num4));
			this.calcChargeRate?.Invoke(ref rate);
			charge = Mathf.Clamp01(charge + rate * deltaTime);
			float num5 = charge;
			this.calcAccumulatedCharge?.Invoke(ref num5);
			charge = num5;
		}
		Color color = baseIndicatorColor;
		this.calcColor?.Invoke(ref color);
		sharedColorPropertyBlock.SetColor("_TintColor", color);
		if ((bool)radiusIndicator)
		{
			radiusIndicator.SetPropertyBlock(sharedColorPropertyBlock);
		}
		bool flag = charge >= 1f;
		if (wasCharged != flag)
		{
			wasCharged = flag;
			if (flag)
			{
				onCharged?.Invoke(this);
				if (applyDevotedEvolution)
				{
					UpdateDevotedLumerains(chargingTeam);
				}
				if (applyDelusionResetChests && RunArtifactManager.instance.IsArtifactEnabled(CU8Content.Artifacts.Delusion))
				{
					foreach (ChestBehavior instances in InstanceTracker.GetInstancesList<ChestBehavior>())
					{
						instances.CallRpcResetChests();
					}
				}
			}
		}
		UpdateHealingNovas(isAnyoneCharging);
	}

	private void OnDrawGizmos()
	{
		Matrix4x4 matrix = Gizmos.matrix;
		Color color = Gizmos.color;
		Gizmos.matrix = base.transform.localToWorldMatrix;
		Gizmos.color = new Color(0.75f, 0f, 0f, 0.5f);
		Gizmos.DrawWireSphere(Vector3.zero, baseRadius);
		Gizmos.color = color;
		Gizmos.matrix = matrix;
	}

	private static bool IsPointInChargingRadius(HoldoutZoneController holdoutZoneController, Vector3 origin, float chargingRadiusSqr, Vector3 point)
	{
		switch (holdoutZoneController.holdoutZoneShape)
		{
		case HoldoutZoneShape.Sphere:
			if ((point - origin).sqrMagnitude <= chargingRadiusSqr)
			{
				return true;
			}
			break;
		case HoldoutZoneShape.VerticalTube:
			point.y = 0f;
			origin.y = 0f;
			if ((point - origin).sqrMagnitude <= chargingRadiusSqr)
			{
				return true;
			}
			break;
		}
		return false;
	}

	private static bool IsBodyInChargingRadius(HoldoutZoneController holdoutZoneController, Vector3 origin, float chargingRadiusSqr, CharacterBody characterBody)
	{
		return IsPointInChargingRadius(holdoutZoneController, origin, chargingRadiusSqr, characterBody.corePosition);
	}

	private static int CountLivingPlayers(TeamIndex teamIndex)
	{
		int num = 0;
		ReadOnlyCollection<TeamComponent> teamMembers = TeamComponent.GetTeamMembers(teamIndex);
		for (int i = 0; i < teamMembers.Count; i++)
		{
			if (teamMembers[i].body.isPlayerControlled)
			{
				num++;
			}
		}
		return num;
	}

	public static int CountPlayersInRadius(HoldoutZoneController holdoutZoneController, Vector3 origin, float chargingRadiusSqr, TeamIndex teamIndex)
	{
		int num = 0;
		ReadOnlyCollection<TeamComponent> teamMembers = TeamComponent.GetTeamMembers(teamIndex);
		for (int i = 0; i < teamMembers.Count; i++)
		{
			TeamComponent teamComponent = teamMembers[i];
			if (teamComponent.body.isPlayerControlled && IsBodyInChargingRadius(holdoutZoneController, origin, chargingRadiusSqr, teamComponent.body))
			{
				num++;
			}
		}
		return num;
	}

	public bool IsBodyInChargingRadius(CharacterBody body)
	{
		if (!body)
		{
			return false;
		}
		return IsBodyInChargingRadius(this, base.transform.position, currentRadius * currentRadius, body);
	}

	[Server]
	public void FullyChargeHoldoutZone()
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.HoldoutZoneController::FullyChargeHoldoutZone()' called on client");
		}
		else
		{
			charge = 1f;
		}
	}

	private static void OnCollectObjectiveSources(CharacterMaster master, List<ObjectivePanelController.ObjectiveSourceDescriptor> objectiveSourcesList)
	{
		List<HoldoutZoneController> instancesList = InstanceTracker.GetInstancesList<HoldoutZoneController>();
		int i = 0;
		for (int count = instancesList.Count; i < count; i++)
		{
			HoldoutZoneController holdoutZoneController = instancesList[i];
			if (holdoutZoneController.showObjective && holdoutZoneController.chargingTeam == master.teamIndex)
			{
				objectiveSourcesList.Add(new ObjectivePanelController.ObjectiveSourceDescriptor
				{
					master = master,
					objectiveType = typeof(ChargeHoldoutZoneObjectiveTracker),
					source = holdoutZoneController
				});
			}
		}
	}

	public override bool IsInBounds(Vector3 position)
	{
		return IsPointInChargingRadius(this, base.transform.position, currentRadius * currentRadius, position);
	}

	private void UpdateDevotedLumerains(TeamIndex teamIndex)
	{
		ReadOnlyCollection<TeamComponent> teamMembers = TeamComponent.GetTeamMembers(teamIndex);
		for (int i = 0; i < teamMembers.Count; i++)
		{
			TeamComponent teamComponent = teamMembers[i];
			if (teamComponent.body.isPlayerControlled && (bool)teamComponent.body.master.devotionInventoryPrefab)
			{
				teamComponent.body.master.devotionInventoryPrefab.GetComponent<DevotionInventoryController>().ActivateDevotedEvolution();
			}
		}
	}

	private void UNetVersion()
	{
	}

	public override bool OnSerialize(NetworkWriter writer, bool forceAll)
	{
		bool flag = base.OnSerialize(writer, forceAll);
		if (forceAll)
		{
			writer.Write(_charge);
			return true;
		}
		bool flag2 = false;
		if ((base.syncVarDirtyBits & (true ? 1u : 0u)) != 0)
		{
			if (!flag2)
			{
				writer.WritePackedUInt32(base.syncVarDirtyBits);
				flag2 = true;
			}
			writer.Write(_charge);
		}
		if (!flag2)
		{
			writer.WritePackedUInt32(base.syncVarDirtyBits);
		}
		return flag2 || flag;
	}

	public override void OnDeserialize(NetworkReader reader, bool initialState)
	{
		base.OnDeserialize(reader, initialState);
		if (initialState)
		{
			_charge = reader.ReadSingle();
			return;
		}
		int num = (int)reader.ReadPackedUInt32();
		if (((uint)num & (true ? 1u : 0u)) != 0)
		{
			_charge = reader.ReadSingle();
		}
	}

	public override void PreStartClient()
	{
		base.PreStartClient();
	}
}
