///
/// Nanotec Nanolib example
/// Copyright (C) Nanotec GmbH & Co. KG - All Rights Reserved
///
/// This product includes software developed by the
/// Nanotec GmbH & Co. KG (http://www.nanotec.com/).
///
/// The Nanolib interface headers and the examples source code provided are 
/// licensed under the Creative Commons Attribution 4.0 Internaltional License. 
/// To view a copy of this license, 
/// visit https://creativecommons.org/licenses/by/4.0/ or send a letter to 
/// Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
///
/// The parts of the library provided in binary format are licensed under 
/// the Creative Commons Attribution-NoDerivatives 4.0 International License. 
/// To view a copy of this license, 
/// visit http://creativecommons.org/licenses/by-nd/4.0/ or send a letter to 
/// Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
///
/// This program is distributed in the hope that it will be useful,
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
///
/// <file_name>DeviceFunctionsExample.cs</file_name>
///
/// <summary>Definition of device specific functions</summary>
///
/// <date>29-10-2024</date>
///
/// <author>Michael Milbradt<author>
///

using MenuUtils;
using Nlc;
using System;
using System.Text;

namespace DeviceFunctionsExample
{
    public static class DeviceFunctions
    {
        /// <summary>
        /// Scans for valid devices on all opened bus hardware
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void ScanDevices(Context ctx) {
            ctx.WaitForUserConfirmation = false;
            bool found = false;
            ctx.ScannedDeviceIds.Clear();

            // no bus hardware
            if (ctx.OpenBusHardwareIds.Count == 0)
            {
                StringUtils.HandleErrorMessage(ctx, "No bus hardware available. Please scan and select a bus hardware first.");
                return;
            }

            // scan for every opened bushardware
            foreach (var openBusHardwareId in ctx.OpenBusHardwareIds)
            {
                Console.WriteLine($"Scan devices for {openBusHardwareId.getProtocol()} ({openBusHardwareId.getName()})");
                ResultDeviceIds resultDeviceIds = ctx.NanolibAccessor.scanDevices(openBusHardwareId, ctx.ScanBusCallback);
                if (resultDeviceIds.hasError())
                {
                    StringUtils.HandleErrorMessage(ctx, "Error during device scan: ", resultDeviceIds.getError());
                    continue;
                }

                if (resultDeviceIds.getResult().Any())
                {
                    found = true;
                    ctx.ScannedDeviceIds.AddRange(resultDeviceIds.getResult());
                }
            }

            if (!found)
            {
                StringUtils.HandleErrorMessage(ctx, "No devices found. Please check your cabling, driver(s) and/or device(s).");
                return;
            }

            // update ctx.ConnectableDeviceIds
            ctx.ConnectableDeviceIds = Menu.GetConnectableDeviceIds(ctx);
        }


        /// <summary>
        /// Adds device and connects to the selected device (ctx.SelectedOption) within Nanolib
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void ConnectDevice(Context ctx) {
            ctx.WaitForUserConfirmation = false;

            if (!ctx.ConnectableDeviceIds.Any())
            {
                StringUtils.HandleErrorMessage(ctx, "No device available. Please scan for devices first.");
                return;
            }

            // check if selected device is already connected
            int index = ctx.SelectedOption;
            DeviceId selectedDeviceId = ctx.ConnectableDeviceIds[index - 1];

            ResultDeviceHandle deviceHandleResult = ctx.NanolibAccessor.addDevice(selectedDeviceId);
            if (deviceHandleResult.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during ConnectDevice (addDevice): ", deviceHandleResult.getError());
                return;
            }

            DeviceHandle deviceHandle = deviceHandleResult.getResult();

            ResultVoid resultVoid = ctx.NanolibAccessor.connectDevice(deviceHandle);
            if (resultVoid.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during ConnectDevice: ", resultVoid.getError());
                ctx.NanolibAccessor.removeDevice(deviceHandle);
                return;
            }

            // store handle
            ctx.ConnectedDeviceHandles.Add(deviceHandle);

            // update availableDeviceIds
            ctx.ConnectableDeviceIds = Menu.GetConnectableDeviceIds(ctx);

            // update ctx.ActiveDevice to new connection
            ctx.ActiveDevice = deviceHandle;
        }

