using CommunityToolkit.Mvvm.ComponentModel;
using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.Kernel.Sketches;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Painting;
using Microsoft.UI.Xaml;
using NixExampleWinUI.Contracts.Services;
using NixExampleWinUI.Helpers;
using NixExampleWinUI.Views;
using NixUniversalSDK;
using SkiaSharp;
using Windows.ApplicationModel.Resources;
using Windows.UI.ViewManagement;

namespace NixExampleWinUI.ViewModels;

/// <summary>
/// View model for <see cref="SpectralPlot"/>
/// </summary>
public class SpectralPlotViewModel : ObservableRecipient
{   
    #region Theme detection
    private static Windows.UI.Color? DefaultBackground => new UISettings().GetColorValue(UIColorType.Background);
    private static bool IsDefaultDark => DefaultBackground.IsDark() ?? false;
    private readonly IThemeSelectorService ThemeSelectorService;
    private bool IsDarkTheme => ThemeSelectorService.Theme switch
    {
        ElementTheme.Light => false,
        ElementTheme.Dark => true,
        _ => IsDefaultDark
    };
    private SKColor PlotTextColor => IsDarkTheme ? SKColors.White : SKColors.Black;
    private SKColor PlotGridColor => IsDarkTheme ? SKColors.DimGray : SKColors.LightGray;
    #endregion

    /// <summary>
    /// Resource loader used to fetch string resources
    /// </summary>
    private static readonly ResourceLoader Loader =  ResourceLoader.GetForViewIndependentUse();

    public SpectralPlotViewModel(IThemeSelectorService themeSelectorService)
    {
        ThemeSelectorService = themeSelectorService;
    }

    private IMeasurementData? _MeasurementData = null;

    /// <summary>
    /// <see cref="IMeasurementData"/> instance
    /// </summary>
    public IMeasurementData? MeasurementData
    {
        get => _MeasurementData;
        set
        {
            // Update value and its computed properties
            SetProperty(ref _MeasurementData, value);
            OnPropertyChanged(nameof(SpectralSeries));
            OnPropertyChanged(nameof(EmptyPlotLabel));
            OnPropertyChanged(nameof(IsPlotVisible));
            OnPropertyChanged(nameof(IsPlotEmpty));

            // Reset plot zoom
            ResetPlotZoom();
        }    
    }
    public bool IsPlotVisible => MeasurementData is not null && MeasurementData.ProvidesSpectral;
    public bool IsPlotEmpty => MeasurementData?.ProvidesSpectral == false;
    public string EmptyPlotLabel
    {
        get
        {
            // Check if spectral data license is active
            var spectralLicensed = LicenseManager.IsFeatureEnabled(LicenseFeature.SpectralData);

            // Check if measurement device supports spectral data
            var spectralDevice = MeasurementData?.DeviceType.IsFeatureSupported(LicenseFeature.SpectralData) ?? false;

            if (!spectralDevice)
            {
                // Device does not support spectral data
                return string.Format(
                    Loader.GetString("MessageSpectralUnavailableDevice"),
                    MeasurementData?.DeviceType.GetFullName());
            } 
            else if (!spectralLicensed)
            {
                // Device supports spectral data but it is not licensed
                return Loader.GetString("MessageSpectralUnavailableLicensing");
            }
            else
            {
                // Spectral data is not available for unknown reasons
                return Loader.GetString("MessageSpectralUnavailableGeneric");
            }
        }
    }

    /// <summary>
    /// <see cref="ISeries"/> array to be shown on the plot
    /// </summary>
    public ISeries[] SpectralSeries
    {
        get
        {
            var scatterPoints = new List<ObservablePoint>();
            var linePoints = new List<ObservablePoint>();

            if (MeasurementData?.SpectralData is ISpectralData data)
            {
                
                for (var i = 0; i < data.Value.Length; i++)
                {
                    // Scatter plot entries (actual measurement data)
                    scatterPoints.Add(new(data.Lambda[i], data.Value[i]));
                }

                var lambdaMin = data.Lambda.Min();
                var lambdaMax = data.Lambda.Max();
                for (var i = lambdaMin; i <= lambdaMax; i++)
                {
                    // Interpolated line at 1 nm spacing
                    linePoints.Add(new(i, data.Interpolate(i)));
                }
            }
            var scatterSeries = new ScatterSeries<ObservablePoint>
            {
                Values = scatterPoints,
                MinGeometrySize = 4,
                GeometrySize = 8,
                Fill = new SolidColorPaint(SKColors.White),
                Stroke = new SolidColorPaint(SKColors.SlateGray, 2),
                Name = string.Empty,
                YToolTipLabelFormatter = (point) => $"{point.Coordinate.SecondaryValue:F0} nm, {point.Coordinate.PrimaryValue:F6}",
            };

            var lineSeries = new LineSeries<ObservablePoint> {
                Values = linePoints,
                GeometryFill = null,
                GeometryStroke = null,
                Fill = null,
                Stroke = new SolidColorPaint(SKColors.SlateGray, 2),
                Name = string.Empty,
                IsHoverable = false
            };

            return new ISeries[] {
                lineSeries,
                scatterSeries,
            };
        }
    }

