using System.Collections.Generic;
using System.Linq;
using EscapeRoomEngine.Engine.Runtime.Measurements;
using EscapeRoomEngine.Engine.Runtime.Modules;
using UnityEngine;
using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger;
using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;

namespace EscapeRoomEngine.Engine.Runtime
{
    /// <summary>
    /// A room manages an entrance to its first space and exit from its last space. It is also responsible for instantiating all spaces in it.
    /// </summary>
    public class Room
    {
        internal Passage entrance, exit;
        internal GameObject roomObject;
        internal readonly List<PuzzleModule> puzzles = new();

        /// <summary>
        /// Return whether this room is the last room of the experience, determined by whether this room has an exit.
        /// </summary>
        internal bool LastRoom => exit == null;
        
        private readonly List<Space> _spaces = new();
        private readonly List<DoorModule> _doors = new();

        internal Room(Passage entrance)
        {
            this.entrance = entrance;
        }

        internal void AddSpace(Space space, Passage spaceExit)
        {
            _spaces.Add(space);
            exit = spaceExit;
        }

        /// <summary>
        /// Solves all puzzles in this room.
        /// </summary>
        public void SkipRoom()
        {
            Logger.Log($"Skipping {this}...", LogType.PuzzleFlow);
            
            puzzles.ForEach(puzzle => puzzle.PuzzleState.Solve());
            if (puzzles.Count == 0 && !LastRoom)
            {
                exit.fromOut.DoorState.Unlock();
            }
        }

        public void EnterRoom()
        {
            entrance.toIn.DoorState.ExitFrom();
        }

        internal void AddPuzzle(PuzzleModule puzzle)
        {
            puzzles.Add(puzzle);
            puzzle.PuzzleState.PuzzleEvent += OnPuzzleEvent;
        }

        private void OnPuzzleEvent(PuzzleModule puzzle, PuzzleEventType type)
        {
            if (type == PuzzleEventType.Solved)
            {
                Measure.Solve((PuzzleModuleDescription)puzzle.description);
                
                if (puzzles.All(p => p.PuzzleState.Solved))
                {
                    exit.fromOut.DoorState.Unlock();
                }
            }
        }

        internal void AddDoor(DoorModule door)
        {
            _doors.Add(door);
            door.DoorState.DoorEvent += (_, type) =>
            {
                // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
                switch (type)
                {
                    // generate a new room as soon as the player completes all puzzles in this one
                    case DoorEventType.Unlocked when !LastRoom && door.Equals(exit.fromOut):
                        Engine.Instance.GenerateRoom();
                        break;
                    // start measurements on every puzzle as soon as the player enters the last room
                    case DoorEventType.ExitedFrom when door.Equals(entrance.toIn) && Engine.Instance.CurrentRoom.Contains(this):
                        puzzles.ForEach(puzzle => Measure.StartMeasuring((PuzzleModuleDescription)puzzle.description));
                        Engine.Instance.HidePreviousRoom();
                        break;
                }
            };
        }

        internal void InstantiateRoom(Transform parent, Vector3 offset, string name)
        {
            roomObject = new GameObject($"Room {name}");
            roomObject.transform.SetParent(parent, false);
            roomObject.transform.localPosition = offset;

            for (var i = 0; i < _spaces.Count; i++)
            {
                _spaces[i].InstantiateSpace(roomObject.transform, i.ToString());
            }
            
            entrance.ConnectDoors();
            entrance.toIn.DoorState.Unlock();
        }

        public override string ToString()
        {
            return roomObject.name;
        }
    }
}
