using System;
using System.Runtime.InteropServices;
using RoR2.Navigation;
using UnityEngine;
using UnityEngine.Networking;

namespace RoR2;

public class InfiniteTowerRun : Run
{
	[Serializable]
	public struct EnemyItemEntry
	{
		public PickupDropTable dropTable;

		public int stacks;
	}

	private const ulong waveRngSalt = 14312uL;

	private const ulong enemyItemRngSalt = 1535uL;

	private const ulong safeWardRngSalt = 769876uL;

	[SerializeField]
	[Header("Infinite Tower Settings")]
	[Tooltip("If all else fails, use this wave prefab")]
	private GameObject defaultWavePrefab;

	[Tooltip("Selects a wave from the first available category in this list")]
	[SerializeField]
	private InfiniteTowerWaveCategory[] waveCategories;

	[Tooltip("Use this indicator for enemies by default")]
	[SerializeField]
	private GameObject defaultWaveEnemyIndicatorPrefab;

	[Tooltip("The repeating pattern of drop tables to use when selecting items for the enemy team")]
	[SerializeField]
	private EnemyItemEntry[] enemyItemPattern;

	[SerializeField]
	[Tooltip("The number of waves before you give the enemy team the next item in the pattern (e.g., \"every Nth wave\")")]
	private int enemyItemPeriod;

	[SerializeField]
	[Tooltip("The reference inventory we use to store which items enemies should get")]
	private Inventory enemyInventory;

	[Tooltip("The number of waves before you transition to the next stage (e.g., \"every Nth wave\").")]
	[SerializeField]
	private int stageTransitionPeriod;

	[Tooltip("Spawn card for the stage transition portal")]
	[SerializeField]
	private InteractableSpawnCard stageTransitionPortalCard;

	[Tooltip("Maximum spawn distance for the stage transition portal.")]
	[SerializeField]
	private float stageTransitionPortalMaxDistance;

	[Tooltip("The chat message that's broadcasted when spawning the stage transition portal.")]
	[SerializeField]
	private string stageTransitionChatToken;

	[Tooltip("The prefab with the FogDamageController attached")]
	[SerializeField]
	private GameObject fogDamagePrefab;

	[SerializeField]
	[Tooltip("The maximum distance to spawn players from the safe ward")]
	private float spawnMaxRadius;

	[Tooltip("Spawn card for the safe ward that is spawned at the beginning of the run")]
	[SerializeField]
	private InteractableSpawnCard initialSafeWardCard;

	[Tooltip("Spawn card for the safe wards (after the first one)")]
	[SerializeField]
	private InteractableSpawnCard safeWardCard;

	[Tooltip("The effect to spawn when a player is revived at the end of a wave")]
	[SerializeField]
	private GameObject playerRespawnEffectPrefab;

	[Tooltip("The number of credits the SceneDirector uses to spawn interactables")]
	[SerializeField]
	private int interactableCredits;

	[Tooltip("Remove all items with these tags from the item pools")]
	[SerializeField]
	private ItemTag[] blacklistedTags;

	[Tooltip("Remove these items from the pool")]
	[SerializeField]
	private ItemDef[] blacklistedItems;

	[SyncVar]
	private int _waveIndex;

	[SyncVar]
	private NetworkInstanceId waveInstanceId;

	private InfiniteTowerWaveController _waveController;

	private Xoroshiro128Plus waveRng;

	private Xoroshiro128Plus enemyItemRng;

	private Xoroshiro128Plus safeWardRng;

	private int enemyItemPatternIndex;

	private InfiniteTowerSafeWardController safeWardController;

	private FogDamageController fogDamageController;

	public int waveIndex => _waveIndex;

	public InfiniteTowerWaveController waveController => _waveController;

	public override bool spawnWithPod => false;

	public override bool canFamilyEventTrigger => false;

	public override bool autoGenerateSpawnPoints => false;

	public GameObject waveInstance => Util.FindNetworkObject(waveInstanceId);

	public int Network_waveIndex
	{
		get
		{
			return _waveIndex;
		}
		[param: In]
		set
		{
			SetSyncVar(value, ref _waveIndex, 64u);
		}
	}

	public NetworkInstanceId NetworkwaveInstanceId
	{
		get
		{
			return waveInstanceId;
		}
		[param: In]
		set
		{
			SetSyncVar(value, ref waveInstanceId, 128u);
		}
	}

