﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Lumenera.USB;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System.Drawing;
using System.Windows.Interop;
using System.Threading;
using OpenCvSharp.Dnn;
using System.Windows.Media.Animation;

namespace TIASshot {

    /// <summary>
    /// Lumeneraカメラクラス
    /// </summary>
    internal class Lucam {

        public string DeviceName { get; private set; } = "Unknown";
        public string SerialNumber { get; private set; }
        public string ErrorMsg { get; private set; }

        private IntPtr _hCam = IntPtr.Zero;
        private PictureBox _picPreview, _picDisplay;
        private Form1 _form;
        private bool _isPreview = false;
        private bool _check = false;
        private int _calibrating = 0;
        private dll.LucamSnapshot _snap;
        private dll.LucamConversion _convert;
        private dll.LucamRgbPreviewCallback _callbackHandler;
        private int _callbackId;
        //private int _count = 0;
        private Bitmap[] _bmps = new Bitmap[2];
        private int _bmpIndex = 0;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="preview"></param>
        /// <param name="display"></param>
        public Lucam(Form1 form, PictureBox preview, PictureBox display) {
            _picPreview = preview;
            _picDisplay = display;
            _form = form;

            // Default value for taking a snapshot.
            _snap.BufferLastFrame = false;
            _snap.Exposure = 20;
            _snap.ExposureDelay = 0;
            _snap.flReserved1 = 0;
            _snap.flReserved2 = 0;
            _snap.Format.BinningX = 1;
            _snap.Format.BinningY = 1;
            _snap.Format.FlagsX = 0;
            _snap.Format.FlagsY = 0;
            _snap.Format.Height = 1024;
            _snap.Format.PixelFormat = dll.LucamPixelFormat.PF_8;
            _snap.Format.SubSampleX = 1;
            _snap.Format.SubSampleY = 1;
            _snap.Format.Width = 1280;
            _snap.Format.X = 0;
            _snap.Format.Y = 0;
            _snap.Gain = 2.0f;
            _snap.GainBlue = 1;
            _snap.GainGrn1 = 1;
            _snap.GainGrn2 = 1;
            _snap.GainRed = 1;
            _snap.ShutterType = dll.LucamShutterType.GlobalShutter;
            _snap.StrobeDelay = 0.1f;
            _snap.StrobeFlags = 0;
            _snap.Timeout = 5000;
            _snap.ulReserved2 = 0;
            _snap.UseHwTrigger = false;

            _convert.DemosaicMethod = dll.LucamDemosaicMethod.HIGH_QUALITY;
            _convert.CorrectionMatrix = dll.LucamCorrectionMatrix.LED;
        }

        /// <summary>
        /// カメラ接続
        /// </summary>
        /// <returns></returns>
        public bool Connect() {
            var numCam = dll.LucamNumCameras();
            if ( numCam < 1 ) {
                ErrorMsg = "カメラが見つかりません．\r\n終了します．";
                return false;
            }
            if (numCam > 1) {
                ErrorMsg = "複数のカメラが見つかりました．\r\n正しいカメラを１つ接続してください．\r\n終了します．";
                return false;
            }

            var lumVersion = new dll.LucamVersion[numCam];
            lumVersion = api.EnumCameras();
            var id = 0;
            if (lumVersion[id].CameraId == 0x49f) DeviceName = "Lw110";
            SerialNumber = lumVersion[id].SerialNumber.ToString();

            _hCam = api.CameraOpen(1);
            if (_hCam == IntPtr.Zero) {
                return false;
            }

            _callbackHandler = new dll.LucamRgbPreviewCallback(PreviewCallback);
            _callbackId = dll.LucamAddRgbPreviewCallback(_hCam, _callbackHandler, IntPtr.Zero, dll.LucamPixelFormat.PF_24);
            if (_callbackId == -1) {
                ErrorMsg = "コールバックの登録に失敗しました．\r\n終了します．";
                return false;
            }

            return true;
        }

