﻿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.Aruco;
using System.Windows.Media.Animation;
using System.Configuration;
using System.Web.ModelBinding;
using System.Windows.Controls;
using System.IO;
using static System.Resources.ResXFileRef;
using System.Windows.Shapes;
using Path = System.IO.Path;
using OpenCvSharp.Dnn;

namespace TIASshot {

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

        // 光源の輝度設定
        // 連続撮影のマルチスレッド化
        // GUI更新はタイマー使用に変更

        IntPtr _hCam = IntPtr.Zero;
        PictureBox _picPreview;
        
        dll.LucamSnapshot _snap;
        dll.LucamConversion _convert;
        dll.LucamRgbPreviewCallback _callbackHandler;
        int _callbackId;

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

            // カメラパラメータの初期値
            _snap.BufferLastFrame = false;
            _snap.Exposure = Config.GetFloat("Lucam/Exposure");
            _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 = Config.GetFloat("Lucam/Gain");
            _snap.GainBlue = Config.GetFloat("Lucam/GainB");
            _snap.GainGrn1 = Config.GetFloat("Lucam/GainG");
            _snap.GainGrn2 = _snap.GainGrn1;
            _snap.GainRed = Config.GetFloat("Lucam/GainR");
            _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 override bool Connect() {
            if (!BootCheck()) return false;

            int numCam = 0;
            try {
                numCam = dll.LucamNumCameras();
            } catch (Exception ex) {
                ErrorMsg = ex.Message;
                return false;
            }
            if ( numCam < 1 ) {
                ErrorMsg = "Lucamが見つかりません";
                return false;
            }
            if (numCam > 1) {
                ErrorMsg = "複数のLucamが見つかりました．\r\n正しいカメラを１つ接続してください．";
                return false;
            }

            // カメラのデバイス情報取得
            var lumVersion = new dll.LucamVersion[numCam];
            lumVersion = api.EnumCameras();
            var id = 0;
            if (lumVersion[id].CameraId == 0x49f) DeviceName = "Lw110";
            SetInfo("カメラ型番", DeviceName);
            SerialNumber = lumVersion[id].SerialNumber.ToString();
            SetInfo("カメラSN", SerialNumber);

            // カメラを開く
            _hCam = api.CameraOpen(1);
            if (_hCam == IntPtr.Zero) {
                ErrorMsg = "カメラの接続に失敗しました．\r\n他のアプリケーションでカメラを使用していないか確認してください．";
                return false;
            }

            // プレビューコールバックの登録
            _callbackHandler = new dll.LucamRgbPreviewCallback(PreviewCallback);
            _callbackId = dll.LucamAddRgbPreviewCallback(_hCam, _callbackHandler, IntPtr.Zero, dll.LucamPixelFormat.PF_24);
            if (_callbackId == -1) {
                ErrorMsg = "コールバックの登録に失敗しました";
                return false;
            }

            SetCameraParam(); // カメラパラメータの設定

            StartStopPreview(); // プレビュー開始

            return true;
        }