	public static event Action<InfiniteTowerWaveController> onWaveInitialized;

	public static event Action<InfiniteTowerWaveController> onAllEnemiesDefeatedServer;

	public override GameObject InstantiateUi(Transform uiRoot)
	{
		GameObject gameObject = base.InstantiateUi(uiRoot);
		if ((bool)_waveController)
		{
			_waveController.InstantiateUi(gameObject.transform);
		}
		return gameObject;
	}

	public override void OverrideRuleChoices(RuleChoiceMask mustInclude, RuleChoiceMask mustExclude, ulong runSeed)
	{
		base.OverrideRuleChoices(mustInclude, mustExclude, base.seed);
		ItemIndex itemIndex = ItemIndex.Count;
		for (ItemIndex itemCount = (ItemIndex)ItemCatalog.itemCount; itemIndex < itemCount; itemIndex++)
		{
			ItemDef itemDef = ItemCatalog.GetItemDef(itemIndex);
			bool flag = Array.IndexOf(blacklistedItems, itemDef) != -1;
			if (!flag)
			{
				ItemTag[] array = blacklistedTags;
				foreach (ItemTag itemTag in array)
				{
					if (itemDef.ContainsTag(itemTag))
					{
						flag = true;
						break;
					}
				}
			}
			if (flag)
			{
				RuleChoiceDef ruleChoiceDef = RuleCatalog.FindRuleDef("Items." + itemDef.name)?.FindChoice("Off");
				if (ruleChoiceDef != null)
				{
					ForceChoice(mustInclude, mustExclude, ruleChoiceDef);
				}
			}
		}
	}

