using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;


using BigSock.UI;
using BigSock.Item;
using BigSock.Service;


namespace BigSock {

	// Takes and handles input and movement for a player character
	public partial class PlayerController : Character
	{

		public const int SKILL_POINTS_PR_LVL = 3; // Skill points to gain pr level up.
		public const int SKILL_POINTS_START = 0; // Skill points to start with.
		public const float XP_SCALE_RATE = 3.0f; // Multiplier for xp gain, helps test system while game is tiny.


		public UtilBar utilBar;
		

		public float collisionOffset = 0.05f;
		public ContactFilter2D movementFilter;
		
		public GameObject attack;

		Vector2 movementInput;
		SpriteRenderer spriteRenderer;
		//Rigidbody2D rb;
		Animator animator;
		List<RaycastHit2D> castCollisions = new List<RaycastHit2D>();

		bool canMove = true;


		// The abilities of the player.
		private List<AbilityEntity> _abilities = new List<AbilityEntity>();

		// A list of the keycodes that correspond to each of the player's abilities.
		private List<List<KeyCode>> _keyMapping = new List<List<KeyCode>> {
			new List<KeyCode>{KeyCode.Space, KeyCode.Mouse0},
			new List<KeyCode>{KeyCode.Z},
			new List<KeyCode>{KeyCode.C},
			new List<KeyCode>{KeyCode.LeftShift},
		};

		//protected IAttack _testAttack;
		//protected IAttack _testAttack2;
		//protected IAttack _testAttack3;
		//protected IAbility _dodge;

		public DateTime NextTimeCanAttack { get; private set; } = DateTime.Now;


		public PlayerController()
		{
			TryPickUpItem(ItemService.SINGLETON.Get(201));
			TryPickUpItem(ItemService.SINGLETON.Get(105));
			TryPickUpItem(ItemService.SINGLETON.Get(202));
			TryPickUpItem(ItemService.SINGLETON.Get(101));
		}

		/*
			Updates the list of the user's abilities. 
		*/
		protected void SetAbilities(List<IAbility> neo) {
			var res = new List<AbilityEntity>();
			for(int i = 0; i < neo.Count; ++i) 
				res.Add(new AbilityEntity(neo[i], i, _keyMapping[i]));
			
			_abilities = res;

			//!! Put in updating he ui here.
			foreach(var ability in _abilities) {
				// AbilityUI.SetAbility(ability.Index, ability); ???
			}
		}

		// Start is called before the first frame update 
		protected override void Start()
		{
			base.Start();



			utilBar?.WithXP((int)xp, (int)maxXp)
				?.WithHealth(Convert.ToInt32(HP), Convert.ToInt32(MaxHP));

			animator = GetComponent<Animator>();
			spriteRenderer = GetComponent<SpriteRenderer>();



			//!! DEBUG: Add item to player at start to test if it works.
			/*
			TryPickUpItem(ItemService.SINGLETON.Get(201));
			TryPickUpItem(ItemService.SINGLETON.Get(201));
			TryPickUpItem(ItemService.SINGLETON.Get(202));
			TryPickUpItem(ItemService.SINGLETON.Get(101));
			*/
			//var tmp = PrefabService.SINGLETON;
			//var tmp = SpriteService.SINGLETON;


			// Get the abilities and set them up.
			var aService = AbilityService.SINGLETON;
			var abilities = new List<IAbility> {
				aService.Get(104),
				aService.Get(102),
				aService.Get(101),
				aService.Get(201),
			};
			SetAbilities(abilities);

			//_testAttack = (IAttack)AbilityService.SINGLETON.Get(104);
			//_testAttack2 = (IAttack)AbilityService.SINGLETON.Get(102);
			//_testAttack3 = (IAttack)AbilityService.SINGLETON.Get(101);
			//_dodge = AbilityService.SINGLETON.Get(201);

			_ = AudioService.SINGLETON;
			_ = SpriteService.SINGLETON;
			_ = PrefabService.SINGLETON;
		}


		
		private void FixedUpdate() {
			if(canMove) {
					// If movement input is not 0, try to move
					if(movementInput != Vector2.zero){
							
							bool success = TryMove(movementInput);

							if(!success) {
									success = TryMove(new Vector2(movementInput.x, 0));
							}

							if(!success) {
									success = TryMove(new Vector2(0, movementInput.y));
							}
							
							animator.SetBool("isMoving", success);
					} else {
							animator.SetBool("isMoving", false);
					}

					// Set direction of sprite to movement direction
					var mouse = Camera.main.ScreenToWorldPoint(Input.mousePosition);
					var pos = transform.position;
					var temp = (mouse - pos);
					//temp.z = 0;
					//direction = temp.normalized;

					if(temp.x < 0) {
							spriteRenderer.flipX = true;
					} else if (temp.x > 0) {
							spriteRenderer.flipX = false;
					}
			}
			

		}

