using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using System;
using System.IO;
using System.Reflection;
using VeloeMinecraftLauncher.Utils;
using Avalonia.Controls;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using Serilog.Events;
using System.Linq;
using Avalonia.Platform.Storage;
using DynamicData.Binding;
using System.Reactive.Linq;
using System.Windows.Input;

namespace VeloeMinecraftLauncher.ViewModels;

public class SettingsViewModel : ViewModelBase
{
    public SettingsViewModel() : this(new Serilog.LoggerConfiguration().CreateLogger(), null)
    { }
    public SettingsViewModel(Serilog.ILogger logger,ICommand update)
    {
        _logger = logger;

        Settings = SettingsService.Instance.Clone() as SettingsSerializable ?? new();
        ResetValidationRules();
        ValidationContext.ValidationStatusChange.Subscribe(x => this.RaisePropertyChanged(nameof(IsValid)));
        AllSettingsProprtiesObservable().Subscribe(x=>this.RaisePropertyChanged(nameof(IsValid)));
        AllSettingsProprtiesObservable().Subscribe(x => this.RaisePropertyChanged(nameof(IsModified)));
        Update = update;


        //AllSettingsProprtiesObservable().Subscribe(x => this.RaisePropertyChanged(nameof(IsValid)));
    }

    private ObservableAsPropertyHelper<bool> _isModified;
    private ObservableAsPropertyHelper<bool> _isValid;

    private ObservableCollection<LogEventLevel> _logEventLevels = new() { LogEventLevel.Debug, LogEventLevel.Information, LogEventLevel.Warning, LogEventLevel.Error };

    public SettingsSerializable Settings { get; private set; }

    public bool IsModified => Settings.GetHashCode() != SettingsService.Instance.GetHashCode();

    public bool IsValid => IsModified && ValidationContext.IsValid;

    public string LauncherVersion => Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown";

    public ObservableCollection<LogEventLevel> LogEventLevels => _logEventLevels;

    private void ResetValidationRules()
    {
        this.ClearValidationRules();
        this.ValidationRule(
            viewModel => viewModel.JavaPath,
            value => { if (value is null) return false; if (!UseCustomJava) return true; var path = Path.Combine(value, "bin/java"); if (OperatingSystem.IsWindows()) path += ".exe"; return File.Exists(path); },
            "Can't find java executable.");
    }

    public void SaveSettings()
    {
        SettingsService.Instance = (SettingsSerializable)Settings.Clone();
        SettingsService.SaveSettings();
        this.RaisePropertyChanged(nameof(SetMinecraftFolder));
        Update.Execute(null);
    }

    public void OpenMinecraftPathDialog(object window)
    {
        Task.Run(async() =>
        {
            var storageProvider = ((Window)window).StorageProvider;
            var initPath = String.Empty;

            if (!string.IsNullOrEmpty(Settings.MinecraftForlderPath))
                initPath = Path.GetFullPath(Settings.MinecraftForlderPath);

            var folderPickerOpenOptions = new FolderPickerOpenOptions() 
            {
                AllowMultiple = false,
                SuggestedStartLocation = await storageProvider.TryGetFolderFromPathAsync(initPath)
            };

            var result = await storageProvider.OpenFolderPickerAsync(folderPickerOpenOptions);

            if (result is null || result.Count != 1 || result.First().Path.AbsolutePath == String.Empty)
                return;

            MinecraftFolderPath = result.First().Path.LocalPath;
        });
    }

    public void OpenJavaPathDialog(object window)
    {
        Task.Run(async() =>
        {
            var storageProvider = ((Window)window).StorageProvider;
            var initPath = String.Empty;

            if (!string.IsNullOrEmpty(Settings.JavaPath))
                initPath = Path.GetFullPath(Settings.JavaPath);

            var folderPickerOpenOptions = new FolderPickerOpenOptions()
            {
                AllowMultiple = false,
                SuggestedStartLocation = await storageProvider.TryGetFolderFromPathAsync(initPath)
            };

            var result = await storageProvider.OpenFolderPickerAsync(folderPickerOpenOptions);

            if (result is null || result.Count != 1 || result.First().Path.AbsolutePath == String.Empty)
                return;

            JavaPath = result.First().Path.LocalPath;
        });
    }

