using MauiTestApp.Command;
using MauiTestApp.Graphing;
using OpenQuantumSafe;

namespace MauiTestApp.ViewModels
{
    public class KEMViewModel : ViewModelBase
    {
        public KEMViewModel()
        {
            TestCommand = new RelayCommand(Test, CanTest);
        }

        private Dictionary<string, string> AlgorithmNames = new()
        {
            { "Kyber 512 (1)", "Kyber512" },
            { "Kyber 768 (3)", "Kyber768" },
            { "Kyber 1024 (5)", "Kyber1024" },
            { "Kyber 512 90s (1)", "Kyber512-90s" },
            { "Kyber 768 90s (3)", "Kyber768-90s" },
            { "Kyber 1024 90s (5)", "Kyber1024-90s" },
            { "McEliece (1)", "Classic-McEliece-348864" },
            { "McEliece fast (1)", "Classic-McEliece-348864f" },
            { "McEliece  (2)", "Classic-McEliece-460896" },
            { "McEliece fast (2)", "Classic-McEliece-460896f" },
            { "McEliece  (3)", "Classic-McEliece-6688128" },
            { "McEliece fast (3)", "Classic-McEliece-6688128f" },
            { "McEliece (4)", "Classic-McEliece-6960119" },
            { "McEliece fast (4)", "Classic-McEliece-6960119f" },
            { "McEliece (5)", "Classic-McEliece-8192128" },
            { "McEliece fast (5)", "Classic-McEliece-8192128f" },
            { "HQC-128 (1)", "HQC-128" },
            { "HQC-192 (3)", "HQC-192" },
            { "HQC-256 (5)", "HQC-256" },
            { "BIKE-L1", "BIKE-L1" },
            { "BIKE-L3", "BIKE-L3" }
        };

        public string[] Algorithms => AlgorithmNames.Keys.ToArray();

        private string _SelectedAlgorithm { get; set; } = "";
        public string SelectedAlgorithm
        {
            get => _SelectedAlgorithm;
            set
            {
                _SelectedAlgorithm = value;
                OnPropertyChanged(nameof(SelectedAlgorithm));
                TestCommand.RaiseCanExecuteChanged();
            }
        }
        
        public event EventHandler? OnDataUpdate;

        public RelayCommand TestCommand { get; private set; }

        private bool CanTest() => AlgorithmNames.ContainsKey(SelectedAlgorithm);

        public string CiphertextLength { get; set; } = "";
        public string SharedSecretLength { get; set; } = "";
        public string SecretKeyLength { get; set; } = "";
        public string PublicKeyLength { get; set; } = "";

        private const int Runs = 100;

        public bool AllSucceeded { get; set; } = true;
        public string SuccessMesssage => AllSucceeded ? "All shared secrets were equal." : "Shared secrets failed!";

        public RunData KeypairData { get; set; } = new();
        public RunData EncapsData { get; set; } = new();
        public RunData DecapsData { get; set; } = new();

        public List<float> KeypairRuns { get; set; } = new();
        public List<float> EncapsRuns { get; set; } = new();
        public List<float> DecapsRuns { get; set; } = new();

        private double GetCPUUsage(TimeSpan cpuDiff, TimeSpan diff)
        {
            return 100.0 / Environment.ProcessorCount * cpuDiff.Ticks / diff.Ticks;
        }

        private double GetTimeAvg(TimeSpan diff) => diff.TotalMilliseconds / Runs;

        private double GetMemoryAvg(long prev) => (GC.GetTotalAllocatedBytes(true) - (double)prev) / Runs;

        private void Test()
        {
            KeypairRuns.Clear();
            EncapsRuns.Clear();
            DecapsRuns.Clear();

            var kem = new KEM(AlgorithmNames[SelectedAlgorithm]);
            CiphertextLength =   $"{kem.CiphertextLength} B";
            SharedSecretLength = $"{kem.SharedSecretLength} B";
            SecretKeyLength =    $"{kem.SecretKeyLength} B";
            PublicKeyLength =    $"{kem.PublicKeyLength} B";

            var proc = System.Diagnostics.Process.GetCurrentProcess();

            TestKeypair(kem, proc);
            TestEncaps(kem, proc);
            TestDecaps(kem, proc);

            UpdateAll();
        }

        private void TestKeypair(KEM kem, System.Diagnostics.Process proc)
        {
            long prev = GC.GetTotalAllocatedBytes(true);

            var startCPU = proc.TotalProcessorTime;
            var start = DateTime.Now;

            for (int i = 0; i < Runs; i++)
            {
                var avgStart = DateTime.Now;
                kem.keypair(out var publicKeyA, out var privateKeyA);
                KeypairRuns.Add((float)(DateTime.Now - avgStart).TotalMilliseconds);
            }

            var endCPU = proc.TotalProcessorTime;
            var end = DateTime.Now;

            KeypairData.SetMemory(GetMemoryAvg(prev));
            KeypairData.SetCPU(GetCPUUsage(endCPU - startCPU, end - start));
            KeypairData.SetTime(GetTimeAvg(end - start));
        }

        private void TestEncaps(KEM kem, System.Diagnostics.Process proc)
        {
            long prev = GC.GetTotalAllocatedBytes(true);

            kem.keypair(out var publicKeyA, out var privateKeyA);

            var startCPU = proc.TotalProcessorTime;
            var start = DateTime.Now;

            for (int i = 0; i < Runs; i++)
            {
                var avgStart = DateTime.Now;
                kem.encaps(out var ciphertextB, out var sharedSecretB, publicKeyA);
                EncapsRuns.Add((float)(DateTime.Now - avgStart).TotalMilliseconds);
            }

            var endCPU = proc.TotalProcessorTime;
            var end = DateTime.Now;

            EncapsData.SetMemory(GetMemoryAvg(prev));
            EncapsData.SetCPU(GetCPUUsage(endCPU - startCPU, end - start));
            EncapsData.SetTime(GetTimeAvg(end - start));
        }

        private void TestDecaps(KEM kem, System.Diagnostics.Process proc)
        {
            long prev = GC.GetTotalAllocatedBytes(true);

            kem.keypair(out var publicKeyA, out var privateKeyA);
            kem.encaps(out var ciphertextB, out var sharedSecretB, publicKeyA);

            AllSucceeded = true;

            var startCPU = proc.TotalProcessorTime;
            var start = DateTime.Now;

            for (int i = 0; i < Runs; i++)
            {
                var avgStart = DateTime.Now;
                kem.decaps(out var sharedSecretA, ciphertextB, privateKeyA);
                DecapsRuns.Add((float)(DateTime.Now - avgStart).TotalMilliseconds);
                AllSucceeded &= Enumerable.SequenceEqual(sharedSecretA, sharedSecretB);
            }

            var endCPU = proc.TotalProcessorTime;
            var end = DateTime.Now;

            DecapsData.SetMemory(GetMemoryAvg(prev));
            DecapsData.SetCPU(GetCPUUsage(endCPU - startCPU, end - start));
            DecapsData.SetTime(GetTimeAvg(end - start));
        }

        private void UpdateAll()
        {
            OnPropertyChanged(nameof(KeypairData));
            OnPropertyChanged(nameof(EncapsData));
            OnPropertyChanged(nameof(DecapsData));

            OnPropertyChanged(nameof(CiphertextLength));
            OnPropertyChanged(nameof(SharedSecretLength));
            OnPropertyChanged(nameof(SecretKeyLength));
            OnPropertyChanged(nameof(PublicKeyLength));

            OnPropertyChanged(nameof(SuccessMesssage));

            OnDataUpdate?.Invoke(null, new());
        }
    }
}
