using OpenCvSharp;
using Serilog;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using uEye.Defines;
using VisionBuilder.UI.Common;
using VisionBuilder.UI.Common.Processing;
using Point = System.Drawing.Point;

namespace Inspectron.Camera.UEye
{

    public class IDSImageSource : IImageSource, IVisionBuilderModule
    {
        private readonly IDSImageSourceSettings _settings;
        private uEye.Camera _camera = null;


        int cameraIdx;

        
        public IDSImageSource(IDSImageSourceSettings settings)
        {
            _settings = settings;
            _camera = new uEye.Camera();
            cameraIdx = _settings.CameraId;
            System.AppDomain.CurrentDomain.ProcessExit += AppDomain_ProcessExit;
            
        }

        private void AppDomain_ProcessExit(object sender, EventArgs e)
        {
            Close();
        }

        private Mat MemoryToImage(int idx)
        {
            uEye.Defines.ColorMode mode;
            _camera.PixelFormat.Get(out mode);
           
            int channels = 0;

            if (mode == uEye.Defines.ColorMode.Mono8)
            {
                channels = 1;
            }
            else if (mode == uEye.Defines.ColorMode.BGR8Packed || mode == uEye.Defines.ColorMode.RGB8Packed)
            {
                channels = 3;
            }
            else if (mode == uEye.Defines.ColorMode.BGRA8Packed)
            {
                channels = 4;
            }
            else
            {
                throw new Exception("unknown image format");
            }

            var size = new uEye.Types.Size<int>();
            _camera.Memory.GetSize(idx, out size);
            int pitch;
            _camera.Memory.GetPitch(idx, out pitch);

            Byte[] u8img;
            _camera.Memory.CopyToArray(idx, out u8img);
            
            
            // Warning! This will work only if pitch == width * channels
            if (pitch != size.Width * channels)
            {
                throw new Exception("pitch must be equal to width * channels");
            }
            Mat mat = new Mat(size.Height, size.Width,MatType.CV_8UC3);
            // copy data to mat
            System.Runtime.InteropServices.Marshal.Copy(u8img, 0, mat.Data, u8img.Length);


            return mat;
        }

        public void Open()
        {
            if (_camera.IsOpened)
                return;

            uEye.Defines.Status statusRet = 0;

            // Open camera
            statusRet = _camera.Init(cameraIdx);
            if (statusRet != uEye.Defines.Status.Success)
            {
                throw new Exception("Camera initialization failed: "+statusRet);
            }
            uEye.Types.SensorInfo info = new uEye.Types.SensorInfo();
            _camera.Information.GetSensorInfo(out info);
            Log.Information($"Camera {cameraIdx} opened: {info.SensorName} ({info.SensorID})");
            _camera.Parameter.Load();
            Log.Information($"Camera {cameraIdx} parameters loaded");

            _camera.Trigger.Set(uEye.Defines.TriggerMode.Continuous);

            statusRet = _camera.Memory.Allocate();
            if (statusRet != uEye.Defines.Status.Success)
            {
                throw new Exception("Allocate Memory failed");
            }
            _camera.EventFrame += onFrameEvent;
            try
            {
                _camera.Device.Feature.ShutterMode.Set(uEye.Defines.Shuttermode.Global);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        public void Close()
        {
            if (!_camera.IsOpened)
                return;

            uEye.Defines.Status statusRet;
            statusRet = _camera.Exit();

            if (statusRet != uEye.Defines.Status.Success)
            {
                throw new Exception("failed to close camera");
            }
        }



        public void LoadParameters(string parameters_file)
        {
            uEye.Defines.Status statusRet = 0;

            Int32[] memList;
            statusRet = _camera.Memory.GetList(out memList);
            if (statusRet != uEye.Defines.Status.Success)
            {
                throw new Exception("Get memory list failed: " + statusRet);
            }

            statusRet = _camera.Memory.Free(memList);
            if (statusRet != uEye.Defines.Status.Success)
            {
                throw new Exception("Free memory list failed: " + statusRet);
            }

            statusRet = _camera.Parameter.Load(parameters_file);
            if (statusRet != uEye.Defines.Status.Success)
            {
                throw new Exception("Loading parameter failed: " + statusRet);
            }

            uEye.Types.Size<int> t = new uEye.Types.Size<int>();


            statusRet = _camera.Memory.Allocate();
            if (statusRet != uEye.Defines.Status.SUCCESS)
            {
                throw new Exception("Allocate Memory failed");
            }
        }

        public void SaveParameters(string parameters_file)
        {
            uEye.Defines.Status statusRet = 0;

            statusRet = _camera.Parameter.Save(parameters_file);
            if (statusRet != uEye.Defines.Status.SUCCESS)
            {
                throw new Exception("failed to save Parameters");
            }
        }



        public int LastAqTime { get; private set; }
        private void onFrameEvent(object sender, EventArgs e)
        {
            try
            {
                uEye.Camera camera = sender as uEye.Camera;
                int s32MemId;
                int active = (int)camera.Memory.GetActive(out s32MemId);
                int num1 = (int)camera.Memory.Lock(s32MemId);
                var sw = System.Diagnostics.Stopwatch.StartNew();
                var image = this.MemoryToImage(s32MemId);
                sw.Stop();
                LastAqTime = (int)sw.ElapsedMilliseconds;
                

                int num2 = (int)camera.Memory.Unlock(s32MemId);
                _channel.Writer.TryWrite(image);
            }
            catch (TaskCanceledException ex)
            {
                int num = (int)(sender as uEye.Camera).Acquisition.Stop();
                
            }
        }


        private Channel<Mat> _channel = Channel.CreateBounded<Mat>(new BoundedChannelOptions(1)
        {
            FullMode = BoundedChannelFullMode.DropOldest,
            SingleReader = true,
            SingleWriter = true,
            AllowSynchronousContinuations = true
        });
        public async Task<Mat> GetImage(CancellationToken token)
        {
            return await _channel.Reader.ReadAsync(token);
        }

        public void InitializeModule()
        {
            Open();
            if(!(string.IsNullOrEmpty(_settings.CameraFile)))
                LoadParameters(_settings.CameraFile);
            _camera.Trigger.Set(uEye.Defines.TriggerMode.Continuous);
            _camera.Acquisition.Capture();
        }
    }
}