///
/// 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>BusFunctionsExample.cs</file_name>
///
/// <summary>Definition of bus hardware specific functions</summary>
///
/// <date>29-10-2024</date>
///
/// <author>Michael Milbradt<author>
///

using MenuUtils;
using Nlc;
using System.Text;

namespace BusFunctionsExample
{
    public static class BusFunctions
    {
        /// <summary>
        /// Helper function for comparing BusHardwareIds
        /// </summary>
        /// <param name="busHardwareId">First bus hardware id</param>
        /// <param name="otherBusHardwareId">Second bus hardware id</param>
        /// <returns>true if equals, false otherwise</returns>
        public static bool IsSameBusHardwareId(BusHardwareId busHardwareId, BusHardwareId otherBusHardwareId)
        {
            // compare BH and device id parts by hand
            // since there is no working equal inteface generated
            if ((busHardwareId.getBusHardware() == otherBusHardwareId.getBusHardware()) &&
                        (busHardwareId.getHardwareSpecifier() == otherBusHardwareId.getHardwareSpecifier()) &&
                        (busHardwareId.getExtraHardwareSpecifier() == otherBusHardwareId.getExtraHardwareSpecifier()) &&
                        (busHardwareId.getName() == otherBusHardwareId.getName()) &&
                        (busHardwareId.getProtocol() == otherBusHardwareId.getProtocol()))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Retrieve list of available bus hardware and store to ctx.OpenableBusHardwareIds
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void ScanBusHardware(Context ctx)
        {
            ctx.WaitForUserConfirmation = false;

            ResultBusHwIds result = ctx.NanolibAccessor.listAvailableBusHardware();
            if (result.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during bus scan: ", result.getError());
                return;
            }

            ctx.ScannedBusHardwareIds = result.getResult();

            if (ctx.ScannedBusHardwareIds.Count == 0)
            {
                StringUtils.HandleErrorMessage(ctx, "No bus hardware found. Please check your cabling, driver and/or devices.");
                return;
            }

            ctx.OpenableBusHardwareIds = Menu.GetOpenableBusHwIds(ctx);
        }

        /// <summary>
        /// Open the selected bus hardware (ctx.SelectedOption)
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void OpenBusHardware(Context ctx)
        {
            ctx.WaitForUserConfirmation = false;

            if (ctx.OpenableBusHardwareIds.Count == 0)
            {
                StringUtils.HandleErrorMessage(ctx, "No bus hardware available. Please do a scan first.");
                return;
            }

            // get selected option
            int index = ctx.SelectedOption - 1;
            BusHardwareId busHwId = ctx.OpenableBusHardwareIds[index];

            // check if already open
            foreach (var openBusHwId in ctx.OpenBusHardwareIds)
            {
                if (openBusHwId.Equals(busHwId))
                {
                    StringUtils.HandleErrorMessage(ctx, $"Bus hardware {busHwId.getName()} already open.");
                    return;
                }
            }

            BusHardwareOptions busHwOptions = BusHardwareOptionsHelper.CreateBusHardwareOptions(busHwId);
            ResultVoid resultVoid = ctx.NanolibAccessor.openBusHardwareWithProtocol(busHwId, busHwOptions);
            if (resultVoid.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during openBusHardware: ", resultVoid.getError());
                return;
            }

            ctx.OpenBusHardwareIds.Add(busHwId);

            // update available bus hardware ids for opening
            ctx.OpenableBusHardwareIds = Menu.GetOpenableBusHwIds(ctx);
        }

        /// <summary>
        /// Close the selected bus hardware (ctx.SelectedOption)
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void CloseBusHardware(Context ctx)
        {
            ctx.WaitForUserConfirmation = false;
            int index = ctx.SelectedOption;

            if (ctx.OpenBusHardwareIds.Count == 0)
            {
                StringUtils.HandleErrorMessage(ctx, "No open bus hardware found.");
                return;
            }

            BusHardwareId closeBusHardwareId = ctx.OpenBusHardwareIds[index - 1];

            var deviceHandleIt = ctx.ConnectedDeviceHandles.FindIndex(e => IsSameBusHardwareId(ctx.NanolibAccessor.getDeviceId(e).getResult().getBusHardwareId(), closeBusHardwareId));
            if (deviceHandleIt != -1)
            {
                if (ctx.ActiveDevice.ToString() == ctx.ConnectedDeviceHandles[deviceHandleIt].ToString())
                {
                    ctx.ActiveDevice = new DeviceHandle();
                }

                ctx.ConnectedDeviceHandles.RemoveAt(deviceHandleIt);
            }

            var deviceIdIt = ctx.ConnectableDeviceIds.FindIndex(e => IsSameBusHardwareId(e.getBusHardwareId(), closeBusHardwareId));
            if (deviceIdIt != -1)
            {
                ctx.ConnectableDeviceIds.RemoveAt(deviceIdIt);
            }

            deviceIdIt = ctx.ScannedDeviceIds.FindIndex(e => IsSameBusHardwareId(e.getBusHardwareId(), closeBusHardwareId));
            if (deviceIdIt != -1)
            {
                ctx.ScannedDeviceIds.RemoveAt(deviceIdIt);
            }

            var resultVoid = ctx.NanolibAccessor.closeBusHardware(closeBusHardwareId);
            if (resultVoid.hasError())
            {
                StringUtils.HandleErrorMessage(ctx, "Error during closeBusHardware: ", resultVoid.getError());
                return;
            }

            var busHardwareIdIt = ctx.OpenBusHardwareIds.ToList<BusHardwareId>().FindIndex(e => IsSameBusHardwareId(e, closeBusHardwareId));
            if (busHardwareIdIt != -1)
            {
                ctx.OpenBusHardwareIds.RemoveAt(busHardwareIdIt);
            }

            if (ctx.OpenBusHardwareIds.Count == 0)
            {
                ctx.ScannedDeviceIds = new List<DeviceId>();
                ctx.ActiveDevice = new DeviceHandle();
            }

            ctx.OpenableBusHardwareIds = Menu.GetOpenableBusHwIds(ctx);
        }

        /// <summary>
        /// Close all open bus hardware
        /// </summary>
        /// <param name="ctx">Menu context</param>
        public static void CloseAllBusHardware(Context ctx)
        {
            ctx.WaitForUserConfirmation = false;
            StringBuilder sb = new StringBuilder();

            // check for open bus hardware
            if (!ctx.OpenBusHardwareIds.Any())
            {
                StringUtils.HandleErrorMessage(ctx, "No open bus hardware found.");
                return;
            }

            // go through all opened bus hardware
            foreach (var openBusHardwareId in ctx.OpenBusHardwareIds)
            {
                // update ctx.ConnectedDeviceHandles
                // remove connected device handles from OpenDeviceHandles
                // it will be removed from nanolib connection in next step
                var deviceHandle = ctx.ConnectedDeviceHandles.FirstOrDefault(e => 
                    openBusHardwareId.Equals(ctx.NanolibAccessor.getDeviceId(e).getResult().getBusHardwareId()));
                if (!deviceHandle.Equals(null))
                {
                    ctx.ConnectedDeviceHandles.Remove(deviceHandle);
                }

                // remove available device ids with matching BusHardwareId from ctx.ConnectableDeviceIds
                var deviceId = ctx.ConnectableDeviceIds.FirstOrDefault(e => 
                    openBusHardwareId.Equals(e.getBusHardwareId()));
                if (deviceId != null)
                {
                    ctx.ConnectableDeviceIds.Remove(deviceId);
                }

                // close bus hardware in nanolib
                ResultVoid resultVoid = ctx.NanolibAccessor.closeBusHardware(openBusHardwareId);
                if (resultVoid.hasError())
                {
                    sb.AppendLine(StringUtils.HandleErrorMessage(ctx, "Error during closeBusHardware: ", resultVoid.getError()));
                    continue;
                }
            }

            ctx.ErrorText = sb.ToString();
            ctx.OpenBusHardwareIds = new BusHWIdVector();

            // no bus hardware open -> clean scanned devices list
            ctx.ScannedDeviceIds = new List<DeviceId>();

            // update ctx.OpenableBusHardwareIds
            ctx.OpenableBusHardwareIds = Menu.GetOpenableBusHwIds(ctx);

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