    public void OpenLogFile()
    {
        if (Path.Exists(SettingsService.logFilePath?.Path))
        {
            System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo() { FileName = SettingsService.logFilePath.Path, UseShellExecute = true });
        }
    }

    private IObservable<SettingsViewModel?> AllSettingsProprtiesObservable()
    {
        return this.WhenAnyPropertyChanged(
                    nameof(SetMinecraftFolder),
                    nameof(MinecraftFolderPath),
                    nameof(UseCustomJava),
                    nameof(CheckAssets),
                    nameof(HideLauncher),
                    nameof(JavaPath),
                    nameof(GameLogToLauncher),
                    nameof(SetMaxRam),
                    nameof(MaxRam),
                    nameof(FileLogEventLevel),
                    nameof(ConsoleLogEventLevel),
                    nameof(SetMaxLog),
                    nameof(FullScreen),
                    nameof(CustomSize),
                    nameof(ClientHeight),
                    nameof(ClientWidth));
    }

    #region Settings Proprties

    public bool SetMinecraftFolder
    {
        get => Settings.SetPath;
        set
        {
            if (Settings.SetPath != value)
            {
                Settings.SetPath = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public string MinecraftFolderPath
    {
        get => Settings.MinecraftForlderPath;
        set
        {
            if (Settings.MinecraftForlderPath != value)
            {
                Settings.MinecraftForlderPath = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public bool UseCustomJava
    {
        get => Settings.UseCustomJava;
        set
        {
            if (Settings.UseCustomJava != value)
            {
                Settings.UseCustomJava = value;
                ResetValidationRules();
                this.RaisePropertyChanged();
            }
        }
    }

    public bool CheckAssets
    {
        get => Settings.CheckGameAssets;
        set
        {
            if (Settings.CheckGameAssets != value)
            {
                Settings.CheckGameAssets = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public bool HideLauncher
    {
        get => Settings.HideLauncher;
        set
        {
            if (Settings.HideLauncher != value)
            {
                Settings.HideLauncher = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public string JavaPath
    {
        get => Settings.JavaPath;
        set
        {
            if (Settings.JavaPath != value)
            {
                Settings.JavaPath = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public bool GameLogToLauncher
    {
        get => Settings.GameLogToLauncher;
        set
        {
            if (Settings.GameLogToLauncher != value)
            {
                Settings.GameLogToLauncher = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public bool SetMaxRam
    {
        get => Settings.SetMaxRam;
        set
        {
            if (Settings.SetMaxRam != value)
            {
                Settings.SetMaxRam = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public uint MaxRam
    {
        get => Settings.MaxRam;
        set
        {
            if (Settings.MaxRam != value)
            {
                Settings.MaxRam = value;
                this.RaisePropertyChanged();
                this.RaisePropertyChanged(nameof(MaxRamPercent));
            }
        }
    }

    public uint MaxRamPercent
    {
        get => (uint)(((MaxRam - MIN_RAM) * 100) /((MAX_RAM- MIN_RAM)));
        set => MaxRam = (uint)((value * (MAX_RAM - MIN_RAM)) / 100 + MIN_RAM);
    }

    public readonly long MAX_RAM = GC.GetGCMemoryInfo().TotalAvailableMemoryBytes / 1024 / 1024;
    public const uint MIN_RAM = 256;

    public LogEventLevel FileLogEventLevel
    {
        get => Settings.FileLogEventLevel;
        set
        {
            if (Settings.FileLogEventLevel != value)
            {
                Settings.FileLogEventLevel = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public LogEventLevel ConsoleLogEventLevel
    {
        get => Settings.ConsoleLogEventLevel;
        set
        {
            if (Settings.ConsoleLogEventLevel != value)
            {
                Settings.ConsoleLogEventLevel = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public bool SetMaxLog
    {
        get => Settings.SetMaxLog;
        set
        {
            if (Settings.SetMaxLog != value)
            {
                Settings.SetMaxLog = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public uint MaxLog
    {
        get => Settings.MaxLog;
        set
        {
            if (Settings.MaxLog != value)
            {
                Settings.MaxLog = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public bool FullScreen
    {
        get => Settings.FullScreen;
        set
        {
            if (Settings.FullScreen != value)
            {
                if (value && Settings.CustomSize) CustomSize = !value;
                Settings.FullScreen = value;

                this.RaisePropertyChanged();
            }
        }
    }

    public bool CustomSize
    {
        get => Settings.CustomSize;
        set
        {
            if (Settings.CustomSize != value)
            {
                if (value && Settings.FullScreen) FullScreen = !value;
                Settings.CustomSize = value;

                this.RaisePropertyChanged();
            }
        }
    }

    public uint ClientWidth
    {
        get => Settings.ClientWidth;
        set
        {
            if (Settings.ClientWidth != value)
            {
                Settings.ClientWidth = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public uint ClientHeight
    {
        get => Settings.ClientHeight;
        set
        {
            if (Settings.ClientHeight != value)
            {
                Settings.ClientHeight = value;
                this.RaisePropertyChanged();
            }
        }
    }

    public ICommand Update { get; }

    #endregion
}
