﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.Remoting.Channels;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Media;
using OpenCvSharp;

namespace TIASshot {
    internal abstract class CameraBase {
        // インターフェース
        public abstract bool Connect();
        public abstract void Disconnect();
        protected abstract void Shot(int numImages=1, int interval=0);

        // テンプレートメソッド用抽象メソッド（プレビュー/校正処理）
        protected abstract void ApplyWhiteBalance(Scalar whitePatch);
        protected abstract void LogCalibrationConverged();
        protected abstract void RecordCalibrationInfo(Mat imgt);

        // テンプレートメソッド用抽象メソッド（撮影処理）
        protected abstract void BeginShot();
        protected abstract Mat CaptureFrame();
        protected abstract void EndShot();

        // プロパティ
        public string DeviceName { get; protected set; } = "Unknown";
        public string SerialNumber { get; protected set; }
        public string ErrorMsg { get; protected set; }
        public bool IsCalibrating => _calibrating > 0;

        // 派生クラスで使用するメンバ
        protected Form1 _form;
        protected int _calibrating = 0;
        protected bool _calibrated = false;
        protected Bitmap[] _bmps = new Bitmap[2];
        protected int _bmpIndex = 0;
        protected bool _isPreview = false;
        protected List<Mat> _chartMasks = new List<Mat>();
        protected string _saveFolder = "";
        protected BlockingCollection<Mat> _shots = new BlockingCollection<Mat>();
        protected Dictionary<string, string> _ShotInfo = new Dictionary<string, string>(); // 設定値保存用
        protected Rect _roi;

        // プライベートメンバ
        readonly float UpdateRate;

        // 色補正処理（TCC 変換行列の計算・色変換）
        readonly ColorCorrector _colorCorrector;

        // チャート検出処理（ARマーカー検出・マスク生成）
        readonly ChartDetector _chartDetector;

        /// <summary>
        /// カメラの基本クラス
        /// </summary>
        public CameraBase(Form1 form) {
            _form = form;
            UpdateRate = Config.GetFloat("Calib/UpdateRate");
            var tccSrgb = IoUtil.LoadMatFromCsv(Config.GetString("File/TccSrgbRef"));
            var tccXyz = IoUtil.LoadMatFromCsv(Config.GetString("File/TccXyzRef"));
            _colorCorrector = new ColorCorrector(tccSrgb, tccXyz);
            _chartDetector = new ChartDetector(_form.ShowMessage);
        }

        /// <summary>
        /// 起動チェック
        /// </summary>
        /// <returns></returns>
        protected bool BootCheck() {

            if (!Config.IsLoaded()) {
                ErrorMsg = "設定ファイル（Config.xml）の読み込みに失敗しました．\r\n終了します．";
                return false;
            }
            if (!_colorCorrector.SrgbRefLoaded) {
                ErrorMsg = $"ファイル（{Config.GetString("File/TccSrgbRef")}）の読み込みに失敗しました．\r\n終了します．";
                return false;
            }
            if (!_colorCorrector.XyzRefLoaded) {
                ErrorMsg = $"ファイル（{Config.GetString("File/TccXyzRef")}）の読み込みに失敗しました．\r\n終了します．";
                return false;
            }
            return true;
        }

        /// <summary>
        /// 画像撮影１枚
        /// </summary>
        public void ShotOne() {
            SetSaveFolder(_form.GetDataName());
            SetInfo("データ名", _form.GetDataName());
            SetInfo("撮影枚数", "1");
            Shot();
            IoUtil.PlaySound(Config.GetString("Sound/OneShotDone"));
            _form.ShowMessage("撮影終了");
        }

        /// <summary>
        /// 複数画像撮影
        /// </summary>
        public void ShotMulti() {
            SetSaveFolder(_form.GetDataName());
            SetInfo("データ名", _form.GetDataName());
            SetInfo("撮影枚数", $"{_form.GetNumMultiShots()}");
            SetInfo("撮影間隔(ms)", $"{_form.GetMultiShotsInterval()}");
            Shot(_form.GetNumMultiShots(), _form.GetMultiShotsInterval());
            IoUtil.PlaySound(Config.GetString("Sound/MultiShotDone"));
            _form.ShowMessage("撮影終了");
        }

        /// <summary>
        /// 画像保存処理
        /// </summary>
        /// <param name="img"></param>
        /// <param name="idx"></param>
        protected void SaveImages(Mat img, int idx) {

            var filename = Config.GetString("File/RgbImage");
            filename = filename.Replace("{NO}", $"{idx + 1:0000}");
            Cv2.ImWrite(Path.Combine(_saveFolder, filename), img);

            foreach (var channel in _colorCorrector.Channels) {
                using (var converted = _colorCorrector.ConvertImage(img, channel)) {
                    filename = GetFilenameWithChannel("File/SrgbImage", channel);
                    filename = filename.Replace("{NO}", $"{idx + 1:0000}");
                    Cv2.ImWrite(Path.Combine(_saveFolder, filename), converted);
                }
            }
        }

        /// <summary>
        /// 画像保存スレッド処理
        /// </summary>
        /// <param name="numImages"></param>
        protected void SaveThread(int numImages) {
            int saveCount = 0;
            foreach (var img in _shots.GetConsumingEnumerable()) {
                SaveImages(img, saveCount);
                saveCount++;
                _form.ShowMessage($"画像{saveCount}/{numImages}枚目保存");
            }
            _form.ShowMessage("全ての画像保存完了");
            _form.EnableShots(true);
        }

