using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;

namespace Barotrauma
{
    class CameraTransition
    {
        private static List<CameraTransition> activeTransitions = new List<CameraTransition>();

        public bool Running
        {
            get;
            private set;
        }

        public Camera AssignedCamera;
        private readonly Alignment? cameraStartPos;
        private readonly Alignment? cameraEndPos;
        private readonly float? startZoom;
        private readonly float? endZoom;

        public readonly float WaitDuration;
        public float EndWaitDuration = 0.1f;
        public readonly float PanDuration;
        public readonly bool FadeOut;
        public readonly bool LosFadeIn;

        private readonly CoroutineHandle updateCoroutine;

        private Character prevControlled;

        public bool AllowInterrupt = false;
        public bool RemoveControlFromCharacter = true;

        public bool RunWhilePaused = true;
        
        public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, bool losFadeIn = false, float waitDuration = 0f, float panDuration = 10.0f, float? startZoom = null, float? endZoom = null)
        {
            WaitDuration = waitDuration;
            PanDuration = panDuration;
            FadeOut = fadeOut;
            LosFadeIn = losFadeIn;
            this.cameraStartPos = cameraStartPos;
            this.cameraEndPos = cameraEndPos;
            this.startZoom = startZoom;
            this.endZoom = endZoom;
            AssignedCamera = cam;

            if (targetEntity == null) { return; }

            Running = true;

            prevControlled = Character.Controlled;
            activeTransitions.RemoveAll(a => !CoroutineManager.IsCoroutineRunning(a.updateCoroutine));
            foreach (var activeTransition in activeTransitions)
            {
                if (activeTransition.prevControlled != null)
                {
                    prevControlled ??= activeTransition.prevControlled;
                }
                activeTransition.Stop();
            }
            updateCoroutine = CoroutineManager.StartCoroutine(Update(targetEntity, cam), "CameraTransition");
            activeTransitions.Add(this);
        }

        public void Stop()
        {
            CoroutineManager.StopCoroutines(updateCoroutine);
            Running = false;
#if CLIENT
            if (FadeOut) { GUI.ScreenOverlayColor = Color.TransparentBlack; }
            if (prevControlled != null && !prevControlled.Removed)
            {
                Character.Controlled = prevControlled;
            }
#endif
        }

        private float DeltaTime => CoroutineManager.Paused && !RunWhilePaused ? 0 : CoroutineManager.DeltaTime;

        private IEnumerable<CoroutineStatus> Update(ISpatialEntity targetEntity, Camera cam)
        {
            if (targetEntity == null || (targetEntity is Entity e && e.Removed)) { yield return CoroutineStatus.Success; }

            prevControlled ??= Character.Controlled;
            if (RemoveControlFromCharacter)
            {
#if CLIENT
                GameMain.LightManager.LosEnabled = false;
#endif
                Character.Controlled = null;
            }
            cam.TargetPos = Vector2.Zero;

            float startZoom = this.startZoom ?? cam.Zoom;
            float endZoom = this.endZoom ?? 0.5f;
            Vector2 initialCameraPos = cam.Position;
            Vector2? initialTargetPos = targetEntity?.WorldPosition;
            Vector2 endPos = cam.Position;

            float timer = -WaitDuration;

            while (timer < PanDuration)
            {
                float clampedTimer = Math.Max(timer, 0f);

                if (Screen.Selected != GameMain.GameScreen)
                {
                    yield return new WaitForSeconds(0.1f);
#if CLIENT
                    if (FadeOut) { GUI.ScreenOverlayColor = Color.TransparentBlack; }
#endif
                    Running = false;
                    yield return CoroutineStatus.Success;
                }

                //switched control to some other character during the transition -> remove control again
                if (Character.Controlled != null)
                {
                    prevControlled = Character.Controlled;
                    if (RemoveControlFromCharacter)
                    {
#if CLIENT
                        GameMain.LightManager.LosEnabled = false;
#endif
                        Character.Controlled = null;
                    }
                }

                if (prevControlled != null && prevControlled.Removed)
                {
                    prevControlled = null;
                }
#if CLIENT
                if (AllowInterrupt && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
                {
                    break;
                }
#endif
                Vector2 minPos = targetEntity.WorldPosition;
                Vector2 maxPos = targetEntity.WorldPosition;
                if (targetEntity is Submarine sub)
                {
                    minPos = new Vector2(sub.WorldPosition.X - sub.Borders.Width / 2, sub.WorldPosition.Y - sub.Borders.Height / 2);
                    maxPos = new Vector2(sub.WorldPosition.X + sub.Borders.Width / 2, sub.WorldPosition.Y + sub.Borders.Height / 2);
                }

                Vector2 startPos = cameraStartPos.HasValue ?
                    new Vector2(
                        MathHelper.Lerp(minPos.X, maxPos.X, (cameraStartPos.Value.ToVector2().X + 1.0f) / 2.0f),
                        MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraStartPos.Value.ToVector2().Y + 1.0f) / 2.0f)) :
                    initialCameraPos;
                if (!cameraStartPos.HasValue && initialTargetPos.HasValue)
                {
                    startPos += targetEntity.WorldPosition - initialTargetPos.Value;
                }
                endPos = cameraEndPos.HasValue ?                    
                    new Vector2(
                        MathHelper.Lerp(minPos.X, maxPos.X, (cameraEndPos.Value.ToVector2().X + 1.0f) / 2.0f),
                        MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraEndPos.Value.ToVector2().Y + 1.0f) / 2.0f)) :
                    prevControlled?.WorldPosition ?? targetEntity.WorldPosition;

                Vector2 cameraPos = Vector2.SmoothStep(startPos, endPos, clampedTimer / PanDuration) + cam.ShakePosition;
                cam.Translate(cameraPos - cam.Position);
                
#if CLIENT
                cam.Zoom = MathHelper.SmoothStep(startZoom, endZoom, clampedTimer / PanDuration);
                if (clampedTimer / PanDuration > 0.9f)
                {
                    if (FadeOut) { GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((clampedTimer / PanDuration) - 0.9f) * 10.0f); }
                }
                if (LosFadeIn && clampedTimer / PanDuration > 0.8f)
                {
                    if (!GameMain.DevMode)
                    {
                        GameMain.LightManager.LosEnabled = true;
                        GameMain.LightManager.LosAlpha = ((clampedTimer / PanDuration) - 0.8f) * 5.0f;
                    }
                    Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity);
                }
#endif
                timer += DeltaTime;

                yield return CoroutineStatus.Running;
            }

            float endTimer = 0.0f;
            while (endTimer <= EndWaitDuration)
            {
                cam.Translate(endPos - cam.Position);
                cam.Zoom = endZoom;
                endTimer += DeltaTime;
                yield return CoroutineStatus.Running;
            }

            Running = false;

#if CLIENT
            GUI.ScreenOverlayColor = Color.TransparentBlack;
            if (!GameMain.DevMode)
            {
                GameMain.LightManager.LosEnabled = true;
                GameMain.LightManager.LosAlpha = 1f;
            }
#endif

            if (prevControlled != null && !prevControlled.Removed)
            {
                Character.Controlled = prevControlled;
            }

            yield return CoroutineStatus.Success;
        }
    }
}