    private readonly double XMinDefault = 380;
    private readonly double XMaxDefault = 720;
    private readonly Axis XAxis = new ()
    {
        Name = Loader.GetString("TitleAxisWavelength"),
        NamePadding = new LiveChartsCore.Drawing.Padding(0, 15),
        NamePaint = new SolidColorPaint
        {
            FontFamily = "Segoe UI",
            SKFontStyle = new SKFontStyle(
                    SKFontStyleWeight.Bold,
                    SKFontStyleWidth.Normal,
                    SKFontStyleSlant.Upright),
        },
        LabelsPaint = new SolidColorPaint
        {
            FontFamily = "Segoe UI",
            SKFontStyle = new SKFontStyle(
                    SKFontStyleWeight.Normal,
                    SKFontStyleWidth.Normal,
                    SKFontStyleSlant.Upright),
        }
    };    
    public IEnumerable<ICartesianAxis> XAxes => new List<ICartesianAxis> { XAxis };

    private readonly double YMinDefault = 0;
    private readonly double YMaxHeadroom = 0.02;
    private double? YMaxDefault => MeasurementData?.SpectralData?.Value.Max() > 1 ? MeasurementData?.SpectralData?.Value.Max() : 1;
    private readonly Axis YAxis = new()
    {
        Name = Loader.GetString("TitleAxisReflectance"),
        NamePadding = new LiveChartsCore.Drawing.Padding(0, 15),
        NamePaint = new SolidColorPaint
        {
            FontFamily = "Segoe UI",
            SKFontStyle = new SKFontStyle(
                    SKFontStyleWeight.Bold,
                    SKFontStyleWidth.Normal,
                    SKFontStyleSlant.Upright),
        },
        LabelsPaint = new SolidColorPaint
        {
            FontFamily = "Segoe UI",
            SKFontStyle = new SKFontStyle(
                    SKFontStyleWeight.Normal,
                    SKFontStyleWidth.Normal,
                    SKFontStyleSlant.Upright),
        },
        SeparatorsPaint = new SolidColorPaint
        { 
            StrokeThickness = 1 
        },
        Labeler = (value) => value.ToString("0.0#####")
    };
    public IEnumerable<ICartesianAxis> YAxes => new List<ICartesianAxis> { YAxis };

    /// <summary>
    /// Forces UI update of plot axes properties to match dark or light theme
    /// </summary>
    /// <param name="notifyProperties"></param>
    public void UpdateTheme(bool notifyProperties = true)
    {
        // Set axis label colors again
        if (XAxis.NamePaint is SolidColorPaint xNamePaint) { xNamePaint.Color = PlotTextColor; }
        if (XAxis.LabelsPaint is SolidColorPaint xLabelsPaint) { xLabelsPaint.Color = PlotTextColor; }
        if (YAxis.NamePaint is SolidColorPaint yNamePaint) { yNamePaint.Color = PlotTextColor; }
        if (YAxis.LabelsPaint is SolidColorPaint yLabelsPaint) { yLabelsPaint.Color = PlotTextColor; }
        if (YAxis.SeparatorsPaint is SolidColorPaint ySeparatorsPaint) { ySeparatorsPaint.Color = PlotGridColor; }

        if (notifyProperties)
        {
            // Update UI
            OnPropertyChanged(nameof(XAxes));
            OnPropertyChanged(nameof(YAxes));
        }
    }
    
    /// <summary>
    /// Resets the plot zoom to default
    /// </summary>
    public void ResetPlotZoom()
    {
        // Update theming (in case it was changed by the user)
        UpdateTheme(notifyProperties: false);

        // Force default zoom limits
        XAxis.MinLimit = XMinDefault;
        XAxis.MaxLimit = XMaxDefault;
        YAxis.MinLimit = YMinDefault;
        YAxis.MaxLimit = (1 + YMaxHeadroom) * YMaxDefault;        

        // Update UI
        OnPropertyChanged(nameof(XAxes));
        OnPropertyChanged(nameof(YAxes));
    }
}
