using System;
using ATVO.ThemesSDK;
using ATVO.ThemeEditor.ThemeModels;
using ATVO.ThemeEditor.Scripting.DotNET;
using ATVO.ThemesSDK.Data.Results;
using ATVO.ThemeEditor.ThemeModels.DataBindings;
using ATVO.ThemesSDK.Data.Enums;
using ATVO.ThemeEditor.Utils;

namespace Scripts
{
    public enum TimingDisplayModes
    {
        None,
        Gap,
        Interval,
        Pitstops,
        LastLap,
        BestLap,
        JokerLapsRemaining,
        JokerLapsCompleted
    }

    public static class TimingDisplayProvider
    {
        // Remember the current modes
        public static TimingDisplayModes CurrentModeV { get; set; } = TimingDisplayModes.Gap;
        public static TimingDisplayModes CurrentModeH { get; set; } = TimingDisplayModes.Gap;

        // Mode text label
        private static Label _modeLabel;
        public static Label GetModeLabel(Theme theme)
        {
            if (_modeLabel == null)
            {
                var widget = theme.Widgets.Find("W_VTickerBg");
                var subw = widget.SubWidgets.Find("SW_TimingMode");
                _modeLabel = subw.Labels.Find("L_TimingMode");
            }
            return _modeLabel;
        }

        // Gap / interval binding format helpers
        public static ClassLiveGapDataBinding LongGapBinding { get; } = new ClassLiveGapDataBinding() {SecondsFormat = "0.000"};
        public static LiveIntervalDataBinding LongIntervalBinding { get; } = new LiveIntervalDataBinding(){SecondsFormat = "0.000"};
        public static ClassLiveGapDataBinding ShortGapBinding { get; } = new ClassLiveGapDataBinding(){SecondsFormat = "0.0"};
        public static LiveIntervalDataBinding ShortIntervalBinding { get; } = new LiveIntervalDataBinding(){SecondsFormat = "0.0"};
    }

    public class GapBindingConverter : IScript
    {
        public object Execute(ThemeContentItem item, object value, string parameter, ISimulation sim)
        {
            // Script is used both to display the gap/interval time, and to change the ticker mode

            // Input type is used to determine which way it is used:
            // 1. value is string (V or H): change timing mode of the V or H ticker
            // 2. value is IEntitySessionResult: convert to desired timing value

            if (value is string)
            {
                // Set the ticker mode
                SetTickerMode(item, value?.ToString());
            }
            else if (value is IEntitySessionResult)
            {
                // Use as a binding converter
                var result = (IEntitySessionResult)value;
                return ConvertToGapString(result, parameter, sim);
            }
            return null;
        }

        private void SetTickerMode(ThemeContentItem item, string value)
        {
            Console.WriteLine("Changing mode");

            TimingDisplayModes mode = TimingDisplayModes.None;

            // Check whether to chang the V or H ticker
            if (value == "H")
            {
                Console.WriteLine("Changing H mode");
                var ddH = item.Theme.Dropdowns.Find("DD_TimingModeH");

                // Change mode
                mode = (TimingDisplayModes)Enum.Parse(typeof(TimingDisplayModes), ddH.SelectedItem.Text);
                TimingDisplayProvider.CurrentModeH = mode;
                Console.WriteLine("Changed H mode: " + mode);
            }
            else
            {
                Console.WriteLine("Changing V mode");
                var ddV = item.Theme.Dropdowns.Find("DD_TimingModeV");

                // Change mode
                mode = (TimingDisplayModes)Enum.Parse(typeof(TimingDisplayModes), ddV.SelectedItem.Text);
                TimingDisplayProvider.CurrentModeV = mode;
                Console.WriteLine("Changed V mode: " + mode);
            }

            // Update the text label
            var text = GetModeDisplayText(TimingDisplayProvider.CurrentModeV);
            TimingDisplayProvider.GetModeLabel(item.Theme).Text = text;
            Console.WriteLine("Changed mode label text: " + text);
        }