        /// <summary>
        /// プレビュー開始・停止
        /// </summary>
        /// <returns></returns>
        public bool StartStopPreview() {
            if (_isPreview) {
                // プレビュー停止
                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>
        /// <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) {
            using (Mat img = Mat.FromPixelData(_snap.Format.Height, _snap.Format.Width, MatType.CV_8UC3, pData)) {
                using (Mat imgt = img.T()) {
                    _bmps[_bmpIndex] = imgt.ToBitmap();

                    if (_calibrating > 0) {
                        var whitePatch = Cv2.Mean(imgt, _chartMasks[12]);
                        Debug.WriteLine($"White patch R {whitePatch.Val2:.00} G {whitePatch.Val1:.00} B {whitePatch.Val0:.00}");
                        if (_calibrating % Config.GetInt("Calib/UpdateInterval") == 0) {
                            _snap.GainBlue *= GetRatio((float)whitePatch.Val0, Config.GetFloat("Calib/Reference/B"));
                            _snap.GainGrn1 *= GetRatio((float)whitePatch.Val1, Config.GetFloat("Calib/Reference/G"));
                            _snap.GainGrn2 = _snap.GainGrn1;
                            _snap.GainRed *= GetRatio((float)whitePatch.Val2, Config.GetFloat("Calib/Reference/R"));
                            SetCameraParam();
                        }
                        _calibrating--;
                        if (_calibrating == 0) {
                            Debug.WriteLine($"Gain Blue : {_snap.GainBlue}");
                            Debug.WriteLine($"Gain Green: {_snap.GainGrn1}");
                            Debug.WriteLine($"Gain Red  : {_snap.GainRed}");

                            CalcTcc(imgt);

                            SetInfo("Exposure(ms)", $"{_snap.Exposure}");
                            SetInfo("Gain", $"{_snap.Gain}");
                            SetInfo("Gain B", $"{_snap.GainBlue}");
                            SetInfo("Gain G", $"{_snap.GainGrn1}");
                            SetInfo("Gain R", $"{_snap.GainRed}");
                            SetInfo("画像幅", $"{imgt.Width}");
                            SetInfo("画像高さ", $"{imgt.Height}");
                        }
                    }

                    if (!_calibrated && _calibrating == 0) {
                        DetectChart(imgt);
                    }
                }
            }
            _form.ShowImage(_bmps[_bmpIndex]);
            _bmpIndex = (_bmpIndex + 1) % 2;
            if (_bmps[_bmpIndex] != null) _bmps[_bmpIndex].Dispose();
        }

        /// <summary>
        /// 撮影
        /// </summary>
        /// <param name="numImages"></param>
        /// <param name="interval"></param>
        protected override void Shot(int numImages=1, int interval=0) {
            SetSnapParam();

            _shots.Clear();
            var thread = new Thread(() => SaveThread(numImages));
            thread.Start();

            dll.LucamEnableFastFrames(_hCam, ref _snap);
            var imageSize = _snap.Format.Width * _snap.Format.Height;
            var rawImage = new byte[imageSize];
            var rgbImage = new byte[imageSize * 3];

            var filename = Config.GetString("File/Info");
            using (var csv = new StreamWriter(Path.Combine(_saveFolder, filename))) {
                WriteInfo(csv);
                csv.WriteLine("Shot,Time(ms)");

                var watch = Stopwatch.StartNew();
                for (var i = 0; i < numImages; i++) {
                    var shotTime = watch.ElapsedMilliseconds;
                    csv.WriteLine($"{i + 1},{shotTime}");
                    _form.ShowMessage($"撮影　{i + 1} / {numImages} 枚目");
                    var ret = dll.LucamTakeFastFrame(_hCam, rawImage);
                    //Debug.WriteLine(ret);

                    dll.LucamConvertFrameToRgb24(_hCam, rgbImage, rawImage,
                        _snap.Format.Width, _snap.Format.Height, dll.LucamPixelFormat.PF_8, ref _convert);
                    using (Mat img = Mat.FromPixelData(_snap.Format.Height, _snap.Format.Width, MatType.CV_8UC3, rgbImage)) {
                        _shots.Add(img.T());
                    }
                    while (watch.ElapsedMilliseconds < interval * (i + 1) && i < numImages - 1) {
                        Thread.Sleep(1);
                    }
                }
            }

            rgbImage = null;
            rawImage = null;

            dll.LucamDisableFastFrames(_hCam);
        }

        /// <summary>
        /// 校正　露光時間とホワイトバランスの自動調整
        /// </summary>
        public void Calibration() {
            Debug.WriteLine("校正前");
            SetSnapParam();
            _calibrating = 30;
        }

        /// <summary>
        /// 撮影パラメータの設定
        /// </summary>
        private void SetSnapParam() {
            dll.LucamPropertyFlag flag;
            dll.LucamGetProperty(_hCam, dll.LucamProperty.EXPOSURE, out _snap.Exposure, out flag);
            dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN, out _snap.Gain, out flag);
            dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_BLUE, out _snap.GainBlue, out flag);
            dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_GREEN1, out _snap.GainGrn1, out flag);
            dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_GREEN2, out _snap.GainGrn2, out flag);
            dll.LucamGetProperty(_hCam, dll.LucamProperty.GAIN_RED, out _snap.GainRed, out flag);
            Debug.WriteLine($"SetSnapParam Exp {_snap.Exposure} Gain {_snap.Gain} GainBlue {_snap.GainBlue} GainGrn1 {_snap.GainGrn1} GainGrn2 {_snap.GainGrn2} GainRed {_snap.GainRed}");
        }

        /// <summary>
        /// カメラパラメータの設定
        /// </summary>
        private void SetCameraParam() {
            var flag = dll.LucamPropertyFlag.NONE;
            dll.LucamSetProperty(_hCam, dll.LucamProperty.EXPOSURE, _snap.Exposure, flag);
            dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN, _snap.Gain, flag);
            dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_BLUE, _snap.GainBlue, flag);
            dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_GREEN1, _snap.GainGrn1, flag);
            dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_GREEN2, _snap.GainGrn2, flag);
            dll.LucamSetProperty(_hCam, dll.LucamProperty.GAIN_RED, _snap.GainRed, flag);
            Debug.WriteLine($"SetCameraParam Exp {_snap.Exposure} Gain {_snap.Gain} GainBlue {_snap.GainBlue} GainGrn1 {_snap.GainGrn1} GainGrn2 {_snap.GainGrn2} GainRed {_snap.GainRed}");
        }


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