        /// <summary>
        /// Disconnect device and removes to the selected device (ctx.SelectedOption) within Nanolib
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void DisconnectDevice(Context ctx) {
            ctx.WaitForUserConfirmation = false;
            int index = ctx.SelectedOption;

            if (!ctx.ConnectedDeviceHandles.Any())
            {
                StringUtils.HandleErrorMessage(ctx, "No device connected.");
                return;
            }

            // Get selected device handle
            DeviceHandle closeDeviceHandle = ctx.ConnectedDeviceHandles[index - 1];

            // Disconnect device in nanolib
            ResultVoid resultVoid = ctx.NanolibAccessor.disconnectDevice(closeDeviceHandle);
            if (resultVoid.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during DisconnectDevice: ", resultVoid.getError());
                return;
            }

            // Remove device from nanolib
            resultVoid = ctx.NanolibAccessor.removeDevice(closeDeviceHandle);
            if (resultVoid.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during DisconnectDevice (RemoveDevice): ", resultVoid.getError());
                return;
            }

            // Update ctx.ConnectedDeviceHandles
            ctx.ConnectedDeviceHandles.RemoveAll(e => e.Equals(closeDeviceHandle));

            // Update ctx.ConnectableDeviceIds
            ctx.ConnectableDeviceIds = Menu.GetConnectableDeviceIds(ctx);

            // Clear ctx.ActiveDevice
            ctx.ActiveDevice = new DeviceHandle();
        }

        /// <summary>
        /// Select the device to use for all device specific functions in Nanolib
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void SelectActiveDevice(Context ctx) {
            ctx.WaitForUserConfirmation = false;

            int index = ctx.SelectedOption;

            if (!ctx.ConnectedDeviceHandles.Any())
            {
                StringUtils.HandleErrorMessage(ctx, "No connected devices. Connect a device first.");
                return;
            }

            ctx.ActiveDevice = ctx.ConnectedDeviceHandles[index - 1];
        }

        /// <summary>
        /// Reboots the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void RebootDevice(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle()))
            {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultVoid rebootResult = ctx.NanolibAccessor.rebootDevice(ctx.ActiveDevice);
            if (rebootResult.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during RebootDevice: ", rebootResult.getError());
            }
        }

        /// <summary>
        /// Update the firmware of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void UpdateFirmware(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle()))
            {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            string inputPath;
            string deviceName = ctx.NanolibAccessor.getDeviceName(ctx.ActiveDevice).getResult();
            string firmwareBuildId = ctx.NanolibAccessor.getDeviceFirmwareBuildId(ctx.ActiveDevice).getResult();

            Console.WriteLine($"Current firmware Build Id: {firmwareBuildId}");
            Console.WriteLine($"Please enter the full path to the firmware file");
            Console.WriteLine($"(e.g. {deviceName}-FIR-vXXXX-BXXXXXXX.fw): ");

            do
            {
                inputPath = Console.ReadLine();
            } while (string.IsNullOrEmpty(inputPath));

            Console.WriteLine("Do not interrupt the data connection or switch off the power until the update process has been finished!");
            ResultVoid uploadResult = ctx.NanolibAccessor.uploadFirmwareFromFile(ctx.ActiveDevice, inputPath, ctx.DataTransferCallback);
            if (uploadResult.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during updateFirmware: ", uploadResult.getError());
                return;
            }
            Console.WriteLine();
        }

