using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Reflection;
using ximc;

namespace test_CSharp
{
    class testapp
    {
        private static API.LoggingCallback callback;
        private static void MyLog (API.LogLevel loglevel, string message, IntPtr user_data)
        {
            Console.WriteLine("MyLog {0}: {1}", loglevel, message);
        }

        public static void print_status(status_t status)
        {
            Console.WriteLine("Status:");
            Console.Write(" rpm: {0},", status.CurSpeed);
            Console.Write(" pos: {0},", status.CurPosition);
            Console.Write(" upwr: {0},", status.Upwr);
            Console.Write(" ipwr: {0},", status.Ipwr);
            Console.Write(" flags: {0}", status.Flags);
            Console.Write(" mvsts: {0}", status.MvCmdSts);
            if ((status.Flags & (uint)Flags.STATE_ALARM) != 0)
            {
                Console.Write(", ALARM");
            }

            if ((status.Flags & (uint)Flags.STATE_ERRC) != 0)
            {
                Console.Write(", ERRC");
            }

            if ((status.Flags & (uint)Flags.STATE_ERRD) != 0)
            {
                Console.Write(", ERRD");
            }

            Console.Write("\n");
        }

        public static string error_string(Result result)
        {
            switch (result)
            {
                case Result.error:
                    return "error";
                case Result.not_implemented:
                    return "not implemented";
                case Result.no_device:
                    return "no device";
                case Result.value_error:
                    return "value error";
                default:
                    return "success";
            };
        }

        private static void get_status_and_print(int device)
        {
            Result result;
            status_t status;
            if ((result = API.get_status(device, out status)) != Result.ok) {
                Console.WriteLine("Error getting status: {0}", error_string(result));
            }
            else {
                print_status(status);
            }
        }

        public static void print_status_calb(status_calb_t status)
        {
            Console.WriteLine("Status calb:");
            Console.Write(" rpm: {0},", status.CurSpeed);
            Console.Write(" pos: {0},", status.CurPosition);
            Console.Write(" upwr: {0},", status.Upwr);
            Console.Write(" ipwr: {0},", status.Ipwr);
            Console.Write(" flags: {0},", status.Flags);
            Console.Write(" mvsts: {0}", status.MvCmdSts);
            if ((status.Flags & (uint)Flags.STATE_ALARM) != 0)
            {
                Console.Write(", ALARM");
            }

            if ((status.Flags & (uint)Flags.STATE_ERRC) != 0)
            {
                Console.Write(", ERRC");
            }

            if ((status.Flags & (uint)Flags.STATE_ERRD) != 0)
            {
                Console.Write(", ERRD");
            }

            Console.Write("\n");
        }

        public static IntPtr search_devices(out int device_count)
        {
            Console.WriteLine("\nSearching for devices...");
            const int probe_flags = (int)(Flags.ENUMERATE_PROBE | Flags.ENUMERATE_NETWORK);
            const string enumerate_hints = "addr=";
            IntPtr devenum = API.enumerate_devices(probe_flags, enumerate_hints);
            if (devenum == IntPtr.Zero)
            {
                Console.WriteLine("Error searching for devices");
                device_count = 0;
                return devenum;
            }

            device_count = API.get_device_count(devenum);
            if (device_count < 1)
            {
                Console.WriteLine("No devices found");
                return devenum;
            }

            Console.WriteLine("Found {0} devices", device_count + 1);
            return devenum;
        }

        public static void print_device_list(IntPtr devenum, int device_count)
        {
            Console.WriteLine("\nFound devices list:");
            int i = 0;
            for (i = 0; i < device_count; ++i)
            {
                string device_name = API.get_device_name(devenum, i);
                Console.WriteLine(" #{0}. {1}", i + 1, device_name);
            }
            Console.WriteLine(" #{0}. virtual", i + 1);
        }