        /// <summary>
        /// プレビュー開始・停止
        /// </summary>
        /// <returns></returns>
        public bool StartStopPreview() {
            if (_isPreview) {
                //dll.LucamRemoveRgbPreviewCallback(_hCam, _callbackId);
                //Debug.WriteLine("CB解除");
                var ret = dll.LucamStreamVideoControl(_hCam, dll.LucamStreamMode.STOP_STREAMING, _picPreview.Handle.ToInt32());
                Debug.WriteLine("プレビュー停止");
                if (!ret) {
                    return false;
                }
                _isPreview = false;
                return true;
            } else {
                var ret = dll.LucamStreamVideoControl(_hCam, dll.LucamStreamMode.START_DISPLAY, _picPreview.Handle.ToInt32());
                if (!ret) {
                    return false;
                }
                _isPreview = true;
                return true;
            }
        }

        /// <summary>
        /// 画像表示（スレッドセーフ）
        /// </summary>
        void SafeDisplay() {
            if (_picDisplay.InvokeRequired) {
                _picDisplay.Invoke((MethodInvoker)delegate { SafeDisplay(); });
                return;
            }
            _picDisplay.Image = _bmps[_bmpIndex];
        }

        /// <summary>
        /// プレビューコールバック
        /// </summary>
        /// <param name="pContext"></param>
        /// <param name="pData">データポインタ</param>
        /// <param name="n">データサイズ</param>
        /// <param name="unused"></param>
        void PreviewCallback(IntPtr pContext, IntPtr pData, int n, uint unused) {
            //Debug.WriteLine(n);
            using (Mat img = Mat.FromPixelData(1024, 1280, MatType.CV_8UC3, pData)) {
                using (Mat imgt = img.T()) {
                    _bmps[_bmpIndex] = imgt.ToBitmap();

                    Scalar m;
                    using (Mat mask = new Mat(imgt.Size(), MatType.CV_8UC1)) {
                        Cv2.Rectangle(mask, new Rect(300, 165, 100, 100), new Scalar(255), Cv2.FILLED);
                        m = Cv2.Mean(imgt, mask);
                    }
                    if (_check || _calibrating > 0) {
                        Debug.WriteLine(m);
                        _check = false;
                    }

                    if (_calibrating > 0 && _calibrating % 5 == 0) {
                        float target = 230;
                        float rate = 0.5F;
                        float mb = target / (float)m.Val0;
                        float mg = target / (float)m.Val1;
                        float mr = target / (float)m.Val2;
                        mb = (mb - 1.0F) * (m.Val0 > 254 ? 1.0F : rate) + 1.0F;
                        mg = (mg - 1.0F) * (m.Val1 > 254 ? 1.0F : rate) + 1.0F;
                        mr = (mr - 1.0F) * (m.Val2 > 254 ? 1.0F : rate) + 1.0F;
                        float value;
                        dll.LucamPropertyFlag flag;
                        dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_BLUE, out value, out flag);
                        Debug.WriteLine($"Gain Blue {value} * {mb} -> {value * mb}  ({flag})");
                        value *= mb;
                        dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_BLUE, value, flag);
                        dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_GREEN1, out value, out flag);
                        Debug.WriteLine($"Gain Green1 {value} * {mg} -> {value * mg}");
                        value *= mg;
                        dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_GREEN1, value, flag);
                        dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_GREEN2, out value, out flag);
                        Debug.WriteLine($"Gain Green2 {value} * {mg} -> {value * mg}");
                        value *= mg;
                        dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_GREEN2, value, flag);
                        dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_RED, out value, out flag);
                        Debug.WriteLine($"Gain Red {value} * {mr} -> {value * mr}");
                        value *= mr;
                        dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_RED, value, flag);
                    }
                    if (_calibrating > 0) _calibrating--;
                }
            }
            //SafeDisplay();
            _picDisplay.Image = _bmps[_bmpIndex];
            _bmpIndex = (_bmpIndex + 1) % 2;
            if (_bmps[_bmpIndex] != null) _bmps[_bmpIndex].Dispose();
        }

        /// <summary>
        /// 画像撮影１枚
        /// </summary>
        public void ShotOne() {
            Shot();
        }

        /// <summary>
        /// 複数画像撮影
        /// </summary>
        public void ShotMulti() {
            Shot(5, 1000);
        }

        /// <summary>
        /// 撮影
        /// </summary>
        /// <param name="numImages"></param>
        /// <param name="interval"></param>
        private void Shot(int numImages=1, int interval = 1000) {
            SetSnapParam();
            dll.LucamEnableFastFrames(_hCam, ref _snap);
            var imageSize = _snap.Format.Width * _snap.Format.Height;
            var rawImage = new byte[imageSize];
            var rgbImage = new byte[imageSize * 3];
            for (var i = 0; i < numImages; i++) {
                var ret = dll.LucamTakeFastFrame(_hCam, rawImage);
                //Debug.WriteLine(ret);
                if (i < numImages - 1) Thread.Sleep(interval);

                dll.LucamConvertFrameToRgb24(_hCam, rgbImage, rawImage, _snap.Format.Width, _snap.Format.Height, dll.LucamPixelFormat.PF_8, ref _convert);
                using (Mat img = Mat.FromPixelData(1024, 1280, MatType.CV_8UC3, rgbImage)) {
                    Cv2.ImWrite($"orig_{i:00}.jpg", img);
                    using (Mat imgt = img.T()) {
                        Cv2.ImWrite($"snap_{i:00}.jpg", imgt);
                    }
                }
            }
            rgbImage = null;
            rawImage = null;

            dll.LucamDisableFastFrames(_hCam);
        }

        public void Calibration() {
            Debug.WriteLine("校正前");
            SetSnapParam();

            dll.LucamOneShotAutoExposure(_hCam, 150, 0, 0, _snap.Format.Width, _snap.Format.Height);
            //dll.LucamOneShotAutoExposure(_hCam, 100, 165, 300, 100, 100);
            //Thread.Sleep(1000);
            //dll.LucamOneShotAutoExposure(_hCam, 200, 165, 300, 100, 100);
            //Thread.Sleep(1000);
            //dll.LucamOneShotAutoExposure(_hCam, 200, 165, 300, 100, 100);
            dll.LucamOneShotAutoWhiteBalance(_hCam, 0, 0, _snap.Format.Width, _snap.Format.Height);
            //dll.LucamOneShotAutoWhiteBalance(_hCam, 165, 300, 100, 100);

            Debug.WriteLine("校正後");
            SetSnapParam();

            _calibrating = 30;
        }

        private void SetSnapParam() {
            dll.LucamPropertyFlag flag;
            float value = 0;
            dll.LucamGetProperty(_hCam, dll.LucamProperty.EXPOSURE, out value, out flag);
            _snap.Exposure = value;
            dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN, out value, out flag);
            _snap.Gain = value;
            dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_BLUE, out value, out flag);
            _snap.GainBlue = value;
            dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_GREEN1, out value, out flag);
            _snap.GainGrn1 = value;
            dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_GREEN2, out value, out flag);
            _snap.GainGrn2 = value;
            dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_RED, out value, out flag);
            _snap.GainRed = value;
            Debug.WriteLine($"Exp {_snap.Exposure} Gain {_snap.Gain} GainBlue {_snap.GainBlue} GainGrn1 {_snap.GainGrn1} GainGrn2 {_snap.GainGrn2} GainRed {_snap.GainRed}");
        }

        public void Check() {
            _check = true;
        }

        /// <summary>
        /// カメラ切断
        /// </summary>
        public void Disconnect() {
            if (_hCam != IntPtr.Zero) {
                if (_isPreview) {
                    StartStopPreview();
                }
                api.CameraClose(_hCam);
                _hCam = IntPtr.Zero;
                Debug.WriteLine("カメラ切断");
            }
        }
    }
}
