﻿using System.Collections.Generic;

using UnityEngine;
using UnityEngine.Events;

using Photon.Pun;

using BR.BattleRoyale.Spells;
using UPG.Extensions;
using UPG.Debugging;

namespace BR.BattleRoyale.Building
{
    public class MagicWall : MonoBehaviourPun, IHittable
    {
        #region STATIC FIELDS
        public static List<MagicWall> ActiveWalls = new List<MagicWall>();
        public static List<MagicWall> ActiveFloors = new List<MagicWall>();
        public static List<MagicWall> ActiveRamps = new List<MagicWall>();

        static Material s_magicWallMat;
        #endregion

        #region STATIC METHODS
        public static bool CheckPositionOccupied(Vector3 _position, WallType _type, WallOrientation _orientation = WallOrientation.North)
        {
            Vector3Int _coordinate = ToCoordinate(_position);

            switch (_type)
            {
                case WallType.Wall:
                    return ActiveWalls.Find(x => (x.Coordinate == _coordinate && x.Orientation == _orientation) || (x.Coordinate == _coordinate + OffsetByOrientation(_orientation) && x.Orientation == InvertOrientation(_orientation)));
                case WallType.Ramp:
                    return ActiveRamps.Find(x => x.Coordinate == _coordinate);
                case WallType.Floor:
                    return ActiveFloors.Find(x => x.Coordinate == _coordinate);
                default:
                    return false;
            }
        }
        public static Vector3Int OffsetByOrientation(WallOrientation _orientation)
        {
            switch (_orientation)
            {
                case WallOrientation.North:
                    return new Vector3Int(0, 0, 1);
                case WallOrientation.East:
                    return new Vector3Int(1, 0, 0);
                case WallOrientation.South:
                    return new Vector3Int(0, 0, -1);
                case WallOrientation.West:
                    return new Vector3Int(-1, 0, 0);
                default:
                    return Vector3Int.zero;
            }
        }
        public static WallOrientation InvertOrientation(WallOrientation _orientation) =>
            (WallOrientation)(((int)_orientation + 2) % 4);
        #endregion

        #region ENUMS
        public enum WallType
        {
            Wall,
            Ramp,
            Floor
        }
        public enum WallOrientation
        {
            North,
            East,
            South,
            West
        }
        #endregion

        #region INSPECTOR FIELDS
        [Header("Health")]
        [SerializeField] BoundedInt m_health = new BoundedInt(100, 100, 0);

        [Header("Type")]
        [SerializeField] WallType m_type = WallType.Wall;

        [Header("Events")]
        public UnityEvent OnHit = new UnityEvent();
        public UnityEvent OnDestroyed = new UnityEvent();

        [Header("Debug")]
        [SerializeField] DebugChannel m_debugChannel = null;
        #endregion

        #region PRIVATE FIELDS
        LoopingFloat m_decayTimer = new LoopingFloat(0, 0, 1);

        Vector3Int m_coordinate;
        #endregion

        #region READABLES
        public static Material MagicWallMat =>
            s_magicWallMat ? s_magicWallMat : s_magicWallMat = Resources.Load<Material>("Materials/MagicWallMat");
        public HitType ObjectType =>
            HitType.Structure;
        public WallType Type =>
            m_type;
        public WallOrientation Orientation
        {
            get
            {
                Vector3 _forward = CardinalForward(transform);

                if (_forward.y != 0)
                    return _forward.y >= 0 ? WallOrientation.North : WallOrientation.South;
                else
                    return _forward.x >= 0 ? WallOrientation.East : WallOrientation.West;
            }
        }
        public Vector3Int Coordinate =>
            m_coordinate;
        #endregion

