using GeneralData;
using ScottPlot.Plottables;

namespace HistoryUI.ViewModels;

public partial class StatusViewModel : BaseViewModel<DBFormat>
{
    public StatusViewModel(Hardwares hardwares, IORM orm) : base(hardwares, orm)
    {
        this.DataGirdVis = Visibility.Collapsed;
        this.Loading = Visibility.Collapsed;
        this.PlotControl = new();
        this.InitPlot();
    }

    private void InitPlot()
    {
        this.PlotControl.Plot.Grid.XAxisStyle.MajorLineStyle.Width = 1f;
        this.PlotControl.Plot.Grid.YAxisStyle.MajorLineStyle.Width = 1f;
        this.PlotControl.Plot.FigureBackground.Color = ScottPlot.Colors.Transparent;

        this.PlotControl.Plot.RenderManager.RenderStarting += (s, e) =>
        {
            Tick[] ticks = this.PlotControl.Plot.Axes.Bottom.TickGenerator.Ticks;
            for (int i = 0; i < ticks.Length; i++)
            {
                DateTime dt = DateTime.FromOADate(ticks[i].Position);
                string label = $"{dt:MM/dd HH:mm:ss}";
                ticks[i] = new Tick(ticks[i].Position, label);
            }
        };

        PixelPadding padding = new(40, 20, 65, 10);
        this.PlotControl.Plot.Layout.Fixed(padding);
        this.PlotControl.Plot.FigureBackground.Color = ScottPlot.Colors.Transparent;
        this.Query();
    }

    [ObservableProperty]
    private WpfPlot _PlotControl;

    [ObservableProperty]
    private string? _Hint;

    [ObservableProperty]
    private Visibility _DataGirdVis;

    [ObservableProperty]
    private Visibility _Loading;

    [RelayCommand]
    private void Show(string para)
    {
        switch (para)
        {
            case "Show":
                this.DataGirdVis = Visibility.Visible;
                break;

            case "Hide":
                this.DataGirdVis = Visibility.Collapsed;
                break;
            default:
                break;
        }

    }

    [RelayCommand]
    protected override void Query()
    {
        if (!base.QueryBase(out byte mini8, out byte Channel))
            return;
        this.Loading = Visibility.Visible;
        _orm.Query<DBFormat>($"Mini8-{mini8}-{Channel}",
            t =>
            t.DateTime >= this.StartTime &&
            t.DateTime <= this.EndTime
            , QueryResult);
    }

    private class Area(DateTime start, DateTime? end, bool success)
    {
        public bool Success { get; set; } = success;
        public DateTime Start { get; } = start;
        public DateTime? End { get; set; } = end;
    }
    private class DataRange
    {
        public readonly List<float> target = [];
        public readonly List<float> temp = [];
        public readonly List<float> caps = [];
        public readonly List<float> floor = [];
        public readonly List<float> capsWarning = [];
        public readonly List<float> floorWarning = [];
        public readonly List<DateTime> tcBrokenLine = [];
        public readonly List<DateTime> time = [];
        public readonly List<Area> autoTuneArea = [];
        public readonly List<Area> tcBrockenArea = [];
    }

    private readonly List<DataRange> _DataRangeCache = [];
    private readonly Dictionary<int, DBFormat> _DataCache = [];

    private void QueryResult(List<DBFormat> results)
    {
        if (results.Count < 1)
        {
            App.Current.Dispatcher.Invoke(() => this.Loading = Visibility.Collapsed);
            return;
        }

        results = [.. results.OrderBy(t => t.DateTime)];
        _DataCache.Clear();
        _DataRangeCache.Clear();
        for (int i = 1; i <= results.Count; i++)
        {
            _DataCache.Add(i, results[i - 1]);

            if (_DataCache[i].ActiveTuneSet != ActiveTuneSet.AutoTune)
                continue;

            _DataCache[i].Running_P = _DataCache[i].AutoTune_P;
            _DataCache[i].Running_I = _DataCache[i].AutoTune_I;
            _DataCache[i].Running_D = _DataCache[i].AutoTune_D;
        }

        //Create Data Range
        DateTime? lastTime = null;
        AutoTuneStatus? autoTune = null;
        TcBorken tcBorken = TcBorken.Normal;
        float lastPV = 0;
        foreach (DBFormat dbData in results)
        {
            if (dbData.SensorBreakAlarm == TcBorken.Error)
                dbData.PV = lastPV;
            lastPV = dbData.PV;

            TimeSpan? span = dbData.DateTime - lastTime;
            if (lastTime is null || !span.HasValue || span.Value.TotalSeconds > 10)
            {
                autoTune = null;
                tcBorken = TcBorken.Normal;
                _DataRangeCache.Add(new());
            }
            lastTime = dbData.DateTime;
            DataRange dataRang = _DataRangeCache.Last();

            dataRang.time.Add(dbData.DateTime);
            dataRang.temp.Add(dbData.PV);
            dataRang.target.Add(dbData.SetPoint);
            dataRang.caps.Add(dbData.Caps);
            dataRang.floor.Add(dbData.Floor);
            dataRang.capsWarning.Add(dbData.CapsWarning);
            dataRang.floorWarning.Add(dbData.FloorWarning);

            if (tcBorken == dbData.SensorBreakAlarm)
                goto Step_AutoTuneStatusCheck;

            switch (dbData.SensorBreakAlarm)
            {
                case TcBorken.Normal:
                    if (dataRang.tcBrockenArea.Count == 0)
                        break;
                    Area s = dataRang.tcBrockenArea.Last();
                    s.Success = true;
                    s.End = dbData.DateTime;
                    break;
                case TcBorken.Error:
                    dataRang.tcBrockenArea.Add(new(dbData.DateTime, null, false));
                    break;
                default:
                    break;
            }
            dataRang.tcBrokenLine.Add(dbData.DateTime);
            tcBorken = dbData.SensorBreakAlarm;

        Step_AutoTuneStatusCheck:
            //autoTune ??= dbData.AutoTuneStatus;
            if (autoTune.HasValue && autoTune == dbData.AutoTuneStatus)
                continue;

            switch (dbData.AutoTuneStatus)
            {
                case AutoTuneStatus.Triggered:
                case AutoTuneStatus.Tuning:
                    autoTune = AutoTuneStatus.Tuning;
                    dataRang.autoTuneArea.Add(new(dbData.DateTime, null, true));
                    break;
                case AutoTuneStatus.Complete:
                    autoTune = AutoTuneStatus.Complete;
                    if (dataRang.autoTuneArea.Count == 0)
                    {
                        dataRang.autoTuneArea.Add(new(dataRang.time[0], dbData.DateTime, true));
                        break;
                    }
                    Area area1 = dataRang.autoTuneArea.Last();
                    area1.Success = true;
                    area1.End = dbData.DateTime;
                    break;
                case AutoTuneStatus.Aborted:
                case AutoTuneStatus.Timeout:
                case AutoTuneStatus.Overflow:
                    autoTune = AutoTuneStatus.Aborted;
                    if (dataRang.autoTuneArea.Count == 0)
                    {
                        dataRang.autoTuneArea.Add(new(dataRang.time[0], dbData.DateTime, false));
                        break;
                    }
                    Area area2 = dataRang.autoTuneArea.Last();
                    area2.Success = false;
                    area2.End = dbData.DateTime;
                    break;
                default:
                    break;
            }
        }


        App.Current.Dispatcher.Invoke(() =>
        {
            this.PlotControl.Plot.Clear();

            this._DataRangeCache.ForEach(range => SetDataRangeInPlot(range));

            this.RefreshPlot();
            this.RefreshDataTable(results);
            this.Loading = Visibility.Collapsed;
        });
    }