        public static string create_virtual_device_name()
        {
            string home_path = Environment.GetEnvironmentVariable("USERPROFILE");
            string file_path = Path.Combine(home_path, "virt_controller.bin");
            string device_name = "xi-emu:///" + file_path;
            Console.WriteLine("Virtual controller file path: '{0}'", file_path);
            return device_name;
        }

        public static string get_device_name_from_list(IntPtr devenum, int device_count)
        {
            while (true)
            {
                print_device_list(devenum, device_count);
                Console.WriteLine("\nWhen using an Ethernet (TCP protocol) connection, multi-user interaction with the device is possible. In this case, the same device may be controlled simultaneously from multiple sources, which can potentially lead to control conflicts.");
                Console.WriteLine("Enter device number from the list:");
                string input = Console.ReadLine();
                int number;
                if (!int.TryParse(input, out number))
                {
                    continue;
                }

                if (1 <= number && number <= device_count)
                {
                    string device_name = API.get_device_name(devenum, number - 1);
                    Console.WriteLine("Selected device: '{0}'", device_name);
                    return device_name;
                }

                if (number == device_count + 1)
                {
                    return create_virtual_device_name();
                }
            }
        }

        public static string choose_device()
        {
            int device_count;
            IntPtr devenum = search_devices(out device_count);
            string device_name;
            if (device_count < 1)
            {
                Console.WriteLine("The real controller is not found or busy with another app.");
                Console.WriteLine("The virtual controller is opening to check the operation of the library...");
                device_name = create_virtual_device_name();
            }
            else
            {
                device_name = get_device_name_from_list(devenum, device_count);
            }

            if (devenum != IntPtr.Zero)
            {
                API.free_enumerate_devices(devenum);
            }
            return device_name;
        }

        public static void print_motor_type(int device)
        {
            /*
            Reading motor parameters and output its type.
            MotorType = 0 motor not defined or not connected.
            MotorType = 1 step.
            MotorType = 2 DC.
            MotorType = 3 BLDC.
            */

            motor_settings_t motor_settings;
            Result result = API.get_motor_settings(device, out motor_settings);
            if (result != Result.ok)
            {
                Console.WriteLine("Error getting motor settings: {0}", error_string(result));
            }
            else
            {
                string motor_type;
                switch (motor_settings.MotorType)
                {
                    case 1:
                        motor_type = "step";
                        break;
                    case 2:
                        motor_type = "DC";
                        break;
                    case 3:
                        motor_type = "BLDC";
                        break;
                    default:
                        motor_type = "motor not defined or not connected";
                        break;
                };
                Console.WriteLine("Motor type: {0} ({1})", motor_settings.MotorType, motor_type);
            }
        }