	[Server]
	public void ResetSafeWard()
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::ResetSafeWard()' called on client");
			return;
		}
		if ((bool)safeWardController)
		{
			if ((bool)fogDamageController)
			{
				fogDamageController.RemoveSafeZone(safeWardController.safeZone);
			}
			safeWardController.SelfDestruct();
		}
		SpawnSafeWard(safeWardCard, new DirectorPlacementRule
		{
			placementMode = DirectorPlacementRule.PlacementMode.Random
		});
	}

	[Server]
	public void MoveSafeWard()
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::MoveSafeWard()' called on client");
		}
		else if ((bool)safeWardController)
		{
			safeWardController.RandomizeLocation(safeWardRng);
			safeWardController.onActivated += OnSafeWardActivated;
		}
	}

	public bool IsStageTransitionWave()
	{
		if (stageTransitionPeriod != 0)
		{
			return waveIndex % stageTransitionPeriod == 0;
		}
		return true;
	}

	public override Vector3 FindSafeTeleportPosition(CharacterBody characterBody, Transform targetDestination)
	{
		if ((bool)safeWardController && !targetDestination)
		{
			return base.FindSafeTeleportPosition(characterBody, safeWardController.transform);
		}
		return base.FindSafeTeleportPosition(characterBody, targetDestination);
	}

	public override Vector3 FindSafeTeleportPosition(CharacterBody characterBody, Transform targetDestination, float idealMinDistance, float idealMaxDistance)
	{
		if ((bool)safeWardController && !targetDestination)
		{
			return base.FindSafeTeleportPosition(characterBody, safeWardController.transform, idealMinDistance, idealMaxDistance);
		}
		return base.FindSafeTeleportPosition(characterBody, targetDestination, idealMinDistance, idealMaxDistance);
	}

	[Server]
	private void OnSafeWardActivated(InfiniteTowerSafeWardController safeWard)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::OnSafeWardActivated(RoR2.InfiniteTowerSafeWardController)' called on client");
			return;
		}
		safeWardController.onActivated -= OnSafeWardActivated;
		if ((bool)_waveController)
		{
			_waveController.ForceFinish();
			CleanUpCurrentWave();
		}
		BeginNextWave();
	}

	[Server]
	private void AdvanceWave()
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::AdvanceWave()' called on client");
			return;
		}
		Network_waveIndex = _waveIndex + 1;
		if (_waveIndex % enemyItemPeriod != 0)
		{
			return;
		}
		EnemyItemEntry enemyItemEntry = enemyItemPattern[enemyItemPatternIndex++ % enemyItemPattern.Length];
		if (!enemyItemEntry.dropTable)
		{
			return;
		}
		PickupIndex pickupIndex = enemyItemEntry.dropTable.GenerateDrop(enemyItemRng);
		if (pickupIndex != PickupIndex.none)
		{
			PickupDef pickupDef = PickupCatalog.GetPickupDef(pickupIndex);
			if (pickupDef != null)
			{
				enemyInventory.GiveItem(pickupDef.itemIndex, enemyItemEntry.stacks);
				Chat.SendBroadcastChat(new Chat.PlayerPickupChatMessage
				{
					baseToken = "INFINITETOWER_ADD_ITEM",
					pickupToken = pickupDef.nameToken,
					pickupColor = pickupDef.baseColor
				});
			}
		}
	}

	[Server]
	private void BeginNextWave()
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::BeginNextWave()' called on client");
			return;
		}
		AdvanceWave();
		GameObject original = defaultWavePrefab;
		InfiniteTowerWaveCategory[] array = waveCategories;
		foreach (InfiniteTowerWaveCategory infiniteTowerWaveCategory in array)
		{
			if (infiniteTowerWaveCategory.IsAvailable(this))
			{
				original = infiniteTowerWaveCategory.SelectWavePrefab(this, waveRng);
				break;
			}
		}
		GameObject gameObject = UnityEngine.Object.Instantiate(original, base.transform);
		NetworkServer.Spawn(gameObject);
		NetworkwaveInstanceId = gameObject.GetComponent<NetworkIdentity>().netId;
		RecalculateDifficultyCoefficentInternal();
	}

	protected override void Start()
	{
		Stage.onServerStageBegin += OnServerStageBegin;
		Stage.onServerStageComplete += OnServerStageComplete;
		SceneDirector.onPrePopulateSceneServer += OnPrePopulateSceneServer;
		base.Start();
	}

	protected override void OnDestroy()
	{
		Stage.onServerStageBegin -= OnServerStageBegin;
		Stage.onServerStageComplete -= OnServerStageComplete;
		SceneDirector.onPrePopulateSceneServer -= OnPrePopulateSceneServer;
		if ((bool)safeWardController)
		{
			UnityEngine.Object.Destroy(safeWardController.gameObject);
			safeWardController = null;
		}
		CleanUpCurrentWave();
		base.OnDestroy();
	}

	private void OnServerStageBegin(Stage stage)
	{
	}

	private void OnServerStageComplete(Stage stage)
	{
		PerformStageCleanUp();
	}

	private void OnPrePopulateSceneServer(SceneDirector sceneDirector)
	{
		PerformStageCleanUp();
		if ((bool)fogDamagePrefab)
		{
			GameObject gameObject = UnityEngine.Object.Instantiate(fogDamagePrefab, Stage.instance.transform);
			NetworkServer.Spawn(gameObject);
			fogDamageController = gameObject.GetComponent<FogDamageController>();
		}
		sceneDirector.interactableCredit = interactableCredits;
		DirectorPlacementRule placementRule = new DirectorPlacementRule
		{
			placementMode = DirectorPlacementRule.PlacementMode.Random
		};
		SpawnSafeWard(initialSafeWardCard, placementRule);
		if (!safeWardController)
		{
			return;
		}
		Vector3 position = safeWardController.transform.position;
		NodeGraph nodeGraph = SceneInfo.instance.GetNodeGraph(MapNodeGroup.GraphType.Ground);
		foreach (NodeGraph.NodeIndex item in nodeGraph.FindNodesInRangeWithFlagConditions(position, 0f, spawnMaxRadius, HullMask.Human, NodeFlags.None, NodeFlags.NoCharacterSpawn, preventOverhead: false))
		{
			if (nodeGraph.GetNodePosition(item, out var position2))
			{
				SpawnPoint.AddSpawnPoint(position2, Quaternion.LookRotation(position, Vector3.up));
			}
		}
	}

	protected override void FixedUpdate()
	{
		base.FixedUpdate();
		if (NetworkServer.active && (bool)waveInstance && (bool)waveController && waveController.isFinished && !IsStageTransitionWave())
		{
			CleanUpCurrentWave();
			BeginNextWave();
		}
		if ((bool)waveInstance)
		{
			if (!_waveController)
			{
				InitializeWaveController();
			}
		}
		else
		{
			_waveController = null;
		}
	}

	protected override void RecalculateDifficultyCoefficentInternal()
	{
		DifficultyDef difficultyDef = DifficultyCatalog.GetDifficultyDef(base.selectedDifficulty);
		float num = 1.5f * (float)waveIndex;
		float num2 = 0.0506f * difficultyDef.scalingValue;
		float num3 = Mathf.Pow(1.02f, waveIndex);
		difficultyCoefficient = (1f + num2 * num) * num3;
		compensatedDifficultyCoefficient = difficultyCoefficient;
		base.ambientLevel = Mathf.Min((difficultyCoefficient - 1f) / 0.33f + 1f, 9999f);
		int num4 = base.ambientLevelFloor;
		base.ambientLevelFloor = Mathf.FloorToInt(base.ambientLevel);
		if (num4 != base.ambientLevelFloor && num4 != 0 && base.ambientLevelFloor > num4)
		{
			OnAmbientLevelUp();
		}
	}

	private void InitializeWaveController()
	{
		_waveController = waveInstance.GetComponent<InfiniteTowerWaveController>();
		if (!_waveController)
		{
			return;
		}
		if (NetworkServer.active)
		{
			_waveController.Initialize(_waveIndex, enemyInventory, safeWardController.gameObject);
		}
		foreach (GameObject uiInstance in base.uiInstances)
		{
			_waveController.InstantiateUi(uiInstance.transform);
		}
		_waveController.PlayBeginSound();
		_waveController.defaultEnemyIndicatorPrefab = defaultWaveEnemyIndicatorPrefab;
		_waveController.onAllEnemiesDefeatedServer += OnWaveAllEnemiesDefeatedServer;
		InfiniteTowerRun.onWaveInitialized?.Invoke(_waveController);
	}

	private void OnWaveAllEnemiesDefeatedServer(InfiniteTowerWaveController wc)
	{
		InfiniteTowerRun.onAllEnemiesDefeatedServer?.Invoke(wc);
		if (base.isGameOverServer)
		{
			return;
		}
		foreach (PlayerCharacterMasterController instance in PlayerCharacterMasterController.instances)
		{
			CharacterMaster master = instance.master;
			if (!instance.isConnected || !master.IsDeadAndOutOfLivesServer())
			{
				continue;
			}
			Vector3 vector = master.deathFootPosition;
			if ((bool)safeWardController)
			{
				vector = TeleportHelper.FindSafeTeleportDestination(safeWardController.transform.position, master.bodyPrefab.GetComponent<CharacterBody>(), RoR2Application.rng) ?? vector;
			}
			master.Respawn(vector, Quaternion.Euler(0f, UnityEngine.Random.Range(0f, 360f), 0f));
			CharacterBody body = master.GetBody();
			if ((bool)body)
			{
				body.AddTimedBuff(RoR2Content.Buffs.Immune, 3f);
				EntityStateMachine[] components = body.GetComponents<EntityStateMachine>();
				foreach (EntityStateMachine obj in components)
				{
					obj.initialStateType = obj.mainStateType;
				}
				if ((bool)playerRespawnEffectPrefab)
				{
					EffectManager.SpawnEffect(playerRespawnEffectPrefab, new EffectData
					{
						origin = vector,
						rotation = body.transform.rotation
					}, transmit: true);
				}
			}
		}
		if (IsStageTransitionWave())
		{
			PickNextStageSceneFromCurrentSceneDestinations();
			DirectorCore.instance.TrySpawnObject(new DirectorSpawnRequest(stageTransitionPortalCard, new DirectorPlacementRule
			{
				minDistance = 0f,
				maxDistance = stageTransitionPortalMaxDistance,
				placementMode = DirectorPlacementRule.PlacementMode.Approximate,
				position = safeWardController.transform.position,
				spawnOnTarget = safeWardController.transform
			}, safeWardRng));
			Chat.SendBroadcastChat(new Chat.SimpleChatMessage
			{
				baseToken = stageTransitionChatToken
			});
			if ((bool)safeWardController)
			{
				safeWardController.WaitForPortal();
			}
		}
	}

	protected override bool ShouldUpdateRunStopwatch()
	{
		return true;
	}

	protected override void OnSeedSet()
	{
		waveRng = new Xoroshiro128Plus(base.seed ^ 0x37E8);
		enemyItemRng = new Xoroshiro128Plus(base.seed ^ 0x5FF);
		safeWardRng = new Xoroshiro128Plus(base.seed ^ 0xBBF54);
	}

	private void CleanUpCurrentWave()
	{
		if ((bool)_waveController)
		{
			_waveController.onAllEnemiesDefeatedServer -= OnWaveAllEnemiesDefeatedServer;
			_waveController = null;
		}
		if ((bool)waveInstance)
		{
			UnityEngine.Object.Destroy(waveInstance);
		}
		NetworkwaveInstanceId = NetworkInstanceId.Invalid;
	}

	private void SpawnSafeWard(InteractableSpawnCard spawnCard, DirectorPlacementRule placementRule)
	{
		GameObject gameObject = DirectorCore.instance.TrySpawnObject(new DirectorSpawnRequest(spawnCard, placementRule, safeWardRng));
		if ((bool)gameObject)
		{
			NetworkServer.Spawn(gameObject);
			safeWardController = gameObject.GetComponent<InfiniteTowerSafeWardController>();
			if ((bool)safeWardController)
			{
				safeWardController.onActivated += OnSafeWardActivated;
			}
			HoldoutZoneController component = gameObject.GetComponent<HoldoutZoneController>();
			if ((bool)component)
			{
				component.calcAccumulatedCharge += CalcHoldoutZoneCharge;
			}
			if ((bool)fogDamageController)
			{
				fogDamageController.AddSafeZone(safeWardController.safeZone);
			}
		}
		else
		{
			Debug.LogError("Unable to spawn safe ward instance.  Are there any ground nodes?");
		}
	}

	private void CalcHoldoutZoneCharge(ref float charge)
	{
		if ((bool)waveController)
		{
			float num = waveController.GetNormalizedProgress();
			if (waveController.GetSquadCount() > 0)
			{
				num = Mathf.Min(num, 0.99f);
			}
			charge = num;
		}
		else
		{
			charge = 0f;
		}
	}

	[Server]
	public override void HandlePlayerFirstEntryAnimation(CharacterBody body, Vector3 spawnPosition, Quaternion spawnRotation)
	{
		if (!NetworkServer.active)
		{
			Debug.LogWarning("[Server] function 'System.Void RoR2.InfiniteTowerRun::HandlePlayerFirstEntryAnimation(RoR2.CharacterBody,UnityEngine.Vector3,UnityEngine.Quaternion)' called on client");
		}
		else
		{
			body.SetBodyStateToPreferredInitialState();
		}
	}

	private void PerformStageCleanUp()
	{
		safeWardController = null;
		if ((bool)fogDamageController)
		{
			UnityEngine.Object.Destroy(fogDamageController.gameObject);
		}
		CleanUpCurrentWave();
	}

	private void UNetVersion()
	{
	}

	public override bool OnSerialize(NetworkWriter writer, bool forceAll)
	{
		bool flag = base.OnSerialize(writer, forceAll);
		if (forceAll)
		{
			writer.WritePackedUInt32((uint)_waveIndex);
			writer.Write(waveInstanceId);
			return true;
		}
		bool flag2 = false;
		if ((base.syncVarDirtyBits & 0x40u) != 0)
		{
			if (!flag2)
			{
				writer.WritePackedUInt32(base.syncVarDirtyBits);
				flag2 = true;
			}
			writer.WritePackedUInt32((uint)_waveIndex);
		}
		if ((base.syncVarDirtyBits & 0x80u) != 0)
		{
			if (!flag2)
			{
				writer.WritePackedUInt32(base.syncVarDirtyBits);
				flag2 = true;
			}
			writer.Write(waveInstanceId);
		}
		if (!flag2)
		{
			writer.WritePackedUInt32(base.syncVarDirtyBits);
		}
		return flag2 || flag;
	}

	public override void OnDeserialize(NetworkReader reader, bool initialState)
	{
		base.OnDeserialize(reader, initialState);
		if (initialState)
		{
			_waveIndex = (int)reader.ReadPackedUInt32();
			waveInstanceId = reader.ReadNetworkId();
			return;
		}
		int num = (int)reader.ReadPackedUInt32();
		if (((uint)num & 0x40u) != 0)
		{
			_waveIndex = (int)reader.ReadPackedUInt32();
		}
		if (((uint)num & 0x80u) != 0)
		{
			waveInstanceId = reader.ReadNetworkId();
		}
	}

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