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

namespace EscapeRoomEngine.Engine.Runtime.Measurements
{
    /// <summary>
    /// This class is the main way to interact with measurements.
    /// </summary>
    public static class Measure
    {
        /// <summary>
        /// Whether to store the taken measurements in the database. Disable for test runs.
        /// </summary>
        public static bool Store { get; set; }
        
        private static Dictionary<int, PuzzleMeasurement> _runningMeasurements;
        private static Session _currentSession;

        /// <summary>
        /// The average time to solve a specific puzzle.
        /// </summary>
        public static float AverageTime(PuzzleModuleDescription puzzle) =>
            PuzzleStorage.Instance.Load(puzzle).AverageTimeToSolve;
        /// <summary>
        /// The average time to solve a group of puzzles.
        /// </summary>
        public static float AverageTime(IEnumerable<PuzzleModuleDescription> puzzles) => puzzles.Sum(AverageTime);
        /// <summary>
        /// The average time to solve a specific room.
        /// </summary>
        /// <remarks>This only counts puzzles already placed in a room.</remarks>
        public static float AverageTime(Room room) =>
            room.puzzles.Sum(puzzle => AverageTime((PuzzleModuleDescription)puzzle.description));

        /// <summary>
        /// Estimate the time a specific puzzle will take to be solved by the current player.
        /// </summary>
        public static float EstimateTime(PuzzleModuleDescription puzzle) =>
            PuzzleStorage.Instance.Load(puzzle).EstimateTimeToSolve(SessionPercentile());
        /// <summary>
        /// Estimate the time a group of puzzles will take to be solved by the current player.
        /// </summary>
        public static float EstimateTime(IEnumerable<PuzzleModuleDescription> puzzles) => puzzles.Sum(EstimateTime);
        /// <summary>
        /// Estimate the time a room will take to be solved by the current player.
        /// </summary>
        /// <remarks>This only counts puzzles already placed in a room.</remarks>
        public static float EstimateTime(Room room) =>
            room.puzzles.Sum(puzzle => EstimateTime((PuzzleModuleDescription)puzzle.description));

        /// <summary>
        /// The estimated percentile the current player is placed in.
        /// </summary>
        public static float SessionPercentile() => _currentSession?.MeanPercentile ?? 0.5f;

        /// <summary>
        /// Start counting the time to solve a specific puzzle.
        /// </summary>
        public static void StartMeasuring(PuzzleModuleDescription puzzle)
        {
            _runningMeasurements[puzzle.Id] = new PuzzleMeasurement
            {
                TimeStarted = Time.time,
                TimeSolved = Time.time
            };
            
            Logger.Log($"Started measuring {puzzle}", LogType.Measuring);
        }

        /// <summary>
        /// A puzzle has been solved and the measurement should be stopped. This will also store the finished measurement in the database.
        /// </summary>
        public static void Solve(PuzzleModuleDescription puzzle)
        {
            if (_currentSession == null)
            {
                throw new EngineException("Measuring session must be started before taking measurements.");
            }
            
            var measurement = _runningMeasurements[puzzle.Id];

            if (Store)
            {
                PuzzleStorage.Instance.EndMeasurement(_currentSession, puzzle, measurement);
            }
            _runningMeasurements.Remove(puzzle.Id);
            
            Logger.Log($"Solved {puzzle} with measurement {measurement}", LogType.Measuring);
        }

        /// <summary>
        /// Start a new session and place the current player at the 50th percentile.
        /// </summary>
        public static void StartSession()
        {
            _currentSession = new Session();
            
            // add a first plan result with the initial target and estimated times
            _currentSession.PlanResults.Add(new PlanResult(
                GameControl.Instance.TargetTime,
                .5f,
                GameControl.Instance.EstimatedTime));
            
            Logger.Log($"Started {_currentSession}", LogType.Measuring);
        }

        /// <summary>
        /// End a session and store it in the database.
        /// </summary>
        public static void EndSession(float time)
        {
            if (Store)
            {
                PuzzleStorage.Instance.EndSession(_currentSession, time);
            }
            
            Logger.Log($"Ended {_currentSession}", LogType.Measuring);
        }

        public static void Clear()
        {
            _runningMeasurements = new Dictionary<int, PuzzleMeasurement>();
            Store = true;
        }
    }
}