namespace Marro.PacManUdon
{
    using System;
    using UnityEngine;

    public enum Direction
    {
        Zero = 0, Up = 1, Down = 2, Left = 4, Right = 8, UpLeft = 5, UpRight = 9, DownLeft = 6, DownRight = 10
    }

    public enum DirectionAxis
    {
        Horizontal = 0,
        Vertical = 1,
        Both = 2
    }

    public abstract class GridMover : SyncedObject
    {
        protected Vector2 position;
        protected Direction direction;
        protected Direction targetDirection;

        // Cannot be static
        public readonly Vector2[] directionVectors =
        {
            Vector2.zero,                 // 0
            Vector2.up,                   // 1
            Vector2.down,                 // 2
            Vector2.zero,                 // 3
            Vector2.left,                 // 4
            Vector2.up + Vector2.left,    // 5
            Vector2.down + Vector2.left,  // 6
            Vector2.left,                 // 7
            Vector2.right,                // 8
            Vector2.up + Vector2.right,   // 9
            Vector2.down + Vector2.right, // 10
            Vector2.right,                // 11
            Vector2.zero,                 // 12
            Vector2.up,                   // 13
            Vector2.down,                 // 14
            Vector2.zero,                 // 15
        };

        public virtual Vector2 GetPosition()
        {
            //return (Vector2)transform.localPosition;
            return position;
        }

        public virtual void SetPosition(Vector2 position)
        {
            this.position = position;
            transform.localPosition = new Vector3(position.x, position.y, transform.localPosition.z);
        }

        public Direction GetDirection()
        {
            return direction;
        }

        public Direction GetTargetDirection()
        {
            return targetDirection;
        }

        public void SetDirection(Direction direction)
        {
            this.direction = direction;
            UpdateAnimator();
        }

        protected void SetTargetDirection(Direction targetDirection)
        {
            this.targetDirection = targetDirection;
            UpdateAnimator();
        }

        public void SetDirectionAndTargetDirection(Direction direction)
        {
            this.direction = direction;
            this.targetDirection = direction;
            UpdateAnimator();
        }

        protected static Direction HorizontalToDirection(float horizontal)
        {
            if (horizontal < 0)
            {
                return Direction.Left;
            }
            if (horizontal > 0)
            {
                return Direction.Right;
            }
            return Direction.Zero;
        }

        protected static Direction VerticalToDirection(float vertical)
        {
            if (vertical < 0)
            {
                return Direction.Down;
            }
            if (vertical > 0)
            {
                return Direction.Up;
            }
            return Direction.Zero;
        }

        protected abstract void UpdateAnimator();

        public override void CollectSyncedData(byte[] data, ref int index, NetworkEventType eventType)
        {
            data.Append(GetPosition(), ref index);
            data.AppendAsByte((int)direction, ref index);
            data.AppendAsByte((int)targetDirection, ref index);
        }

        public override bool WriteSyncedData(byte[] data, ref int index, NetworkEventType eventType)
        {
            SetPosition(data.ReadVector2(ref index));
            direction = (Direction)data.ReadByte(ref index);
            targetDirection = (Direction)data.ReadByte(ref index);
            UpdateAnimator();

            return true;
        }

        public void PadSyncedData(ref int index)
        {
            index += 10;
        }

        #region Utils
        public static Direction GetInverseDirection(Direction direction)
        {
            switch (direction)
            {
                case Direction.Up:
                    return Direction.Down;
                case Direction.Down:
                    return Direction.Up;
                case Direction.Left:
                    return Direction.Right;
                case Direction.Right:
                    return Direction.Left;
                case Direction.UpLeft:
                    return Direction.DownRight;
                case Direction.UpRight:
                    return Direction.DownLeft;
                case Direction.DownLeft:
                    return Direction.UpRight;
                case Direction.DownRight:
                    return Direction.UpLeft;
                default:
                    return Direction.Zero;
            }
        }

        public static bool IsVertical(Direction direction) => ((int)direction & 0b0011) != 0;
        public static bool IsHorizontal(Direction direction) => ((int)direction & 0b1100) != 0;

        public static Direction VerticalComponent(Direction direction) => (Direction)((int)direction & 0b0011);
        public static Direction HorizontalComponent(Direction direction) => (Direction)((int)direction & 0b1100);

        public static Vector2 GetNextPosition(Vector2 currentPosition, Vector2 directionVector, float speed, float deltaTime)
        {
            var nextPosition = currentPosition + deltaTime * speed * directionVector;
            nextPosition = new Vector2((nextPosition.x + 32) % 32, (nextPosition.y + 32) % 32);
            return nextPosition;
        }

        public static Vector2 PositionToGrid(Vector2 position)
        {
            return new Vector2((int)position.x + 0.5f, (int)position.y + 0.5f);
        }

        public static bool CrossesTileBorder(Vector2 currentPosition, Vector2 nextPosition, Direction direction)
        {
            bool result = false;

            if (IsHorizontal(direction))
            {
                result = Math.Floor(currentPosition.x) != Math.Floor(nextPosition.x);
            }
            if (!result && IsVertical(direction))
            {
                result = Math.Floor(currentPosition.y) != Math.Floor(nextPosition.y);
            }

            return result;
        }

        public static bool CrossesTileCenter(Vector2 currentPosition, Vector2 nextPosition, Direction direction)
        {
            bool result = false;

            if (IsHorizontal(direction))
            {
                result = Math.Round(currentPosition.x) != Math.Round(nextPosition.x);
            }
            if (!result && IsVertical(direction))
            {
                result = Math.Round(currentPosition.y) != Math.Round(nextPosition.y);
            }

            return result;
        }
        #endregion
    }
}