        /// <summary>
        /// Update the bootloader of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void UpdateBootloader(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle()))
            {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            string inputPath;
            string deviceName = ctx.NanolibAccessor.getDeviceName(ctx.ActiveDevice).getResult();
            string bootloaderBuildId = ctx.NanolibAccessor.getDeviceBootloaderBuildId(ctx.ActiveDevice).getResult();
            string bootloaderVersion = (ctx.NanolibAccessor.getDeviceBootloaderVersion(ctx.ActiveDevice).getResult() >> 16).ToString();

            Console.WriteLine($"Current bootloader Build Id: {bootloaderBuildId}");
            Console.WriteLine($"Bootloader version: {bootloaderVersion}");
            Console.WriteLine("Please enter the full path to the bootloader file: ");

            do
            {
                inputPath = Console.ReadLine();
            } while (string.IsNullOrEmpty(inputPath));

            Console.WriteLine("Do not interrupt the data connection or switch off the power until the update process has been finished!");
            ResultVoid uploadResult = ctx.NanolibAccessor.uploadBootloaderFromFile(ctx.ActiveDevice, inputPath, ctx.DataTransferCallback);
            if (uploadResult.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during updateBootloader: ", uploadResult.getError());
                return;
            }
            Console.WriteLine();
        }

        /// <summary>
        /// Upload a compiled NanoJ binary to the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void UploadNanoJ(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle()))
            {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            string inputPath;
            Console.WriteLine("Please enter the full path to the NanoJ file (e.g. vmmcode.usr): ");

            do
            {
                inputPath = Console.ReadLine();
            } while (string.IsNullOrEmpty(inputPath));

            Console.WriteLine("Do not interrupt the data connection or switch off the power until the update process has been finished!");
            ResultVoid uploadResult = ctx.NanolibAccessor.uploadNanoJFromFile(ctx.ActiveDevice, inputPath, ctx.DataTransferCallback);
            if (uploadResult.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during uploadNanoJ: ", uploadResult.getError());
                return;
            }
            Console.WriteLine();
            Console.WriteLine("Use runNanoJ menu option to re-start the uploaded NanoJ program.");
        }

        /// <summary>
        /// Executes the NanoJ program on the current active device if available
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void RunNanoJ(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle()))
            {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            // check for errors
            ResultInt errorResult = ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, OdIndices.OdNanoJError);
            if (errorResult.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during runNanoJ: ", errorResult.getError());
                return;
            }

            if (errorResult.getResult() != 0)
            {
                StringUtils.HandleErrorMessage(ctx, "Failed to start NanoJ program - NanoJ error code is set: ", errorResult.getResult().ToString());
                return;
            }

            // write start to NanoJ control object (0x2300)
            ResultVoid writeNumberResult = ctx.NanolibAccessor.writeNumber(ctx.ActiveDevice, 0x1, OdIndices.OdNanoJControl, 32);
            if (writeNumberResult.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during runNanoJ: ", writeNumberResult.getError());
                return;
            }

            // start might take some time (up to 200ms)
            Thread.Sleep(250);

