using System;
using System.Collections.Generic;
using EscapeRoomEngine.Engine.Runtime.Modules;
using EscapeRoomEngine.Engine.Runtime.Utilities;
using NaughtyAttributes;
using UnityEngine;

namespace EscapeRoomEngine.Portal.Runtime
{
    /// <summary>
    /// The portal is a specific type of <see cref="DoorState"/> for seamless transitions between spaces.
    /// </summary>
    public class Portal : DoorState
    {
        public static readonly Matrix4x4 HalfRotation = Matrix4x4.Rotate(Quaternion.Euler(0, 180, 0));
        
        /// <summary>
        /// The portal that is connected with this one.
        /// </summary>
        public Portal linkedPortal;
        /// <summary>
        /// The camera that will draw the view for this portal.
        /// </summary>
        [BoxGroup("Internal")] public PortalCamera portalCamera;
        /// <summary>
        /// The transform marking the edge of the portal plane.
        /// </summary>
        [BoxGroup("Internal")] public Transform portalTransform;

        /// <summary>
        /// Whether this portal is connected is determined by whether the reference to its linked portal is set.
        /// </summary>
        internal bool Connected => linkedPortal != null;
        internal readonly List<PortalDriver> closePortalDrivers = new();

        protected virtual void Awake()
        {
            DoorEvent += (_, type) =>
            {
                // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
                switch (type)
                {
                    case DoorEventType.Connected:
                        linkedPortal = FromDoorState(Module.ConnectedDoorState);
                        portalCamera.screen.gameObject.SetActive(true);
                        portalCamera.enabled = true;
                        break;
                    case DoorEventType.Locked:
                        portalCamera.enabled = false;
                        portalCamera.screen.gameObject.SetActive(false);
                        linkedPortal = null;
                        break;
                }
            };
        }

        private void FixedUpdate()
        {
            if (Connected)
            {
                for (var i = 0; i < closePortalDrivers.Count; i++)
                {
                    var portalDriver = closePortalDrivers[i];
                    if (portalDriver.entrySide < 0 && CalculateSide(portalDriver.transform) >= 0) // must have entered from the front and exited the back
                    {
                        StopTrackingDriver(portalDriver);
                        linkedPortal.StartTrackingDriver(portalDriver, -1);
                        portalDriver.Teleport(this, linkedPortal);
                        if (portalDriver.player)
                        {
                            linkedPortal.ExitFrom();
                        }
                        i--; // decrease the loop counter because the list is one element smaller now
                    }
                }
            }
        }

        /// <summary>
        /// Begin tracking a portal driver that came close to this portal and might need to be teleported.
        /// </summary>
        internal void StartTrackingDriver(PortalDriver portalDriver, int entrySide)
        {
            closePortalDrivers.Add(portalDriver);
            portalDriver.EnableClone(linkedPortal);
            portalDriver.entrySide = entrySide;
        }

        /// <summary>
        /// End tracking a portal driver that distanced itself from this portal or was teleported to the linked portal.
        /// </summary>
        internal void StopTrackingDriver(PortalDriver portalDriver)
        {
            closePortalDrivers.Remove(portalDriver);
            portalDriver.DisableClone(linkedPortal);
        }

        /// <summary>
        /// Calculate which side of the portal plane a transform is.
        /// </summary>
        internal int CalculateSide(Transform portalDriverTransform)
        {
            return Math.Sign(Vector3.Dot(portalTransform.forward, portalDriverTransform.position - portalTransform.position));
        }

        private static Portal FromDoorState(DoorState state)
        {
            if (state is Portal portal)
            {
                return portal;
            }
            
            throw new WrongTypeException(typeof(Portal), state.GetType(), typeof(DoorState));
        }
    }
}