		// Dictionary that holds start times for charging abilities.
		//Dictionary<KeyCode, float> chargeStarts = new Dictionary<KeyCode, float>();

		/*
			Triggers an ability if it should be.
				Need to add support for mouse buttons n shiet
		*/
		private void CheckAbilityInput(AbilityEntity ability) {
			var par = GetAbilityParam(Camera.main.ScreenToWorldPoint(Input.mousePosition));

			// Check input on each key, stop if one was a success.
			foreach(var key in ability.Keys)
				if(CheckAbilityInput(key, ability)) break;

			// Update the UI.
			//> AbilityUI?.UpdateCharge(ability.Index, ability.ChargePercent);
			//> AbilityUI?.UpdateCooldown(ability.Index, ability.CooldownPercent);

		}

		private bool CheckAbilityInput(KeyCode key, AbilityEntity ability) {
			var par = GetAbilityParam(Camera.main.ScreenToWorldPoint(Input.mousePosition));

			switch (ability.Ability.FireType) {
				// Standard: Press to fire.
				case FireType.Standard:
					if (Input.GetKeyDown(key)) { 
						ability.Ability.Use(par);
						return true;
					}
					break;
				// FullAuto: Keep firing while key is down.
				case FireType.FullAuto:
					if (Input.GetKey(key)) {
						ability.Ability.Use(par);
						return true;
					}
					break;
				// Charge: Fire when let go.
				case FireType.Charge:
					// If pressed down: Store start time.
					if (Input.GetKeyDown(key)) {
						ability.ChargeStarted = Time.time;
						return true;
					}
					// If let go: Activate
					else if (Input.GetKeyUp(key)) {
						par.ChargeTime = ability.ChargeTime;
						var t = ability.Ability.Use(par);
						if (!t) {
							if (par.ChargeTime < ability.Ability.MinCharge)
								Debug.Log($"[PlayerController.CheckAbilityInput({key})] {ability.Ability.Name} not fired ({par.ChargeTime:N3} < {ability.Ability.MinCharge:N3})");
						}
						return true;
					}
					break;
				default:
					break;
			}
			return false;
		}


		private void Update()
		{
			// Regenerate mana & stamina.
			Regenerate();

			// Object w/ parameters for abilities.
			//var par = new AbilityParam{
			//	Actor = this,
			//	TargetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition),
			//	MovementDir = moveDir,
			//};


			//// If pressed Soace or LMB: Regular attack.
			//if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButton(0)) {
			//	_testAttack.Use(par);
			//}

			// Check and update all abilities.
			foreach(var ability in _abilities) {
				CheckAbilityInput(ability);
			}
			// Check ability 1.
			//CheckAbilityInput(KeyCode.Space, _testAttack);
			//CheckAbilityInput(KeyCode.Mouse0, _testAttack);
			// Check ability 2.
			//CheckAbilityInput(KeyCode.Z, _testAttack2);
			// Check ability 3.
			//CheckAbilityInput(KeyCode.LeftShift, _dodge);
			//CheckAbilityInput(KeyCode.C, _testAttack3);


			//if(Input.GetKeyDown(KeyCode.Z)) Debug.Log($"[PlayerController.Update()] Z was pressed.");
			//if(Input.GetKeyUp(KeyCode.Z)) Debug.Log($"[PlayerController.Update()] Z was let go.");
			//if(Input.GetKeyUp(KeyCode.UpArrow)) Debug.Log($"[PlayerController.Update()] UpArrow was let go.");
			//if(Input.GetKeyUp(KeyCode.Space)) Debug.Log($"[PlayerController.Update()] Space was let go.");

			//// If pressed Z: Big attack.
			//if(Input.GetKey(KeyCode.Z)) {
			//	_testAttack2.Use(par);
			//}

			//// If pressed X: dodge.
			//if(Input.GetKey(KeyCode.X)) {
			//	_dodge.Use(par);
			//}



			//!! Code for testing the new item stuff.
			if (Input.GetKeyDown(KeyCode.M))
			{
				var item = ItemService.SINGLETON.GetRandom(); // new ItemRunningShoes();
				TryPickUpItem(item);
			}

			// Code for opening the menu.
			if (Input.GetKeyDown(KeyCode.I))
			{
				GameObject canvas = GameObject.Find("Canvas");
				GameObject oldPlayerMenu = GameObject.Find("PlayerMenu(Clone)");
				if (canvas != null && oldPlayerMenu == null)
				{
					var playerMenu = PrefabService.SINGLETON.Instance("UI/PlayerMenu", canvas.transform);
					var invMen = playerMenu.transform.GetChild(1).transform.GetChild(0).GetComponent<InventoryPanel>();
					var statMen = playerMenu.transform.GetChild(1).transform.GetChild(1).GetComponent<StatPanel>();
					invMen.player = this;
					statMen.player = this;
				}
			}

			// Code for opening control scheme
			if (Input.GetKeyDown(KeyCode.L))
			{
				GameObject canvas = GameObject.Find("Canvas");
				GameObject oldControlScheme = GameObject.Find("ControlScheme(Clone)");
				if (canvas != null && oldControlScheme == null)
				{
					var controlScheme = PrefabService.SINGLETON.Instance("UI/ControlScheme", canvas.transform);
				}
			}
		}

