using System;
using System.Collections;
using UnityEngine;

public sealed class HyperJumpView : MonoBehaviour
{
    [SerializeField]
    private OrbitalCameraControl orbitalCamera = null;

    [SerializeField]
    private float defaultCameraFoW = 60;
    [SerializeField]
    private float jumpCameraFoW = 45;
    [SerializeField]
    private float jumpCameraDistance = 5f;
    [SerializeField]
    private float shipDistance = 60;
    [SerializeField]
    private float shipSpeed = 1.5f;
    [SerializeField]
    private float rotationTime = 0.6f;

    public void ShowHyperJumpPhase(StarShip starShip, ShipView shipView, HyperJumpPhase jump, NavPointSpaceView fromView, NavPointSpaceView endView, PlanetsView planetsView, Action callback)
    {
        StartCoroutine(MoveShip(starShip, shipView, fromView, endView, planetsView, jump.From.SystemPostition, jump.To.SystemPostition, callback));
    }

    private IEnumerator MoveShip(StarShip ship, ShipView shipView, NavPointSpaceView fromView, NavPointSpaceView toView, PlanetsView planetsView, Vector3 startSystemPosition, Vector3 endSystemPosition, Action callback)
    {
        const float systemScale = 10000f;

        var flyDynamics = AnimationCurve.EaseInOut(0, 0, 1, 1);
        var rotationDynamics = AnimationCurve.EaseInOut(0, 0, 1, 1);
        var systemDelta = startSystemPosition - endSystemPosition;
        var direction = systemDelta.normalized;
        var systemDistance = systemDelta.magnitude;
        float navPointDistance = systemDistance * systemScale;

        var startShipRotation = ship.Rotation;
        var endShipRotation = Quaternion.LookRotation(direction, Vector3.up);
        ship.Rotation = endShipRotation;

        var startShipPosition = ship.Position;
        var endShipPosition = direction * shipDistance;
        ship.Position = endShipPosition;

        var prevStart = new Vector3(0, 0, 0);
        var prevEnd = direction * navPointDistance;

        var nextStart = -direction * navPointDistance;
        var nextEnd = new Vector3(0, 0, 0);

        var startPlanetsPosition = -startSystemPosition * systemScale;
        var endPlanetsPosition = -endSystemPosition * systemScale;

        var startCameraAngles = Quaternion.Euler(orbitalCamera.Euler);
        var endCameraAngles = Quaternion.LookRotation(-direction) * Quaternion.Euler(15, (UnityEngine.Random.Range(0, 2) * 2 - 1) * 10f, 0);

        fromView.transform.position = prevStart;
        toView.transform.position = nextStart;
        shipView.transform.position = startShipPosition;
        shipView.transform.rotation = startShipRotation;
        planetsView.transform.localPosition = startPlanetsPosition;
        orbitalCamera.SetRotation(startCameraAngles);

        // rotate ship and camera
        orbitalCamera.LockControl();
        var rotationTimer = rotationTime;
        while (rotationTimer > 0)
        {
            yield return new WaitForEndOfFrame();
            rotationTimer -= Time.deltaTime;
            var animation = rotationDynamics.Evaluate(rotationTimer / rotationTime);
            shipView.transform.rotation = Quaternion.Lerp(endShipRotation, startShipRotation, animation);
            var cameraRotation = Quaternion.Lerp(endCameraAngles, startCameraAngles, animation);
            orbitalCamera.SetRotation(cameraRotation);
        }
        shipView.transform.rotation = endShipRotation;
        orbitalCamera.SetRotation(endCameraAngles);
        // prepare fow effect
        var cameraOriginalDistance = orbitalCamera.Distance;
        var jumpCameraDistance = this.jumpCameraDistance;// cameraOriginalDistance * jumpCameraScale;
        const float preparationTime = 0.6f;
        var prepareTimer = preparationTime;
        while (prepareTimer > 0)
        {
            yield return new WaitForEndOfFrame();
            prepareTimer -= Time.deltaTime;
            var animation = rotationDynamics.Evaluate(prepareTimer / preparationTime);
            orbitalCamera.SetFieldOfView(Mathf.Lerp(jumpCameraFoW, defaultCameraFoW, animation));
            orbitalCamera.Distance = Mathf.Lerp(jumpCameraDistance, cameraOriginalDistance, animation);
        }
        orbitalCamera.SetFieldOfView(jumpCameraFoW);
        orbitalCamera.Distance = jumpCameraDistance;

        orbitalCamera.LockControl(false);
        shipView.ShowMoveEffect(true);
        var flyTime = 1f + systemDistance / shipSpeed;
        var timer = flyTime;
        const float hideEffectTime = 0.8f;
        const float fowEffectTime = 0.6f;
        // move
        while (timer > 0)
        {
            yield return new WaitForEndOfFrame();
            timer -= Time.deltaTime;
            var animation = flyDynamics.Evaluate(timer / flyTime);
            fromView.transform.position = Vector3.Lerp(prevEnd, prevStart, animation);
            toView.transform.position = Vector3.Lerp(nextEnd, nextStart, animation);
            planetsView.transform.localPosition = Vector3.Lerp(endPlanetsPosition, startPlanetsPosition, animation);
            var shipPosition = Vector3.Lerp(endShipPosition, startShipPosition, animation);
            shipView.transform.position = shipPosition;
            orbitalCamera.SetPosition(shipPosition);
            if (timer < hideEffectTime)
                shipView.ShowMoveEffect(false);
            orbitalCamera.SetFieldOfView(Mathf.Lerp(defaultCameraFoW, jumpCameraFoW, timer / fowEffectTime));
            orbitalCamera.Distance = Mathf.Lerp(cameraOriginalDistance, jumpCameraDistance, timer / fowEffectTime);
        }

        orbitalCamera.SetFieldOfView(defaultCameraFoW);
        shipView.ShowMoveEffect(false);
        fromView.transform.position = prevEnd;
        toView.transform.position = nextEnd;
        planetsView.transform.localPosition = endPlanetsPosition;
        shipView.transform.position = endShipPosition;
        shipView.transform.rotation = endShipRotation;
        orbitalCamera.SetPosition(endShipPosition);
        orbitalCamera.Distance = cameraOriginalDistance;
        callback?.Invoke();
    }
}
