using System.Collections;
using System.Collections.Generic;
using KinematicCharacterController;
using UnityEngine;
using DG.Tweening;
using UnityEngine.SceneManagement;

[RequireComponent(typeof(KinematicCharacterMotor))]
public class PlayerController : MonoBehaviour, ICharacterController {
  public Sprite crystalIcon;

  [Header("Movement settings")]
  public float groundSpeed = 5f;
  public float accel = 100f;
  public float turnSpeed = 250f;

  [Header("Animation details")]
  public float minIdleTurnTime = 2f;
  public float maxIdleTurnTime = 5f;

  [Header("Commands")]
  public float commandFreezeTime = 1f;
  public AudioClip commandComeClip;
  public AudioClip commandStayClip;

  [Header("References")]
  public FootstepPlayer footstepPlayer;
  public Animator animator;
  public GameObject heldCrystal;

  // Readable state
  public bool IsOffering { get; private set; }

  public bool Frozen { get; set; }

  private Flags flags;
  private KinematicCharacterMotor motor;
  private Rewired.Player rwplayer;

  private GolemController golem;

  private Vector2 frameMoveInput;
  private Vector3 frameMoveVector;
  private Vector3? forceImpulse = null;

  private bool forceDeath;

  private float commandActiveTimer;
  private float idleTurnTimer;
  private float forceDeathTimer;

  private void Awake() {
    motor = GetComponent<KinematicCharacterMotor>();
    motor.CharacterController = this;
    rwplayer = Rewired.ReInput.players.GetPlayer(0);
    flags = Flags.Instance;
    golem = GameObject.FindGameObjectWithTag("Golem").GetComponent<GolemController>();

    ResetIdleTurnTimer();
  }

  private void Update() {
    var command = GetCommandForFrame();

    var canMove = command < 1 && commandActiveTimer < 0f && !Frozen;

    frameMoveInput = canMove ? new Vector2(
      rwplayer.GetAxis("Move horizontal"),
      rwplayer.GetAxis("Move vertical")
    ) : Vector2.zero;

    // Clamp input range.
    var inputVector = Vector3.ClampMagnitude(
      new Vector3(frameMoveInput.x, 0f, frameMoveInput.y), 1f);

    // Align the movement vector with the camera forward.
    var camForwardLook = Quaternion.LookRotation(Camera.main.transform.forward);
    frameMoveVector = camForwardLook * inputVector;

    // Check timers.
    commandActiveTimer -= Time.deltaTime;
    idleTurnTimer -= Time.deltaTime;
    forceDeathTimer -= Time.deltaTime;
    if (idleTurnTimer < 0f) {
      ResetIdleTurnTimer();
    }

    // Check interaction buttons
    IsOffering = rwplayer.GetButton("Offer") && frameMoveInput.magnitude == 0f;

    if (command > 0 && commandActiveTimer < 0f && command < 3) {
      SayCommand(command);
    }

    // Update visual state.
    heldCrystal.SetActive(flags.HoldingRedCrystal);
  }

  private void ResetIdleTurnTimer() {
    idleTurnTimer = Random.Range(minIdleTurnTime, maxIdleTurnTime);
  }

  // 0 means no command.
  private int GetCommandForFrame() {
    for (int i = 1; i < 4; i++) {
      var action = $"Command{i}";
      if (rwplayer.GetButtonDown(action) && flags.KnowsCommand(i)) {
        return i;
      }
    }
    return 0;
  }

  private void SayCommand(int id) {
    commandActiveTimer = commandFreezeTime;
    animator.SetTrigger("command");
    var clip = id == 2 ? commandStayClip : commandComeClip;
    footstepPlayer.source.PlayOneShot(clip);
    golem.TryHearingCommand(id);
  }


  /// Game events

  public void HandleSmashed(SmashingRock rock) {
    Debug.Log("Smashed by " + rock.gameObject.name);
    forceImpulse = rock.transform.forward * 50f;
  }

  public void HandlePickup(Pickup pickup) {
    if (!pickup.Infinite) {
      GameObject.Destroy(pickup.gameObject);
    }
    flags.HoldingRedCrystal = true;
    if (!flags.FoundAnyRedCrystal) {
      UI.Instance.ShowTutorial("Red Crystal", Messages.RedCrystal, crystalIcon);
    }
    flags.FoundAnyRedCrystal = true;

    AudioController.Instance.PlaySuccess1();
  }

  public void HandleSwitch(Switch sw) {
    footstepPlayer.surfaceFlag = 1;
    if (sw.isHeavySwitch && !sw.WasActivated) {
      UI.Instance.ShowInfo(Messages.HeavySwitch);
    } else {
      sw.Press();
    }
  }

  public void HandleLeaveSwitch(Switch sw) {
    footstepPlayer.surfaceFlag = 0;
    if (!sw.isHeavySwitch) {
      sw.Unpress();
    }
  }


  /// Kinematic controller interface

  public void UpdateRotation(ref Quaternion currentRotation, float deltaTime) {
    if (frameMoveVector.sqrMagnitude > 0f) {
      var targetRotation = Quaternion.LookRotation(
          new Vector3(frameMoveVector.x, 0, frameMoveVector.z));
      currentRotation = Quaternion.Slerp(currentRotation, targetRotation, turnSpeed * Time.deltaTime);
    }
  }

  public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime) {
    if (forceImpulse.HasValue) {
      motor.ForceUnground();
      currentVelocity += forceImpulse.Value;
      Frozen = true;
      forceImpulse = null;
      UI.Instance.FadeOutScene().OnComplete(
          () => SceneManager.LoadScene(SceneManager.GetActiveScene().name));
    } else if (motor.GroundingStatus.IsStableOnGround) {
      // Project on surface.
      var surfaceNormal = motor.GroundingStatus.GroundNormal;
      var moveRight = Vector3.Cross(frameMoveVector, motor.CharacterUp);
      var surfaceMoveVector = Vector3.Cross(surfaceNormal, moveRight);

      // Compute final velocity.
      var targetVelocity = surfaceMoveVector * groundSpeed;
      currentVelocity = Vector3.Lerp(currentVelocity, targetVelocity, 1f - Mathf.Exp(-accel * deltaTime));
    } else {
      // Apply air movement.
      // if (frameMoveVector.sqrMagnitude > 0f) {
      //   var targetVelocity = frameMoveVector * groundSpeed;
      //   var diff = Vector3.ProjectOnPlane(targetVelocity - currentVelocity, Physics.gravity);
      //   currentVelocity += diff * 1f * deltaTime;
      // }

      // Apply gravity.
      currentVelocity += Physics.gravity * Time.deltaTime * 10f;
    }

    // Update animation state.
    animator.SetFloat("speed", currentVelocity.magnitude);
    animator.SetFloat("turnTimer", idleTurnTimer);
    animator.SetBool("isOffering", IsOffering);
  }

  public void AfterCharacterUpdate(float deltaTime) { }

  public bool IsColliderValidForCollisions(Collider coll) {
    return true;
  }

  /// Unused kinematic controller methods.

  public void BeforeCharacterUpdate(float deltaTime) { }
  public void OnDiscreteCollisionDetected(Collider hitCollider) { }
  public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport) { }
  public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport) { }
  public void PostGroundingUpdate(float deltaTime) { }
  public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport) { }
}