		/*
			Updates the modifiers to the character's stats.
		*/
		public override void UpdateModifiers(ICharacterStats modifiers = null) {
			base.UpdateModifiers(modifiers);

			utilBar?.WithMana(maxValue: (int)Stats.MaxMana)
				?.WithStamina(maxValue: (int)Stats.MaxStamina);
		}

			
		/*
			Regenerates mana and stamina.
		*/
		protected override void Regenerate() {
			base.Regenerate();
			utilBar?.WithMana(value: (int)Mana)
				?.WithStamina(value: (int)Stamina);
		}

		private bool TryMove_OLD(Vector2 direction) {
				if(direction != Vector2.zero) {
						// Check for potential collisions
						int count = rb.Cast(
								direction, // X and Y values between -1 and 1 that represent the direction from the body to look for collisions
								movementFilter, // The settings that determine where a collision can occur on such as layers to collide with
								castCollisions, // List of collisions to store the found collisions into after the Cast is finished
								(float) MovementSpeed * Time.fixedDeltaTime + collisionOffset); // The amount to cast equal to the movement plus an offset

						if(count == 0){
								rb.MovePosition(rb.position + direction * (float) MovementSpeed * Time.fixedDeltaTime);
								return true;
						} else {
								return false;
						}
				} else {
						// Can't move if there's no direction to move in
						return false;
				}
		
				// 
		}

		void OnMove(InputValue movementValue) {
				movementInput = movementValue.Get<Vector2>();
		}


		public void LockMovement() {
				canMove = false;
		}

		public void UnlockMovement() {
				canMove = true;
		}

		/*
		Method for what to do when the character takes damage.
		*/
		protected override void AfterDamage(IAttackStats attack)  {
			base.AfterDamage(attack);
			utilBar?.WithHealth(Convert.ToInt32(HP));
		}

		public void GainXp(float xp) {
			GiveXp(xp * XP_SCALE_RATE);
			CheckXp();
			utilBar?.WithXP((int)this.xp);
		}


		/*
			Checks for, and handles, level-ups.
		*/
		private void CheckXp() {
			// If the character has leveled up.
			if(xp > maxXp) {
				level += 1; 
				xp -= maxXp;
				maxXp = (level + 1) * 100;
				utilBar?.WithXP(maxValue: (int)maxXp);

				SkillPoints += SKILL_POINTS_PR_LVL;
			}
			/*
				To-do:
					- Maybe not flat skill points pr level.
					- Scale differently. (Separate function for getting xp requirement pr level)
			*/
		}

		public void OnDestroy()
		{
			Time.timeScale = 0;
			GameObject canvas = GameObject.Find("Canvas");
			
			if (canvas != null)
			{
				PrefabService.SINGLETON.Instance("UI/EndScreen", canvas.transform);
			}
		}
	}


	/*
		Skills related code.
	*/
	public partial class PlayerController {
		

		/*
			The number of skill points the user has at their disposal.
		*/
		public int SkillPoints { get; set; } = SKILL_POINTS_START;

		/*
			The amount of points the user has currently spent on each skill.
		*/
		public Skills Skills { get; set; } = new Skills();

		/*
			The maximum amount of points the user can spend on each skill.
		*/
		public Skills MaxSkills { get; set; } = new Skills{
			HP            = 10,
			SP            = 10,
			MP            = 10,
			Damage        = 10,
			Speed         = 10,
			Luck          = 10,
			Concentration = 10,
		};

		/*
			How much a stat will increas each skill point.
		*/
		public CharacterStats StatIncreasePrSkillLevel { get; protected set; } = new CharacterStats {
			//HP
			MaxHP = 3f,
			// MP
			MaxMana = 2f,
			RegenMana = 0.1f,
			// SP
			MaxStamina = 2f,
			RegenStamina = 0.1f,
			// Damage
			Damage = 0.1f,
			Knockback = 0.1f,
			// Speed
			MoveSpeed = 0.1f,
			AttackSpeed = 0.1f,
			// Luck
			CritChance = 0.05f,
			CritDamageModifier = 0.1f,
			// Concentration
			Range = 0.1f,
			ProjectileSpeed = 0.1f,
			//-- Accuracy
			Accuracy = 0.1f,

		};




	}


}