        private string ConvertToGapString(IEntitySessionResult result, string parameter, ISimulation sim)
        {
            // Check which ticker is requesting the value
            TimingDisplayModes mode = TimingDisplayModes.None;
            if (parameter == "H")
                mode = TimingDisplayProvider.CurrentModeH;
            else
                mode = TimingDisplayProvider.CurrentModeV;

            switch (mode)
            {
                case TimingDisplayModes.Gap:
                case TimingDisplayModes.Interval:
                    return ConvertToGapOrInterval(mode, result);
                case TimingDisplayModes.Pitstops:
                    return ConvertToPitstops(result);
               case TimingDisplayModes.LastLap:
                    return ConvertToLastLap(result);
                case TimingDisplayModes.BestLap:
                    return ConvertToBestLap(result);
                case TimingDisplayModes.JokerLapsRemaining:
                    return ConvertToJokerLapsRemaining(result, sim);
                case TimingDisplayModes.JokerLapsCompleted:
                    return ConvertToJokerLapsCompleted(result);
            }
			return "";
        }
        
        private string GetModeDisplayText(TimingDisplayModes mode)
        {
            switch (mode)
            {
                case TimingDisplayModes.Gap:
                    return "GAP";
                case TimingDisplayModes.Interval:
                    return "INTERVAL";
                case TimingDisplayModes.Pitstops:
                    return "PITSTOPS";
               case TimingDisplayModes.LastLap:
                    return "LAST LAP";
                case TimingDisplayModes.BestLap:
                    return "BEST LAP";
                case TimingDisplayModes.JokerLapsRemaining:
                    return "JOKER LAPS REMAINING";
                case TimingDisplayModes.JokerLapsCompleted:
                    return "JOKER LAPS COMPLETED";
            }
			return "";
        }
        
        private string ConvertToGapOrInterval(TimingDisplayModes mode, IEntitySessionResult r)
        {
            // show OUT/PIT in a race
        	if (r.Session.Type == SessionType.Race)
            {        	
                if (r.Out)
                    return "OUT";
                if (r.Entity?.Car?.Movement?.IsInPits == true)
                    return "PIT";
            }

            // Handle leader separately
            if (r.LivePosition == 1)
            {
                if (r.Session.Type == SessionType.Race)
                {
                    return "LEADER";
                }
                else
                {
                    return TimeHelpers.SecondsToLaptime(r.FastestLapTime);      
                }          
            }

            // Everything else: use the gap binding converters
            if (mode == TimingDisplayModes.Gap)
                return ConvertToGap(r);
            else
                return ConvertToInterval(r);
        }

        private string ConvertToGap(IEntitySessionResult r)
        {
            // Use the gap/interval binding helper to format the gap
            // Use a 'short' display (1 decimal) for times over a minute, or a longer display (3 decimals) below 1min
            var binding = (r.LiveClassGap > 60) ? TimingDisplayProvider.ShortGapBinding : TimingDisplayProvider.LongGapBinding;
            return binding.Format(r.LiveClassGap, r.LiveClassPosition, r.LiveClassGapLaps, r.Session.Type);
        }

        private string ConvertToInterval(IEntitySessionResult r)
        {
            // Use the gap/interval binding helper to format the gap
            // Use a 'short' display (1 decimal) for times over a minute, or a longer display (3 decimals) below 1min
            var binding = (r.LiveClassInterval > 60) ? TimingDisplayProvider.ShortIntervalBinding : TimingDisplayProvider.LongIntervalBinding;
            return binding.Format(r.LiveClassInterval, r.LiveClassPosition, r.LiveClassIntervalLaps, r.Session.Type);
        }

        private string ConvertToPitstops(IEntitySessionResult r)
        {
            return r.PitStopCount.ToString();
        }

        private string ConvertToLastLap(IEntitySessionResult r)
        {
            return ConvertSecondsToLaptime(r.LastLapTime);
        }

        private string ConvertToBestLap(IEntitySessionResult r)
        {
            return ConvertSecondsToLaptime(r.FastestLapTime);
        }

        private string ConvertToJokerLapsCompleted(IEntitySessionResult r)
        {
            return r.JokerLapsCompleted.ToString();
        }

        private string ConvertToJokerLapsRemaining(IEntitySessionResult r, ISimulation sim)
        {
            return (sim.Session.Info.NumJokerLaps - r.JokerLapsCompleted).ToString();
        }

        private string ConvertSecondsToLaptime(float seconds, string secondsFormat = "0.000")
        {
            // Convert 'total seconds' (e.g 83.134) to formatted laptime (e.g. 1:23.134)
            if (seconds <= 0) 
                return "";

            if (seconds < 60)
                return seconds.ToString(secondsFormat);

            var minutes = (int)(seconds/60);
            var remainder = seconds%60;
            return $"{minutes}:{remainder.ToString(secondsFormat)}";
        }
    }
}