using Euresys.EGrabber;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace egrabber
{
    class Program
    {
        const int BUFFER_COUNT = 5;
        const int FRAME_COUNT = 20;

        static void SampleBasicQueries(EGrabberCameraInfo cameraInfo)
        {
            Console.WriteLine();
            Console.WriteLine("SampleBasicQueries");
            Console.WriteLine("------------------");
            Console.WriteLine();
            using (var grabber = new EGrabber(cameraInfo))
            {
                Console.WriteLine("DEVICE_INFO_DISPLAYNAME = {0}",
                    grabber.Device.GetInfo<string>(DEVICE_INFO_CMD.DEVICE_INFO_DISPLAYNAME));
                Console.WriteLine("TL_INFO_PATHNAME = {0}",
                    grabber.System.GetInfo<string>(TL_INFO_CMD.TL_INFO_PATHNAME));

                string[] features = grabber.Remote.Features();
                Console.WriteLine("Remote device features:");
                foreach (string f in features)
                {
                    Console.WriteLine(" - {0}", f);
                }
                Console.WriteLine();

                if (features.Contains("ExposureMode"))
                {
                    string[] entries = grabber.Remote.EnumEntries("ExposureMode");
                    Console.WriteLine("ExposureMode entries:");
                    foreach (string ee in entries)
                    {
                        Console.WriteLine(" - {0}", ee);
                    }
                    Console.WriteLine();
                }

                Console.WriteLine("Grabber config: {0}x{1}, pixel format: {2}", grabber.Width, grabber.Height, grabber.PixelFormat);
            }
        }

        static void SampleScopedBuffer(EGrabberCameraInfo cameraInfo)
        {
            Console.WriteLine();
            Console.WriteLine("SampleScopedBuffer");
            Console.WriteLine("------------------");
            Console.WriteLine();
            using (var grabber = new EGrabber(cameraInfo))
            {
                // grabber.RunScript("config.js");
                grabber.ReallocBuffers(BUFFER_COUNT);
                grabber.Start(FRAME_COUNT);
                for (ulong frame = 0; frame < FRAME_COUNT; ++frame)
                {
                    using (var buffer = new ScopedBuffer(grabber, 1000))
                    {
                        var dataSize = buffer.GetInfo<ulong>(BUFFER_INFO_CMD.BUFFER_INFO_DATA_SIZE);
                        var timestamp = buffer.GetInfo<ulong>(BUFFER_INFO_CMD.BUFFER_INFO_TIMESTAMP);
                        var imgPtr = buffer.GetInfo<IntPtr>(BUFFER_INFO_CMD.BUFFER_INFO_BASE);
                        var intFormat = buffer.GetInfo<ulong>(BUFFER_INFO_CMD.BUFFER_INFO_PIXELFORMAT);
                        Console.WriteLine("Acquisition Index {0} dataSize={1} timestamp={2}.{3} imagePointer={4}",
                            frame, dataSize, timestamp / 1000000, timestamp % 1000000, imgPtr);

                        using (var bgr = buffer.Convert("BGR8"))
                        {
                            Console.WriteLine("bgr data: {0} ({1} bytes)", bgr.Pixels, bgr.BufferSize);
                        }
                    }
                }
            }
        }

        static void SampleProcessEventsAsync(EGrabberCameraInfo cameraInfo)
        {
            Console.WriteLine();
            Console.WriteLine("SampleProcessEventsAsync");
            Console.WriteLine("------------------------");
            Console.WriteLine();
            using (var grabber = new EGrabber(cameraInfo))
            {
                try
                {
                    grabber.EnableEvent(EventType.All);
                    grabber.Stream.Set("EventNotificationAll", true);
                    grabber.ReallocBuffers(BUFFER_COUNT);
                    grabber.RegisterEventCallback<NewBufferData>((g, data) =>
                    {
                        using (var buffer = new ScopedBuffer(g, data))
                        {
                            Console.WriteLine("task #{0}: {1} | NEW BUFFER", Task.CurrentId, data.Timestamp);
                        }
                    });
                    grabber.RegisterEventCallback<DataStreamData>((g, data) =>
                    {
                        Console.WriteLine("task #{0}: {1} | {2}", Task.CurrentId, data.Timestamp, data.NumId);
                    });
                    // More event type callbacks could be registered with:
                    //  - grabber.RegisterEventCallback<IoToolboxData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<CicData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<CxpInterfaceData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<DeviceErrorData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<CxpDeviceData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<RemoteDeviceData>((g, data) => {});
                    using (var cancelTokenSource = new CancellationTokenSource())
                    {
                        var ct = cancelTokenSource.Token;
                        Task task = grabber.ProcessEventsAsync(EventType.All, ct);
                        Console.WriteLine("Event processing task started");
                        grabber.Start();
                        Console.WriteLine("Grabber started, sleeping for 1 second...");
                        System.Threading.Thread.Sleep(1000);
                        Console.WriteLine("Grabber cleanup...");
                        grabber.Stop();
                        cancelTokenSource.Cancel();
                        task.Wait();
                        Console.WriteLine("Event processing task stopped");
                    }
                }
                finally
                {
                    grabber.Stream.Set("EventNotificationAll", false);
                }
            }
        }

        static void SampleProcessEventsManuallyInSingleTask(EGrabberCameraInfo cameraInfo)
        {
            Console.WriteLine();
            Console.WriteLine("SampleProcessEventsManuallyInSingleTask");
            Console.WriteLine("---------------------------------------");
            Console.WriteLine();
            using (var grabber = new EGrabber(cameraInfo))
            {
                try
                {
                    grabber.EnableEvent(EventType.All);
                    grabber.Stream.Set("EventNotificationAll", true);
                    grabber.ReallocBuffers(BUFFER_COUNT);
                    grabber.RegisterEventCallback<NewBufferData>((g, data) =>
                    {
                        using (var buffer = new ScopedBuffer(g, data))
                        {
                            Console.WriteLine("task #{0}: {1} | NEW BUFFER", Task.CurrentId, data.Timestamp);
                        }
                    });
                    grabber.RegisterEventCallback<DataStreamData>((g, data) =>
                    {
                        Console.WriteLine("task #{0}: {1} | {2}", Task.CurrentId, data.Timestamp, data.NumId);
                    });
                    // More event type callbacks could be registered with:
                    //  - grabber.RegisterEventCallback<IoToolboxData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<CicData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<CxpInterfaceData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<DeviceErrorData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<CxpDeviceData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<RemoteDeviceData>((g, data) => {});
                    using (var cancelTokenSource = new CancellationTokenSource())
                    {
                        var ct = cancelTokenSource.Token;
                        Task task = Task.Run(() =>
                        {
                            // Please note this code is voluntarily kept simple to avoid
                            // overloading the sample with unnecessary details;
                            // in production code, we would certainly need to add
                            // - proper exception handling around EGrabber.ProcessEvent
                            //   to make sure the Task is not terminated by the first
                            //   unexpected exception;
                            // - proper handling of Task cancellation so that the Task
                            //   can be gracefully terminated when requested by the user
                            //   even if it's busy waiting for an event
                            // This code is there to explain how to use the lower level
                            // API EGrabber.ProcessEvent. Though this method gives full
                            // control over EGrabber event processing, it requires a deeper
                            // knowelege of EGrabber exception handling as well as C# Task
                            // management.
                            // All those non trivial details are implemented in the helper
                            // function EGrabber.ProcessEventsAsync; we strongly encourage
                            // you to use EGrabber.ProcessEventsAsync if you want to process
                            // events asynchronously; you can refer to the dedicated samples
                            // (SampleProcessEventsAsync, SampleProcessEventsAsyncInDifferentTasks)
                            // as well as the EGrabber documentation for more details
                            while (!ct.IsCancellationRequested)
                            {
                                try
                                {
                                    grabber.ProcessEvent(EventType.All);
                                }
                                catch (GenTLError e)
                                {
                                    switch (e.GcError)
                                    {
                                        case GC_ERROR.GC_ERR_INVALID_HANDLE: // an event has been unregistered
                                        case GC_ERROR.GC_ERR_ABORT:          // an event has been cancelled
                                            break;
                                        default:
                                            // TODO: an actual error occured, please update the code accordingly
                                            throw; 
                                    }
                                }
                                catch (ClientError)
                                {
                                    // This may happen when EGrabber is reconfiguring the event filtering
                                    // while ProcessEvent has been called, just wait a bit and retry
                                    Thread.Sleep(10);
                                }
                            }
                        });
                        Console.WriteLine("Event processing task started");
                        grabber.Start();
                        Console.WriteLine("Grabber started, sleeping for 1 second...");
                        System.Threading.Thread.Sleep(1000);
                        Console.WriteLine("Grabber cleanup...");
                        grabber.Stop();
                        cancelTokenSource.Cancel();
                        grabber.CancelEvent(EventType.All);
                        try
                        {
                            task.Wait();
                        }
                        catch (AggregateException)
                        {
                        }
                        Console.WriteLine("Event processing task stopped");
                    }
                }
                finally
                {
                    grabber.Stream.Set("EventNotificationAll", false);
                }
            }
        }

        static void SampleProcessEventsAsyncInDifferentTasks(EGrabberCameraInfo cameraInfo)
        {
            Console.WriteLine();
            Console.WriteLine("SampleProcessEventsAsyncInDifferentTasks");
            Console.WriteLine("----------------------------------------");
            Console.WriteLine();
            using (var grabber = new EGrabber(cameraInfo))
            {
                try
                {
                    grabber.EnableEvent(EventType.All);
                    grabber.Stream.Set("EventNotificationAll", true);
                    grabber.ReallocBuffers(BUFFER_COUNT);
                    grabber.RegisterEventCallback<NewBufferData>((g, data) =>
                    {
                        using (var buffer = new ScopedBuffer(g, data))
                        {
                            Console.WriteLine("task #{0}: {1} | NEW BUFFER", Task.CurrentId, data.Timestamp);
                        }
                    });
                    grabber.RegisterEventCallback<DataStreamData>((g, data) =>
                    {
                        Console.WriteLine("task #{0}: {1} | {2}", Task.CurrentId, data.Timestamp, data.NumId);
                    });
                    // More event type callbacks could be registered with:
                    //  - grabber.RegisterEventCallback<IoToolboxData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<CicData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<CxpInterfaceData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<DeviceErrorData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<CxpDeviceData>((g, data) => {});
                    //  - grabber.RegisterEventCallback<RemoteDeviceData>((g, data) => {});
                    using (var cancelTokenSource = new CancellationTokenSource())
                    {
                        var ct = cancelTokenSource.Token;
                        Task[] tasks = new Task[] {
                            grabber.ProcessEventsAsync(EventType.NewBufferData, ct),
                            grabber.ProcessEventsAsync(EventType.DataStreamData, ct),
                            // More event tasks could be created with:
                            //  - grabber.ProcessEventsAsync(EventType.IoToolboxData, ct),
                            //  - grabber.ProcessEventsAsync(EventType.CicData, ct),
                            //  - grabber.ProcessEventsAsync(EventType.CxpInterfaceData, ct),
                            //  - grabber.ProcessEventsAsync(EventType.DeviceErrorData, ct),
                            //  - grabber.ProcessEventsAsync(EventType.CxpDeviceData, ct),
                            //  - grabber.ProcessEventsAsync(EventType.RemoteDeviceData, ct)
                        };
                        Console.WriteLine("Event processing tasks started (one task per event type)");
                        grabber.Start();
                        Console.WriteLine("Grabber started, sleeping for 1 second...");
                        System.Threading.Thread.Sleep(1000);
                        Console.WriteLine("Grabber cleanup...");
                        grabber.Stop();
                        cancelTokenSource.Cancel();
                        try
                        {
                            Task.WaitAll(tasks);
                        }
                        catch (AggregateException)
                        {
                        }
                        Console.WriteLine("Event processing tasks stopped");
                    }
                }
                finally
                {
                    grabber.Stream.Set("EventNotificationAll", false);
                }
            }
        }

        static void SamplePopEvents(EGrabberCameraInfo cameraInfo)
        {
            Console.WriteLine();
            Console.WriteLine("SamplePopEvents");
            Console.WriteLine("---------------");
            Console.WriteLine();
            using (var grabber = new EGrabber(cameraInfo))
            {
                try
                {
                    grabber.EnableEvent(EventType.All);
                    grabber.Stream.Set("EventNotificationAll", true);
                    grabber.ReallocBuffers(BUFFER_COUNT);

                    grabber.Start(FRAME_COUNT);
                    ulong frame = 0;
                    while (frame < FRAME_COUNT)
                    {
                        var someEventData = grabber.PopOneOf(EventType.All);
                        Console.WriteLine("Got {0}, remaining event count is {1}", someEventData.EventData, someEventData.NumInQueue);
                        if (someEventData.EventData is NewBufferData newBufferData)
                        {
                            ++frame;
                            using (var buffer = new ScopedBuffer(grabber, newBufferData))
                            {
                                Console.WriteLine("  {0} | NEW BUFFER", newBufferData.Timestamp);
                            }
                        }
                        else if (someEventData.EventData is DataStreamData dataStreamData)
                        {
                            Console.WriteLine("  {0} | {1}", dataStreamData.Timestamp, dataStreamData.NumId);
                        }
                        // More event types could be checked with:
                        //  - else if (someEventData.EventData is IoToolboxData) { ... }
                        //  - else if (someEventData.EventData is CicData) { ... }
                        //  - else if (someEventData.EventData is CxpInterfaceData) { ... }
                        //  - else if (someEventData.EventData is DeviceErrorData) { ... }
                        //  - else if (someEventData.EventData is CxpDeviceData) { ... }
                        //  - else if (someEventData.EventData is RemoteDeviceData) { ... }
                    }
                }
                finally
                {
                    grabber.Stream.Set("EventNotificationAll", false);
                }
            }
        }

        static void Main()
        {
            try
            {
                using (var gentl = new EGenTL())
                {
                    using (var discovery = new EGrabberDiscovery(gentl))
                    {
                        discovery.Discover();
                        if (discovery.EGrabbers.Count > 0)
                        {
                            var camera0Info = discovery.Cameras[0];
                            SampleBasicQueries(camera0Info);
                            SampleScopedBuffer(camera0Info);
                            SampleProcessEventsAsync(camera0Info);
                            SampleProcessEventsInSingleTask(camera0Info);
                            SampleProcessEventsInDifferentTasks(camera0Info);
                            SamplePopEvents(camera0Info);
                        }
                    }
                }
            }
            catch (System.Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}