        #region DEFAULT METHODS
        void OnValidate()
        {
            if (Application.isPlaying)
                return;

            if (!MagicWallMat)
                return;

            MeshRenderer _meshRenderer = GetComponent<MeshRenderer>();

            if (_meshRenderer)
                _meshRenderer.sharedMaterial = MagicWallMat;

            LODGroup _lodGroup = GetComponent<LODGroup>();

            if (_lodGroup)
                foreach (LOD _lod in _lodGroup.GetLODs())
                    foreach (Renderer _renderer in _lod.renderers)
                        _renderer.sharedMaterial = MagicWallMat;
        }
        void Awake()
        {
            m_decayTimer = new LoopingFloat(0, 0, 1);

            m_decayTimer.OnRangeExceeded.AddListener(() => {
                if (photonView)
                    if (PhotonNetwork.InRoom && !photonView.IsMine)
                        return;

                m_health -= 3;
            });

            m_health = new BoundedInt(100, 100, 0);

            m_health.OnValueUpdated.AddListener((BoundedInt _value) => {
                if (photonView && photonView.IsMine)
                    photonView.RPC(nameof(SetValue), RpcTarget.All, _value.Value);
            });

            m_health.OnValueMinimum.AddListener(() => { 
                OnDestroyed?.Invoke();
                Destroy(gameObject);
            });

            m_coordinate = ToCoordinate(m_type == WallType.Wall ? transform.position - transform.forward * Constants.GridSize / 2 : transform.position);

            switch (Type)
            {
                case WallType.Wall:
                    ActiveWalls.Add(this);
                    break;
                case WallType.Ramp:
                    ActiveRamps.Add(this);
                    break;
                case WallType.Floor:
                    ActiveFloors.Add(this);
                    break;
            }
        }
        void Update()
        {
            m_decayTimer += Time.deltaTime;
        }
        void OnDestroy()
        {
            switch (Type)
            {
                case WallType.Wall:
                    ActiveWalls.Remove(this);
                    break;
                case WallType.Ramp:
                    ActiveRamps.Remove(this);
                    break;
                case WallType.Floor:
                    ActiveFloors.Remove(this);
                    break;
            }
        }
        void OnDrawGizmosSelected()
        {
            m_debugChannel?.DrawText(m_health.Value.ToString("###.#") + "/100", transform.position + Vector3.zero * Constants.GridSize);
        }
        #endregion

        #region COROUTINES
        #endregion

        #region HIT METHODS
        public void ReceiveHit(float _damage)
        {
            if (PhotonNetwork.IsConnected && PhotonNetwork.InRoom && !photonView.IsMine)
                return;

            m_health -= Mathf.RoundToInt(_damage);

            OnHit?.Invoke();
        }
        [PunRPC]
        public void SetValue(int _value)
        {
            m_health.Value = _value;

            OnHit?.Invoke();
        }
        #endregion

        #region GETTER METHODS
        public static Vector3Int ToCoordinate(Vector3 _position)
        {
            int _xCoordinate = (_position.x / Constants.GridSize).RoundDown();
            int _yCoordinate = (_position.y / Constants.GridSize).RoundDown();
            int _zCoordinate = (_position.z / Constants.GridSize).RoundDown();

            return new Vector3Int(_xCoordinate, _yCoordinate, _zCoordinate);
        }
        public static Vector3 ToPosition(Vector3Int _coordinate) =>
            (new Vector3(_coordinate.x, _coordinate.y, _coordinate.z) * Constants.GridSize) + (Vector3.right + Vector3.forward) * (Constants.GridSize / 2);
        public static MagicWall AtPosition(Vector3Int _position) =>
            ActiveWalls.Find(x => x.Coordinate == _position);
        public static Vector3 CardinalForward(Transform _transform) => 
            CardinalForward(_transform.forward);
        public static Vector3 CardinalForward(Vector3 _forward)
        {
            if (_forward == Vector3.up || _forward == Vector3.down)
                return Vector3.forward;

            Vector3 _flatForward = new Vector3(_forward.x, 0, _forward.z).normalized;

            bool _horizontal = Mathf.Abs(_flatForward.x) > Mathf.Abs(_flatForward.z);

            Vector3 _roundedForward = new Vector3(_horizontal ? _flatForward.x / Mathf.Abs(_flatForward.x) : 0, 0, _horizontal ? 0 : _flatForward.z / Mathf.Abs(_flatForward.z));

            return _roundedForward;
        }
        #endregion
    }
}