        static void Main(string[] args)
        {
            int device = -1;
            Result result;
            try
            {
                callback = new API.LoggingCallback(MyLog);
                //API.set_logging_callback(callback, IntPtr.Zero);

                StringBuilder ximc_version_str = new StringBuilder(32);
                API.ximc_version(ximc_version_str);
                Console.WriteLine("This is a ximc test program\nlibximc version {0}", ximc_version_str);

                string device_name = choose_device();
                Console.WriteLine("\nOpening device...");
                device = API.open_device(device_name);
                if (device == -1)
                {
                    Console.WriteLine("Error opening device\nPress Enter to exit...");
                    Console.ReadLine();
                    return;
                }

                // Read device status
                get_status_and_print(device);

                // Read device information
                device_information_t di;
                result = API.get_device_information(device, out di);
                if (result != Result.ok)
                {
                    Console.WriteLine("Error getting device information: {0}\n", error_string(result));
                }
                else
                {
                    Console.WriteLine("Device information:\n manufacturer: {0}, id: {1}, product: {2}, version: {3}.{4}.{5}",
                        di.Manufacturer, di.ManufacturerId, di.ProductDescription, di.Major, di.Minor, di.Release);
                }

                // Send "zero" command to device
                result = API.command_zero(device);
                if (result != Result.ok)
                {
                    Console.WriteLine("Error zeroing: {0}", error_string(result));
                }

                // Read device status
                get_status_and_print(device);

                // Read engine settings
                engine_settings_t engine_settings;
                result = API.get_engine_settings(device, out engine_settings);
                if (result != Result.ok)
                {
                    Console.WriteLine("Error getting engine settings: {0}", error_string(result));
                }
                else
                {
                    Console.WriteLine("Engine settings:\n voltage: {0}, current: {1}, speed {2}",
                        engine_settings.NomVoltage, engine_settings.NomCurrent, engine_settings.NomSpeed);
                }

                // Read electromechanical settings
                emf_settings_t emf_settings;
                result = API.get_emf_settings(device, out emf_settings);
                if (result != Result.ok)
                {
                    Console.WriteLine("Error getting electromechanical settings: {0}", error_string(result));
                }
                else
                {
                    Console.WriteLine("Electromechanical settings:\n Km: {0}, L: {1}, R: {2}",
                        emf_settings.Km, emf_settings.L, emf_settings.R);
                }

                // Read engine advanced settings
                engine_advanced_setup_t eas_settings = new engine_advanced_setup_t();
                result = API.get_engine_advanced_setup(device, out eas_settings);
                if (result != Result.ok)
                {
                    Console.WriteLine("Error getting engine advanced settings: {0}", error_string(result));
                }
                else
                {
                    Console.WriteLine("Engine advanced settings:\n 1 {0}, 2 {1}, 3 {2}",
                        eas_settings.stepcloseloop_Kw, eas_settings.stepcloseloop_Kp_low, eas_settings.stepcloseloop_Kp_high);
                }

                print_motor_type(device);

                // Read engine calb settings
                calibration_t calibration = new calibration_t();
                calibration.A = 1;
                calibration.MicrostepMode = Flags.MICROSTEP_MODE_FULL;
                engine_settings_calb_t engine_settings_calb;
                result = API.get_engine_settings_calb(device, out engine_settings_calb, ref calibration);
                if (result != Result.ok)
                {
                    Console.WriteLine("Error getting engine calb settings: {0}", error_string(result));
                }
                else
                {
                    Console.WriteLine("Engine calb settings:\n voltage: {0}, current: {1}, speed: {2:F3}",
                        engine_settings_calb.NomVoltage, engine_settings_calb.NomCurrent, engine_settings_calb.NomSpeed);
                }

                int sec = 2;
                Console.WriteLine("\nEngine rotating left for {0} seconds...\n", sec);
                result = API.command_left(device);
                if (result != Result.ok)
                {
                    Console.WriteLine("Error in command left: {0}", error_string(result));
                }
                else
                {
                    System.Threading.Thread.Sleep(sec * 1000);
                }

                // Read device status
                get_status_and_print(device);

                // Read position in user units
                get_position_calb_t get_pos;
                result = API.get_position_calb(device, out get_pos, ref calibration);
                if (result != Result.ok)
                {
                    Console.WriteLine("Error getting position in user units: {0}", error_string(result));
                }
                else
                {
                    Console.WriteLine("Position in user units:\n pos: {0:F3}", get_pos.Position);
                }

                // Read device calibrated status
                status_calb_t status_calb;
                result = API.get_status_calb(device, out status_calb, ref calibration);
                if (result != Result.ok)
                {
                    Console.WriteLine("Error getting calibrated state: {0}", error_string(result));
                }
                else
                {
                    print_status_calb(status_calb);
                }

                Console.WriteLine("\nStopping engine...\n");
                result = API.command_stop(device);
                if (result != Result.ok)
                {
                    Console.WriteLine("Error in command stop: {0}", error_string(result));
                }

                // Read device status
                get_status_and_print(device);

                Console.WriteLine("\nSleeping for {0} seconds...\n", sec);
                System.Threading.Thread.Sleep(sec * 1000);
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception " + e.Message);
                Console.WriteLine(e.StackTrace.ToString());
            }
            finally
            {
                if (device != -1)
                    API.close_device(ref device);
            }
            Console.WriteLine("Done\nPress Enter to exit...");
            Console.ReadLine();
        }
    }
}