        /// <summary>
        /// 校正フレーム処理の共通骨格（テンプレートメソッド）
        /// imgt が用意された後の校正/検出フローを担う．
        /// ゲイン反映（ApplyWhiteBalance）・収束時ログ出力（LogCalibrationConverged）・
        /// 校正情報記録（RecordCalibrationInfo）は派生クラスに委譲する．
        /// 呼び出し順序は元コードに合わせ「LogCalibrationConverged → CalcTcc → RecordCalibrationInfo」．
        /// </summary>
        /// <param name="imgt">処理対象 Mat（前処理済み）</param>
        protected void ProcessCalibrationFrame(Mat imgt) {
            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) {
                    ApplyWhiteBalance(whitePatch);
                }
                _calibrating--;
                if (_calibrating == 0) {
                    LogCalibrationConverged();
                    CalcTcc(imgt);
                    RecordCalibrationInfo(imgt);
                }
            }

            if (!_calibrated && _calibrating == 0) {
                DetectChart(imgt);
            }
        }

        /// <summary>
        /// 撮影ループの共通骨格（テンプレートメソッド）
        /// CSV 書き込み・ループ・タイミング制御を担う．
        /// フレーム取得（CaptureFrame）は派生クラスに委譲する．
        /// BeginShot/EndShot は呼び出し元（派生クラスの Shot）で前後に実行すること．
        /// ループ完了後に _shots.CompleteAdding() を呼び，SaveThread に終了を通知する．
        /// </summary>
        /// <param name="numImages">撮影枚数</param>
        /// <param name="interval">撮影間隔(ms)</param>
        protected void RunShotLoop(int numImages, int interval) {
            try {
                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} 枚目");
                        _shots.Add(CaptureFrame());
                        while (watch.ElapsedMilliseconds < interval * (i + 1) && i < numImages - 1) {
                            Thread.Sleep(1);
                        }
                    }
                }
            } finally {
                _shots.CompleteAdding();
            }
        }

        /// <summary>
        /// チャートの検出
        /// </summary>
        /// <param name="img"></param>
        protected void DetectChart(Mat img) {
            // チャート検出・マスク生成は ChartDetector に委譲する
            var masks = _chartDetector.DetectChart(img);
            if (masks == null) return;

            _chartMasks.Clear();
            _chartMasks.AddRange(masks);
            _calibrating = Config.GetInt("Calib/Frames");
        }

        /// <summary>
        /// ファイル名にチャネル数を追加する
        /// </summary>
        /// <param name="config"></param>
        /// <param name="channel"></param>
        /// <returns></returns>
        private string GetFilenameWithChannel(string config, int channel) {
            var filename = Config.GetString(config);
            filename = filename.Replace("{CN}", $"{channel:00}");
            return filename;
        }

        /// <summary>
        /// チャートに基づく変換行列の計算
        /// </summary>
        /// <param name="img"></param>
        protected void CalcTcc(Mat img) {

            // 変換行列の計算
            var tccRgb = GetTccRgb(img);
            var imgRois = GetTccRoisImage(img);

            // データ保存
            SetSaveFolder("校正");
            SetInfo("校正データ", _saveFolder);
            Cv2.ImWrite(Path.Combine(_saveFolder, Config.GetString("File/TccImage")), img);
            Cv2.ImWrite(Path.Combine(_saveFolder, Config.GetString("File/TccRoisImage")), imgRois);

            // 変換行列の計算・保存は ColorCorrector に委譲する
            _colorCorrector.CalcTcc(tccRgb, _saveFolder, IoUtil.SaveMatToCsv);

            IoUtil.PlaySound(Config.GetString("Sound/CalibDone"));
            _form.ShowMessage("自動校正完了");
            _form.EnableShots();
            _calibrated = true;
        }

        /// <summary>
        /// 画像からチャートのRGB値算出
        /// </summary>
        /// <param name="img"></param>
        /// <returns></returns>
        private Mat GetTccRgb(Mat img) {
            var arrRGB = new Mat(24, 3, MatType.CV_64FC1);
            for (int i = 0; i < _chartMasks.Count; i++) {
                var rgb = Cv2.Mean(img, _chartMasks[i]);
                arrRGB.At<double>(i, 0) = rgb.Val0; // Blue
                arrRGB.At<double>(i, 1) = rgb.Val1; // Green
                arrRGB.At<double>(i, 2) = rgb.Val2; // Red
            }
            return arrRGB;
        }

        /// <summary>
        /// TCCのROI画像を取得
        /// </summary>
        /// <param name="img"></param>
        /// <returns></returns>
        private Mat GetTccRoisImage(Mat img) {
            var imgRois = img.Clone();
            for (int i = 0; i < _chartMasks.Count; i++) {
                imgRois.SetTo(new Scalar(0, 200, 0), _chartMasks[i]);
            }
            return imgRois;
        }

        /// <summary>
        /// ゲイン値の更新比率計算
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected float GetRatio(float value, float target) {
            float ratio = target / value;
            ratio = (ratio - 1.0f) * (value == 255.0f ? 1.0f : UpdateRate) + 1.0f;
            return ratio;
        }

        /// <summary>
        /// 保存フォルダの取得
        /// </summary>
        /// <param name="note"></param>
        protected void SetSaveFolder(string note) {
            _saveFolder = IoUtil.CreateSaveFolder(note);
        }

        /// <summary>
        /// 設定値を保存する
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        protected void SetInfo(string key, string value) {
            if (_ShotInfo.ContainsKey(key)) {
                _ShotInfo[key] = value;
            } else {
                _ShotInfo.Add(key, value);
            }
        }

        /// <summary>
        /// 設定値をCSVファイルに書き込む
        /// </summary>
        /// <param name="sw"></param>
        protected void WriteInfo(StreamWriter sw) {
            IoUtil.WriteInfo(sw, _ShotInfo);
        }
    }
}
