using NixUniversalSDK;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace NixExampleConsole
{
    internal class Program
    {
        #region Static / constants
        private const string LogPrefix = "## ";
        private static bool IsRunning = true;
        private const int MaxListedDeviceCount = 10;

        /// <summary>
        /// Main entry point
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // Activate the NixUniversalSDK
            _ = NixUniversalSDK.LicenseManager.Activate(
                options: LicenseKey.Options,
                signature: LicenseKey.Signature);

            // Start the program
            Task.Run(() => new Program().StartMenuAsync());

            // Keep window open while `IsRunning` flag is set
            while (IsRunning) { Thread.Sleep(1); }
        }
        #endregion

        #region Constructors
        Program()
        {
            // Create DeviceScanner instance and set up its events
            Scanner = new DeviceScanner();
            Scanner.ScannerCreated += OnScannerCreated;
            Scanner.ScannerStarted += OnScannerStarted;
            Scanner.ScannerStopped += OnScannerStopped;
            Scanner.ScanResult += OnScanResult;

            // Initialize (async)
            ScannerInitTask = Scanner.InitializeAsync();
        }
        #endregion

        #region IDeviceScanner / device search
        private readonly IDeviceScanner Scanner;
        public Task<DeviceScannerState> ScannerInitTask;
        private readonly ConcurrentDictionary<string, IDeviceCompat> Devices = new ConcurrentDictionary<string, IDeviceCompat>();
        private List<IDeviceCompat> SortedDevices => Devices.Values
            .Where(x => x != null)
            .OrderBy(x => x.Rssi).Reverse()
            .ToList();

        private void OnScannerCreated(
            object sender,
            ScannerCreatedEventArgs args)
        {
            Console.WriteLine($"{LogPrefix}OnScannerCreated with state {args.State}");
        }

        private void OnScannerStarted(
            object sender,
            EventArgs args)
        {
            Console.WriteLine($"{LogPrefix}OnScannerStarted");
        }

        private void OnScannerStopped(
            object sender,
            EventArgs args)
        {
            Console.WriteLine($"{LogPrefix}OnScannerStopped");
        }

        private void OnScanResult(
            object sender,
            ScanResultEventArgs args)
        {
            if (args.Device is IDeviceCompat device)
            {
                Console.WriteLine($"{LogPrefix}Found {device?.Id} ({device?.Name}) at RSSI {device?.Rssi}");
                
                // Add / update the device in the list
                Devices[device.Id] = device;
            }
        }

        public async Task RunDeviceSearchAsync(long timeoutMs = DeviceScanner.DefaultGeneralScanPeriodMs)
        {
            // Clear the list
            Devices.Clear();
            
            // Check DeviceScanner state
            if (Scanner.State != DeviceScannerState.Idle)
            {
                Console.WriteLine($"{LogPrefix}Error: cannot run scanner because its state is {Scanner.State}");
                Console.WriteLine($"{LogPrefix}Listing USB devices anyways ...");
                await UpdateUsbDeviceListAsync();
            }
            else
            {
                // Start the search and watch for the scanner to stop
                var completionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
                Scanner.ScannerStopped += Local_OnScannerStopped;
                Scanner.Start(timeoutMs);
                await completionSource.Task;

                void Local_OnScannerStopped(object sender, EventArgs args)
                {
                    // Scanner has stopped
                    // Remove this event
                    if (Scanner != null) { Scanner.ScannerStopped -= Local_OnScannerStopped; }

                    // Complete the task
                    completionSource.TrySetResult(true);
                }
            }
        }

        public async Task UpdateUsbDeviceListAsync()
        {
            // Clear the list
            Devices.Clear();
            
            // Run USB listing
            var usbDeviceList = await Scanner.ListUsbDevicesAsync();
            if (usbDeviceList != null && usbDeviceList.Any())
            {
                // Add/update all seen USB devices in the devices list
                foreach (var usbDevice in usbDeviceList)
                {
                    if (usbDevice is IDeviceCompat nonNullDevice)
                    {
                        Devices[nonNullDevice.Id] = nonNullDevice;
                    }
                }
            }
        }
        #endregion

        #region IDeviceCompat / selected device
        private IDeviceCompat _Device = null;
        public IDeviceCompat Device
        {
            get => _Device;
            set
            {
                // Remove device event handlers from old value
                RemoveDeviceEvents();

                // Update the stored value
                _Device = value;

                // Add device handlers to new value
                AddDeviceEvents();
            }
        }

        private void AddDeviceEvents()
        {
            if (Device != null)
            {
                Device.Connected += OnConnected;
                Device.Disconnected += OnDisconnected;
                Device.BatteryStateChanged += OnBatteryStateChanged;
                Device.ExtPowerStateChanged += OnExtPowerStateChanged;
            }
        }

        private void RemoveDeviceEvents()
        {
            if (Device != null)
            {
                Device.Connected -= OnConnected;
                Device.Disconnected -= OnDisconnected;
                Device.BatteryStateChanged -= OnBatteryStateChanged;
                Device.ExtPowerStateChanged -= OnExtPowerStateChanged;
            }
        }

        private void OnConnected(
            object sender,
            EventArgs args)
        {
            Console.WriteLine($"{LogPrefix}OnConnected()");

            // Go to the 'Connected' menu
            ConnectedMenuAsync();
        }

        private void OnDisconnected(
            object sender,
            DeviceStatusArgs args)
        {
            Console.WriteLine($"{LogPrefix}OnDisconnected() with status {args.Status}");

            // Remove device event handlers
            RemoveDeviceEvents();

            // Cancel pending console reads
            CancelConsoleReadTask();

            // Back to the start menu
            StartMenuAsync();
        }

        private void OnBatteryStateChanged(
            object sender,
            BatteryStateEventArgs args)
        {
            Console.WriteLine($"{LogPrefix}OnBatteryStateChanged to {args.NewState}");
        }

        private void OnExtPowerStateChanged(
            object sender,
            ExtPowerStateEventArgs args)
        {
            Console.WriteLine($"{LogPrefix}OnExtPowerStateChanged to {args.NewState}");
        }

        private async void ConnectAsync(IDeviceCompat device)
        {
            Console.WriteLine($"Connecting to {device.Id} ({device.Name}) ...");

            // Save reference to device
            Device = device;

            // Open connection                        
            var connectResult = await device.ConnectAsync();
            if (connectResult == DeviceStatus.Success)
            {
                // Successful connection will trigger OnConnected
                // OnConnected loads the 'Connected' menu
                // ...
            }
            else
            {
                Debug.WriteLine($"Error: connection failed with status {connectResult}");
                
                // Failed connection will trigger OnDisconnected
                // OnDisconnected goes back to the 'Start' menu
                // ...
            }
        }

        private async Task MeasureAsync()
        {
            if (Device is null)
            {
                Console.WriteLine("Error: Device is null");
            }
            else
            {
                // Await measurement results
                Console.WriteLine("Measuring ...");
                var result = await Device.MeasureAsync();

                // Note: the above command will report measurements in all
                // supported modes by the device. It is also possible to
                // specify specific scan modes. This is usually not necessary.
                // Some cases where this could be desired include:

                // ** On Spectro 2 devices running firmware F1.0.0 **:
                // M0 and M1 are captured in two separate measurements. If
                // only one of M0 or M1 is desired, requesting only one will
                // speed up the scan cycle.

                // Specific example cases are listed below:

                // EXAMPLE: request scan only for M2 mode:
                // Device.MeasureAsync(ScanMode.M2)

                // EXAMPLE: request scan for M0 and M1 mode:
                // Device.MeasureAsync(ScanMode.M0, ScanMode.M1)

                // EXAMPLE: request scan for all supported modes:
                // Device.MeasureAsync()

                Console.WriteLine($"Measurement result had status {result.Status}");

                // Example showing how to parse `IMeasurementData` and get
                // spectral (if available) and CIELAB values.
                // This block loops over all returned measurement modes and
                // all ref white options available (tailor to your needs)
                if (result.Status == CommandStatus.Success && result.Measurements != null)
                {
                    Console.WriteLine();
                    Console.WriteLine("Scan values");
                    Console.WriteLine("-----------");

                    foreach (IMeasurementData measurement in result.Measurements.Values)
                    {
                        // Print spectral value if available
                        if (measurement.ProvidesSpectral)
                        {
                            var spectralData = measurement.SpectralData;
                            PrintSpectral(spectralData);
                        }

                        // Print out CIELAB values
                        foreach (var reference in measurement.SupportedReferences)
                        {
                            var colorData = measurement.ToColorData(reference, ColorType.CIELAB);
                            PrintCielab(colorData);
                        }
                    }

                    // Print scan info for first measurement (details should be same across measurements)
                    PrintScanInfo(result.Measurements.Values.First());
                }
            }
        }

        private void PrintSpectral(ISpectralData data)
        {
            if (data is null)
            {
                Console.WriteLine("Error spectral data was null");
                return;
            }

            for (var i = 0; i < data.Lambda.Length; i++)
            {
                Console.WriteLine($"{data.Mode.GetFullName()} " +
                    $"R({data.Lambda[i]}): " +
                    $"{data.Value[i]}");
            }
        }

        private void PrintCielab(IColorData data)
        {
            if (data is null)
            {
                Console.WriteLine("Error: color data was null");
                return;
            }
            
            if (data.Type != ColorType.CIELAB)
            {
                data.ConvertTo(ColorType.CIELAB);
            }

            Console.WriteLine($"{data.Mode} CIELAB " +
                $"({data.Reference.GetFullName()}): " +
                $"{data.Value[0]}, " +
                $"{data.Value[1]}, " +
                $"{data.Value[2]}");
        }

        private void PrintScanInfo(IMeasurementData data)
        {
            Console.WriteLine();
            Console.WriteLine("Scan info");
            Console.WriteLine("---------");
            
            if (data is null)
            {
                Console.WriteLine("Error: data is null");
            }
            else
            {
                // Temperature formatter
                var temperatureFormat = (data?.TReal == true) ?
                    "{0:f2} °C" :
                    "{0:f0} (raw)";

                // Device name
                Console.WriteLine($"Device name: {data.DeviceType.GetFullName()}");

                // Reference temperature
                if (data?.TRef is float tRef)
                {
                    Console.WriteLine($"Reference temperature: {string.Format(temperatureFormat, tRef)}");
                }
                
                // Scan temperature
                if (data?.TScan is float tScan)
                {
                    Console.WriteLine($"Scan temperature: {string.Format(temperatureFormat, tScan)}");
                }

                // Temperature compensation
                if (data?.TCompEnabled is bool tCompEnabled)
                {
                    var label = tCompEnabled ? "enabled" : "disabled";
                    Console.WriteLine($"Temperature compensation: {label}");
                }

                // Tile normalization
                if (data?.TileEnabled is bool tileEnabled)
                {
                    var label = tileEnabled ? "enabled" : "false";
                    Console.WriteLine($"Tile normalization: {label}");
                }
            }
        }

        private void PrintDeviceInfo()
        {
            Console.WriteLine();
            Console.WriteLine("Device info");
            Console.WriteLine("-----------");

            if (Device is null)
            {
                Console.WriteLine("Error: Device is null");
            }
            else
            {
                // Device name
                Console.WriteLine($"Device name: {Device.Name}");

                // Hardware version
                Console.WriteLine($"Hardware version: {Device.HardwareVersion.String}");

                // Firmware version
                Console.WriteLine($"Firmware version: {Device.FirmwareVersion.String}");

                // Serial number
                Console.WriteLine($"Serial number: {Device.SerialNumber}");

                // Battery level
                var batteryString = Device.BatteryLevel is null ?
                    "unavailable" :
                    $"{Device.BatteryLevel}%";
                Console.WriteLine($"Battery level: {batteryString}");

                // External power connected
                var powerString = Device.ExtPowerState ? "true" : "false";
                Console.WriteLine($"External power connected: {powerString}");

                // Optional device properties
                // Scan counter
                if (Device.ScanCount is uint scanCount)
                {
                    Console.WriteLine($"Scan counter: {scanCount}");
                }

                // Field calibration info
                if (Device?.ReferenceDate is DateTime referenceDate)
                {
                    // Note: if field calibration has never been performed,
                    // reference date will be Unix epoch (January 1, 1970, 00:00 UTC)
                    var dateString = referenceDate.Year < 2000 ?
                        "unavailable" :
                        referenceDate.ToString();
                    Console.WriteLine($"Field calibration date: {dateString}");

                    var dueString = Device.FieldCalibrationDue == true ? "true" : "false";
                    Console.WriteLine($"Field calibration due: {dueString}");
                }
            }
        }

        private async Task RunFieldCalibrationAsync(string tileString)
        {
            if (Device?.IsTileStringValid(tileString) != true)
            {
                Console.WriteLine("Error: Entered tile string is invalid");
            } 
            else
            {
                Console.WriteLine("Tile string is valid. Place the unit on the reference tile scanning area.");
                Console.WriteLine("Press any key to continue ...");
                try
                {
                    _ = await ReadKeyAsync(ConsoleReadTokenSource.Token);
                    Console.WriteLine("Running field calibration ...");
                    var result = await Device?.RunFieldCalibrationAsync(tileString);
                    Console.WriteLine($"Field calibration result had status {result?.Status ?? CommandStatus.ErrorInternal}");
                }
                catch (OperationCanceledException)
                {
                    return;
                }
            }
        }
        #endregion

        #region Menus
        private CancellationTokenSource ConsoleReadTokenSource = new CancellationTokenSource();
        private Task<string> ConsoleReadTask = null;
        private void CancelConsoleReadTask()
        {
            // Request cancellation
            ConsoleReadTokenSource.Cancel();
            ConsoleReadTokenSource.Dispose();

            // Clean up and 'reset' token source
            ConsoleReadTokenSource = new CancellationTokenSource();
        }

        private async Task<string> ReadLineAsync(CancellationToken cancellationToken = default)
        {
            ConsoleReadTask = ConsoleReadTask ?? Task.Run(() => Console.ReadLine());
            await Task.WhenAny(ConsoleReadTask, Task.Delay(-1, cancellationToken));
            cancellationToken.ThrowIfCancellationRequested();

            string result = await ConsoleReadTask;
            ConsoleReadTask = null;

            return result;
        }

        private async Task<string> ReadKeyAsync(CancellationToken cancellationToken = default)
        {
            ConsoleReadTask = ConsoleReadTask ?? Task.Run(() => Console.ReadKey().ToString());
            await Task.WhenAny(ConsoleReadTask, Task.Delay(-1, cancellationToken));
            cancellationToken.ThrowIfCancellationRequested();

            string result = await ConsoleReadTask;
            ConsoleReadTask = null;

            return result;
        }

        private async void StartMenuAsync()
        {
            // Wait for DeviceScanner to initialize
            _ = await ScannerInitTask;

            Console.WriteLine();
            Console.WriteLine("Start Menu/Enter an option:");
            Console.WriteLine("1: Run search for Bluetooth and USB devices");
            Console.WriteLine("2: List USB devices only");
            Console.WriteLine("3: Reset the `DeviceScanner`");
            Console.WriteLine("4: Display license info");
            Console.WriteLine("5: Change license key");
            Console.WriteLine("Q: Quit");
            Console.Write("> ");

            try
            {
                var input = (await ReadLineAsync(ConsoleReadTokenSource.Token)).ToLower();
                switch (input)
                {
                    case "1":
                        Console.Write($"Search period in milliseconds ({DeviceScanner.DefaultGeneralScanPeriodMs})? ");
                        var periodMs = DeviceScanner.DefaultGeneralScanPeriodMs;
                        try
                        {
                            periodMs = long.Parse(Console.ReadLine());
                        }
                        catch { }

                        Console.WriteLine($"Starting search for {periodMs} ms ...");
                        await RunDeviceSearchAsync(periodMs);
                        ListDevicesMenuAsync();
                        break;
                    case "2":
                        await UpdateUsbDeviceListAsync();
                        ListDevicesMenuAsync();
                        break;
                    case "3":
                        ScannerInitTask = Scanner.InitializeAsync();
                        StartMenuAsync();
                        break;
                    case "4":
                        PrintLicenseInfo();
                        StartMenuAsync();
                        break;
                    case "5":
                        ChangeLicenseKeyAsync();
                        break;
                    case "q":
                        Console.WriteLine("Goodbye");
                        IsRunning = false;
                        break;
                    default:
                        Console.WriteLine("Invalid option!");
                        StartMenuAsync();
                        break;
                }
            }
            catch (OperationCanceledException)
            {
                return;
            }
        }

        private async void ListDevicesMenuAsync()
        {
            Console.WriteLine();
            Console.WriteLine($"List Menu/Found {Devices.Count} device(s)");
            if (Devices.Count == 0)
            {
                // Go back
                StartMenuAsync();
            }
            else
            {
                var limit = Math.Min(Devices.Count, MaxListedDeviceCount);                
                Console.WriteLine($"Showing top {MaxListedDeviceCount} ... Select an entry to open a connection:");
                for (var i = 0; i < limit; i++)
                {
                    if (SortedDevices[i] is IDeviceCompat device)
                    {
                        Console.WriteLine($"{i}: {device.Id} ({device.Name}) at RSSI {device.Rssi}");
                    }
                }
                Console.WriteLine("B: Go back to Main Menu");
                Console.Write("> ");
                
                try
                {
                    var input = (await ReadLineAsync(ConsoleReadTokenSource.Token)).ToLower();
                    try
                    {
                        var selectedIndex = int.Parse(input);
                        if (selectedIndex < limit && SortedDevices[selectedIndex] is IDeviceCompat device)
                        {
                            ConnectAsync(device);
                        }
                        else
                        {
                            throw new FormatException();
                        }
                    }
                    catch (FormatException)
                    {
                        // Non-integer or out of range value entered
                        if (input == "b")
                        {
                            // Return to start
                            StartMenuAsync();
                        }
                        else
                        {
                            // Try again
                            Console.WriteLine("Invalid option!");
                            ListDevicesMenuAsync();
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                    return;
                }
            }            
        }

        private async void ConnectedMenuAsync()
        {
            Console.WriteLine();
            Console.WriteLine($"Connected Menu/{Device.Id} ({Device.Name}) is {Device.State}");
            Console.WriteLine("Enter option:");
            Console.WriteLine("1. Display device info");
            Console.WriteLine("2. Run measurements for all supported modes");
            if (Device.HasOptions())
            {
                Console.WriteLine("3. Toggle device options");
            }
            if (Device.SupportsFieldCalibration)
            {
                Console.WriteLine("4. Run field calibration");
            }
            Console.WriteLine("B. Disconnect");
            Console.Write("> ");

            try
            {
                var input = (await ReadLineAsync(ConsoleReadTokenSource.Token)).ToLower();
                switch (input)
                {
                    case "1":
                        PrintDeviceInfo();
                        ConnectedMenuAsync();
                        break;
                    case "2":
                        await MeasureAsync();
                        ConnectedMenuAsync();
                        break;
                    case "3":
                        OptionsMenuAsync();
                        break;
                    case "4":
                        Console.Write("Enter tile string: ");
                        try
                        {
                            var enteredTileString = await ReadLineAsync(ConsoleReadTokenSource.Token);
                            await RunFieldCalibrationAsync(enteredTileString);
                            ConnectedMenuAsync();
                        }
                        catch (OperationCanceledException)
                        {
                            return;
                        }
                        break;
                    case "b":
                        if (Device != null)
                        {
                            // Disconnect (see also OnDisconnected which will navigate back to start)
                            Device?.Disconnect();
                        }
                        else
                        {
                            // Simply navigate back now
                            StartMenuAsync();
                        }
                        break;
                    default:
                        Console.WriteLine("Invalid option!");
                        ConnectedMenuAsync();
                        break;
                }
            }
            catch (OperationCanceledException)
            {
                return;
            }
        }

        private async void OptionsMenuAsync()
        {
            if (Device is null)
            {
                Console.WriteLine("Options Menu/Error: device is null ... restarting...");
                StartMenuAsync();
                return;
            }
            
            Console.WriteLine();
            Console.WriteLine("Options Menu/Select an option to toggle its state:");
            if (Device.SupportsRgbFeedback)
            {
                var rgbStateString = Device.RgbFeedbackEnabled ? "ON" : "OFF";
                Console.WriteLine($"1. Toggle RGB feedback (currently {rgbStateString})");
            }
            if (Device.SupportsHapticFeedback)
            {
                var hapticStateString = Device.HapticFeedbackEnabled ? "ON" : "OFF";
                Console.WriteLine($"2. Toggle haptic feedback (currently {hapticStateString})");
            }
            if (Device.SupportsFieldCalibration)
            {
                var tileNormalizationState = Device.FieldCalibrationEnabled ? "ON" : "OFF";
                Console.WriteLine($"3. Toggle tile normalization (currently {tileNormalizationState})");
            }
            if (Device.SupportsTemperatureCompensation)
            {
                var temperatureCompensationState = Device.TemperatureCompensationEnabled ? "ON" : "OFF";
                Console.WriteLine($"4. Toggle temperature compensation (currently {temperatureCompensationState})");
            }
            Console.WriteLine("B. Go back to Connected Menu");
            Console.Write("> ");

            try
            {
                var input = (await ReadLineAsync(ConsoleReadTokenSource.Token)).ToLower();
                switch (input)
                {
                    case "1":
                        // Toggle RGB feedback state
                        _ = await Device.SetRgbFeedbackEnabledAsync(
                            !Device.RgbFeedbackEnabled);
                        OptionsMenuAsync();
                        break;
                    case "2":
                        // Toggle haptic feedback state
                        _ = await Device.SetHapticFeedbackEnabledAsync(
                            !Device.HapticFeedbackEnabled);
                        OptionsMenuAsync();
                        break;
                    case "3":
                        // Toggle tile normalization state
                        _ = await Device.SetFieldCalibrationEnabledAsync(
                            !Device.FieldCalibrationEnabled);
                        OptionsMenuAsync();
                        break;
                    case "4":
                        // Toggle temperature compensation state
                        _ = await Device.SetTemperatureCompensationEnabledAsync(
                            !Device.TemperatureCompensationEnabled);
                        OptionsMenuAsync();
                        break;
                    case "b":
                        ConnectedMenuAsync();
                        break;
                    default:
                        Console.WriteLine("Invalid option!");
                        OptionsMenuAsync();
                        break;
                }
            }
            catch (OperationCanceledException)
            {
                return;
            }
        }

        private void PrintLicenseInfo()
        {
            Console.WriteLine("");
            Console.WriteLine("License Info");
            Console.WriteLine("------------");

            // License state
            Console.WriteLine($"State: {LicenseManager.State}");

            // UUID
            Console.WriteLine($"UUID: {LicenseManager.Uuid}");

            // Expiry
            Console.WriteLine($"Expiry: {LicenseManager.Expiry}");

            // Allocations
            Console.WriteLine($"Allocation codes: {string.Join(", ", LicenseManager.Allocations)}");

            // Allowed devices
            Console.WriteLine($"Device types: {string.Join(", ", LicenseManager.AllowedDeviceTypes)}");

            // Features
            Console.WriteLine($"Features: {string.Join(", ", LicenseManager.Features)}");

            // SDK version
            Console.WriteLine($"NixUniversalSDK version: {LicenseManager.LibraryVersion}");
        }

        private async void ChangeLicenseKeyAsync()
        {
            string options = string.Empty;
            Console.WriteLine("");
            Console.WriteLine("Enter options string, or blank for default...");
            Console.Write("> ");
            try
            {
                options = await ReadLineAsync(ConsoleReadTokenSource.Token);
            }
            catch (OperationCanceledException)
            {
                options = string.Empty;
            }
            if (options.Length == 0)
            {
                options = LicenseKey.Options;
            }

            string signature = string.Empty;
            Console.WriteLine("");
            Console.WriteLine("Enter signature string, or blank for default...");
            Console.Write("> ");
            try
            {
                signature = await ReadLineAsync(ConsoleReadTokenSource.Token);
            }
            catch (OperationCanceledException)
            {
                signature = string.Empty;
            }
            if (signature.Length == 0)
            {
                signature = LicenseKey.Signature;
            }

            Console.WriteLine("");
            Console.WriteLine("Using:");
            Console.WriteLine($"Options: '{options}'");
            Console.WriteLine($"Signature: '{signature}'");

            // Run the activation command
            _ = LicenseManager.Activate(options, signature);

            // Show the new license status
            PrintLicenseInfo();
            Console.WriteLine("");

            // Reset the DeviceScanner, since the feature set has changed
            ScannerInitTask = Scanner.InitializeAsync();

            // Return to the start menu
            StartMenuAsync();
        }
        #endregion
    }
}