            // check if running and no error
            errorResult = ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, OdIndices.OdNanoJError);
            if (errorResult.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during runNanoJ: ", errorResult.getError());
                return;
            }

            if (errorResult.getResult() != 0)
            {
                StringUtils.HandleErrorMessage(ctx, "Error during runNanoJ - program exited with error: ", errorResult.getResult().ToString());
                return;
            }

            // check if program is still running, stopped or has error
            ResultInt readNumberResult = ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, OdIndices.OdNanoJStatus);
            if (readNumberResult.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during runNanoJ: ", readNumberResult.getError());
                return;
            }

            if (readNumberResult.getResult() == 0)
            {
                Console.WriteLine("NanoJ program stopped ...");
            }
            else if (readNumberResult.getResult() == 1)
            {
                Console.WriteLine("NanoJ program running ...");
            }
            else
            {
                Console.WriteLine("NanoJ program exited with error.");
            }
        }

        /// <summary>
        /// Stops the NanoJ program on the current active device if available
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void StopNanoJ(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle()))
            {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultVoid writeNumberResult = ctx.NanolibAccessor.writeNumber(ctx.ActiveDevice, 0x00, OdIndices.OdNanoJControl, 32);
            if (writeNumberResult.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during stopNanoJ: ", writeNumberResult.getError());
                return;
            }

            // stop might take some time
            Thread.Sleep(50);

            ResultInt readNumberResult = ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, OdIndices.OdNanoJStatus);
            if (readNumberResult.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during stopNanoJ: ", readNumberResult.getError());
                return;
            }

            if (readNumberResult.getResult() == 0)
            {
                Console.WriteLine("NanoJ program stopped ...");
            }
            else if (readNumberResult.getResult() == 1)
            {
                Console.WriteLine("NanoJ program still running ...");
            }
            else
            {
                Console.WriteLine($"NanoJ program exited with error: {ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, OdIndices.OdNanoJError).getResult()}");
            }
        }

        /// <summary>
        /// Read and output the device vendor id of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetDeviceVendorId(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultInt resultInt = ctx.NanolibAccessor.getDeviceVendorId(ctx.ActiveDevice);

            if (resultInt.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during getDeviceVendorId: ", resultInt.getError());
                return;
            }

            Console.WriteLine($"Device vendor id = '{resultInt.getResult()}'");
        }

        /// <summary>
        /// Read and output the product code of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetDeviceProductCode(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultInt resultInt = ctx.NanolibAccessor.getDeviceProductCode(ctx.ActiveDevice);

            if (resultInt.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during getDeviceProductCode: ", resultInt.getError());
                return;
            }

            Console.WriteLine($"Device product code = '{resultInt.getResult()}'");
        }

        /// <summary>
        /// Read and output the device name of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetDeviceName(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultString resultString = ctx.NanolibAccessor.getDeviceName(ctx.ActiveDevice);

            if (resultString.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during getDeviceName: ", resultString.getError());
                return;
            }

            Console.WriteLine($"Device name = '{resultString.getResult()}'");
        }

        /// <summary>
        /// Read and output the hardware version of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetDeviceHardwareVersion(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultString resultString = ctx.NanolibAccessor.getDeviceHardwareVersion(ctx.ActiveDevice);

            if (resultString.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during getDeviceHardwareVersion: ", resultString.getError());
                return;
            }

            Console.WriteLine($"Device hardware version = '{resultString.getResult()}'");
        }

        /// <summary>
        /// Read and output the firmware build id of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetDeviceFirmwareBuildId(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultString resultString = ctx.NanolibAccessor.getDeviceFirmwareBuildId(ctx.ActiveDevice);

            if (resultString.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during getDeviceFirmwareBuildId: ", resultString.getError());
                return;
            }

            Console.WriteLine($"Device firmware build id = '{resultString.getResult()}'");
        }

        /// <summary>
        /// Read and output the bootloader build id of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetDeviceBootloaderBuildId(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultString resultString = ctx.NanolibAccessor.getDeviceBootloaderBuildId(ctx.ActiveDevice);

            if (resultString.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during getDeviceBootloaderBuildId: ", resultString.getError());
                return;
            }

            Console.WriteLine($"Device bootloader build id = '{resultString.getResult()}'");
        }

        /// <summary>
        /// Read and output the serial number of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetDeviceSerialNumber(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultString resultString = ctx.NanolibAccessor.getDeviceSerialNumber(ctx.ActiveDevice);

            if (resultString.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during getDeviceSerialNumber: ", resultString.getError());
                return;
            }

            Console.WriteLine($"Device serial number = '{resultString.getResult()}'");
        }

        /// <summary>
        /// Read and output the device unique id of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetDeviceUid(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle()))
            {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultArrayByte resultArray = ctx.NanolibAccessor.getDeviceUid(ctx.ActiveDevice);

            if (resultArray.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during getDeviceUid: " + resultArray.getError());
                return;
            }

            // Convert byte array to hex string
            StringBuilder s = new StringBuilder(resultArray.getResult().Count() * 2); // Two digits per byte
            const string hex = "0123456789ABCDEF";
            
            foreach (byte c in resultArray.getResult())
            {
                s.Append(hex[c / 16]);
                s.Append(hex[c % 16]);
            }

            Console.WriteLine($"Device unique id = '{s}'");
        }

        /// <summary>
        /// Read and output the bootloader version of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetDeviceBootloaderVersion(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultInt resultInt = ctx.NanolibAccessor.getDeviceBootloaderVersion(ctx.ActiveDevice);

            if (resultInt.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during getDeviceBootloaderVersion: ", resultInt.getError());
                return;
            }

            Console.WriteLine($"Device bootloader version = '{resultInt.getResult() >> 16}'");
        }

        /// <summary>
        /// Read and output the hardware group of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetDeviceHardwareGroup(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultInt resultInt = ctx.NanolibAccessor.getDeviceHardwareGroup(ctx.ActiveDevice);

            if (resultInt.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during getDeviceHardwareGroup: ", resultInt.getError());
                return;
            }

            Console.WriteLine($"Device hardware group = '{resultInt.getResult()}'");
        }

        /// <summary>
        /// Read and output the connection state of the current active device
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetConnectionState(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "No active device set. Select an active device first.");
                return;
            }

            ResultConnectionState resultConState = ctx.NanolibAccessor.getConnectionState(ctx.ActiveDevice);

            if (resultConState.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during getConnectionState: ", resultConState.getError());
                return;
            }

            string connectionState = "unknown";

            switch (resultConState.getResult()) {
                case DeviceConnectionStateInfo.Connected:
                    connectionState = "Connected";
                    break;
                case DeviceConnectionStateInfo.Disconnected:
                    connectionState = "Disconnected";
                    break;
                case DeviceConnectionStateInfo.ConnectedBootloader:
                    connectionState = "Connected to bootloader";
                    break;
                default:
                    connectionState = "unknown";
                    break;
            }

            Console.WriteLine($"Device connection state = '{connectionState}'");
        }

        /// <summary>
        /// Read and output error-stack
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void GetErrorFields(Context ctx) {
            ctx.WaitForUserConfirmation = true;

            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "", "No active device set. Select an active device first.");
                return;
            }

            ResultInt errorNumberResult = ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, OdIndices.OdErrorCount);
            if (errorNumberResult.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during getErrorField: ", errorNumberResult.getError());
                return;
            }

            if (errorNumberResult.getResult() == 0) {
                Console.WriteLine("\nCurrently there are no errors.");
                return;
            }

            byte numberOfErrors = (byte)(errorNumberResult.getResult());
            Console.WriteLine($"Currently there are {numberOfErrors} errors.\n");

            // go through every error field (max 8)
            for (byte i = 1; i <= numberOfErrors; i++) {
                OdIndex currentErrorField = new OdIndex(OdIndices.OdErrorStackIndex, i);

                errorNumberResult = ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, currentErrorField);
                if (errorNumberResult.hasError()) {
                    StringUtils.HandleErrorMessage(ctx, "Error during getErrorField: ", errorNumberResult.getError());
                    return;
                }

                // decode error field
                Console.WriteLine($"- Error Number [{i}] = {ErrorHelper.GetErrorNumberString(errorNumberResult.getResult())}");
                Console.WriteLine($"- Error Class  [{i}] = {ErrorHelper.GetErrorClassString(errorNumberResult.getResult())}");
                Console.WriteLine($"- Error Code   [{i}] = {ErrorHelper.GetErrorCodeString(errorNumberResult.getResult())}\n");
            }
        }

        /// <summary>
        /// Reset encoder resolution interfaces, reset drive mode selection and finally restore all default parameters
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void RestoreDefaults(Context ctx) {
            if (ctx.ActiveDevice.Equals(new DeviceHandle())) {
                StringUtils.HandleErrorMessage(ctx, "", "No active device set. Select an active device first.");
                return;
            }

            // read Additional position encoder resolution interface #1
            var posEncoderResResult = ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, OdIndices.OdPosEncoderIncrementsInterface1);
            if (!posEncoderResResult.hasError()) {
                Console.WriteLine($"Position encoder resolution - encoder increments feedback interface #1 = {posEncoderResResult.getResult()}");
            }

            // read Additional position encoder resolution interface #2
            posEncoderResResult = ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, OdIndices.OdPosEncoderIncrementsInterface2);
            if (!posEncoderResResult.hasError()) {
                Console.WriteLine($"Position encoder resolution - encoder increments feedback interface #2 = {posEncoderResResult.getResult()}");
            }

            // read Additional position encoder resolution interface #3
            posEncoderResResult = ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, OdIndices.OdPosEncoderIncrementsInterface3);
            if (!posEncoderResResult.hasError()) {
                Console.WriteLine($"Position encoder resolution - encoder increments feedback interface #3 = {posEncoderResResult.getResult()}");
            }

            // set interface values to zero
            ctx.NanolibAccessor.writeNumber(ctx.ActiveDevice, 0, OdIndices.OdPosEncoderIncrementsInterface1, 32);
            ctx.NanolibAccessor.writeNumber(ctx.ActiveDevice, 0, OdIndices.OdPosEncoderIncrementsInterface2, 32);
            ctx.NanolibAccessor.writeNumber(ctx.ActiveDevice, 0, OdIndices.OdPosEncoderIncrementsInterface3, 32);

            var subModeSelectResult = ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, OdIndices.OdMotorDriveSubmodeSelect);
            Console.WriteLine($"Motor drive submode select = {subModeSelectResult.getResult()}");

            // set motor drive submode select to zero
            ctx.NanolibAccessor.writeNumber(ctx.ActiveDevice, 0, OdIndices.OdMotorDriveSubmodeSelect, 32);

            // Save all parameters to non-volatile memory
            ResultVoid writeResult = ctx.NanolibAccessor.writeNumber(ctx.ActiveDevice, 1702257011, OdIndices.OdStoreAllParams, 32);
            if (writeResult.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during restoreDefaults: ", writeResult.getError());
                return;
            }

            // wait until write has completed
            do {
                var storeResult = ctx.NanolibAccessor.readNumber(ctx.ActiveDevice, OdIndices.OdStoreAllParams);
                if (storeResult.getResult() == 1) {
                    break;
                }
            } while (true);

            // reboot current active device
            Console.WriteLine("Rebooting ...");
            ResultVoid rebootResult = ctx.NanolibAccessor.rebootDevice(ctx.ActiveDevice);
            if (rebootResult.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during restoreDefaults: ", rebootResult.getError());
            }

            // Restore all default parameters
            Console.WriteLine("Restoring all default parameters ...");
            writeResult = ctx.NanolibAccessor.writeNumber(ctx.ActiveDevice, 1684107116, OdIndices.OdRestoreAllDefParams, 32);
            if (writeResult.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during restoreDefaults: ", writeResult.getError());
                return;
            }

            // Restore tuning default parameters
            Console.WriteLine("Restoring tuning default parameters ...");
            writeResult = ctx.NanolibAccessor.writeNumber(ctx.ActiveDevice, 1684107116, OdIndices.OdRestoreTuningDefParams, 32);
            if (writeResult.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during restoreDefaults: ", writeResult.getError());
                return;
            }

            // reboot current active device
            Console.WriteLine("Rebooting ...");
            rebootResult = ctx.NanolibAccessor.rebootDevice(ctx.ActiveDevice);
            if (rebootResult.hasError()) {
                StringUtils.HandleErrorMessage(ctx, "Error during restoreDefaults: ", rebootResult.getError());
            }

            Console.WriteLine("All done. Check for errors.");
        }
    }
}