    private void SetLine(List<DateTime> time, List<float> value, string color, LinePattern linePattern, MarkerStyle markerStyle, float lineWidth)
    {
        Scatter mainScatter = this.PlotControl.Plot.Add.Scatter(time, value, ScottPlot.Color.FromHex(color));
        mainScatter.MarkerStyle = markerStyle;
        mainScatter.LineWidth = lineWidth;
        mainScatter.LinePattern = linePattern;
    }

    private void SetDataRangeInPlot(DataRange range)
    {
        this.SetLine(range.time, range.temp, "0000cd", LinePattern.Solid, MarkerStyle.None, 2f);
        this.SetLine(range.time, range.caps, "FF0000", LinePattern.Dotted, MarkerStyle.None, 1.5f);
        this.SetLine(range.time, range.floor, "FF0000", LinePattern.Dotted, MarkerStyle.None, 1.5f);
        this.SetLine(range.time, range.capsWarning, "FFA500", LinePattern.Dotted, MarkerStyle.None, 1.5f);
        this.SetLine(range.time, range.floorWarning, "FFA500", LinePattern.Dotted, MarkerStyle.None, 1.5f);
        this.SetLine(range.time, range.target, "00FF00", LinePattern.Dotted, MarkerStyle.None, 1.5f);

        foreach (var item in range.autoTuneArea)
        {
            item.End ??= range.time.Last();
            _ = item.Success switch
            {
                true => this.PlotControl.Plot.Add.HorizontalSpan(item.Start.ToOADate(), item.End.Value.ToOADate(), ScottPlot.Color.FromHex("00ff00").WithAlpha(0.2)),
                false => this.PlotControl.Plot.Add.HorizontalSpan(item.Start.ToOADate(), item.End.Value.ToOADate(), ScottPlot.Color.FromHex("828282").WithAlpha(0.2))
            };
        }

        foreach (var item in range.tcBrockenArea)
        {
            item.End ??= range.time.Last();
            this.PlotControl.Plot.Add.HorizontalSpan(item.Start.ToOADate(), item.End.Value.ToOADate(), ScottPlot.Color.FromHex("f98083").WithAlpha(0.2));
        }
    }

    private void RefreshDataTable(List<DBFormat> results)
    {
        Dictionary<int, DBFormat> temp = [];
        this.Results = null;
        for (int i = 0; i < results.Count; i++)
            temp.Add(i, results[i]);
        this.Results = temp;
    }

    private void RefreshPlot()
    {
        this.PlotControl.Plot.HideLegend();
        this.PlotControl.Plot.Axes.DateTimeTicksBottom();
        this.PlotControl.Plot.Axes.Bottom.TickLabelStyle.Rotation = -45;
        this.PlotControl.Plot.Axes.Bottom.TickLabelStyle.Alignment = Alignment.MiddleRight;
        this.PlotControl.Plot.Axes.AutoScale();
        this.PlotControl.Refresh();
        this.PlotControl.Plot.Axes.Zoom(1.085, 1);
    }

    [RelayCommand]
    private void ReScale(string para)
    {
        switch (para)
        {
            case "+":
                this.PlotControl.Plot.Axes.Zoom(1.25, 1);
                break;
            case "-":
                this.PlotControl.Plot.Axes.Zoom(0.8, 1);
                break;
            case "add":
                this.PlotControl.Plot.Axes.Zoom(1, 1.25);
                break;
            case "minus":
                this.PlotControl.Plot.Axes.Zoom(1, 0.8);
                break;

            default:
                this.PlotControl.Plot.Axes.AutoScale();
                this.PlotControl.Plot.Axes.Zoom(1.085, 1);
                break;
        }
        this.PlotControl.Refresh();
    }
}