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

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

        private Dictionary<string, string> AlgorithmNames = new()
        {
            { "Kyber 512 (1)", "Kyber512" },
            { "Kyber 768 (3)", "Kyber768" },
            { "Kyber 1024 (5)", "Kyber1024" }
        };

        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 kem2 = new KEM(AlgorithmNames[SelectedAlgorithm]);
            CiphertextLength = $"{kem2.CiphertextLength} B";
            SharedSecretLength = $"{kem2.SharedSecretLength} B";
            SecretKeyLength = $"{kem2.SecretKeyLength} B";
            PublicKeyLength = $"{kem2.PublicKeyLength} B";

            Kyber kem;

            switch (AlgorithmNames[SelectedAlgorithm])
            {
                default:
                    kem = Kyber.Create512();
                    break;
                case "Kyber512":
                    kem = Kyber.Create512();
                    break;
                case "Kyber768":
                    kem = Kyber.Create768();
                    break;
                case "Kyber1024":
                    kem = Kyber.Create1024();
                    break;
                
            }

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

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

            UpdateAll();
        }

        private void TestKeypair(Kyber 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;
                (var publicKey, var privateKey) = kem.CCAKEM_KeyGen();
                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(Kyber kem, System.Diagnostics.Process proc)
        {
            long prev = GC.GetTotalAllocatedBytes(true);

            (var publicKey, var privateKey) = kem.CCAKEM_KeyGen();

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

            for (int i = 0; i < Runs; i++)
            {
                var avgStart = DateTime.Now;
                (var c, var K) = kem.CCAKEM_Enc(publicKey);
                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(Kyber kem, System.Diagnostics.Process proc)
        {
            long prev = GC.GetTotalAllocatedBytes(true);

            (var publicKey, var privateKey) = kem.CCAKEM_KeyGen();
            (var c, var K) = kem.CCAKEM_Enc(publicKey);

            AllSucceeded = true;

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

            for (int i = 0; i < Runs; i++)
            {
                var avgStart = DateTime.Now;
                var KO = kem.CCAKEM_Dec(c, privateKey);
                DecapsRuns.Add((float)(DateTime.Now - avgStart).TotalMilliseconds);
                AllSucceeded &= Enumerable.SequenceEqual(K, KO);
            }

            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());
        }
    }
}

