#if SELFTEST
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace TIASshot {

    /// <summary>
    /// セルフテスト用の偽カメラ（SELFTEST 限定）．
    /// 実カメラ SDK（Lumenera / IC Imaging Control）には一切触れず，合成フレームを
    /// 専用スレッド（SDK のコールバックスレッドの代替）から流す．
    /// CameraBase の実コード（BootCheck・ProcessCalibrationFrame・DetectChart・CalcTcc・
    /// ShotOne → RunShotLoop → SaveThread → SaveImages）をそのまま通すことを目的とし，
    /// 本クラスで代替するのはカメラ固有部分（フレーム生成・ゲイン反映・撮影開始/終了）のみ．
    /// </summary>
    internal sealed class SelfTestCamera : CameraBase {

        // 合成フレームの生成元（呼び出しごとに新しい Mat を返す．所有権は本クラスへ移る）
        private readonly Func<int, Mat> _frameFactory;

        // 疑似ゲイン（ApplyWhiteBalance で更新し，生成フレームに乗算する）
        private readonly object _gainLock = new object();
        private double _gainB = 1.0;
        private double _gainG = 1.0;
        private double _gainR = 1.0;

        private Thread _previewThread;
        private volatile bool _stopPreview;
        private int _frameIndex;
        private int _framesDelivered;

        // 本クラスが生成した表示用入力 Bitmap（同一性の比較専用．解放は PreviewCallback の二重バッファが行う）
        private readonly List<Bitmap> _producedBitmaps = new List<Bitmap>();
        private readonly object _producedLock = new object();

        public SelfTestCamera(Form1 form, Func<int, Mat> frameFactory) : base(form) {
            _frameFactory = frameFactory;
        }

        /// <summary>プレビュースレッドで発生した例外（無ければ null）</summary>
        public Exception PreviewError { get; private set; }

        /// <summary>コールバックへ渡したフレーム数</summary>
        public int FramesDelivered => Volatile.Read(ref _framesDelivered);

        /// <summary>自動校正が完了したか（CameraBase.CalcTcc が立てるフラグ）</summary>
        public bool IsCalibrated => _calibrated;

        /// <summary>CalcTcc が校正データを保存したフォルダ（RecordCalibrationInfo 時点で記録）</summary>
        public string CalibrationFolder { get; private set; }

        /// <summary>直近の撮影データの保存フォルダ</summary>
        public string CurrentSaveFolder => _saveFolder;

        /// <summary>プレビュースレッドが動作中か</summary>
        public bool IsPreviewRunning => _previewThread != null && _previewThread.IsAlive;

        public override bool Connect() {
            if (!BootCheck()) return false;
            DeviceName = "SelfTestCamera";
            SerialNumber = "SELFTEST";
            SetInfo("カメラ型番", DeviceName);
            SetInfo("カメラSN", SerialNumber);
            return true;
        }

        /// <summary>
        /// 切断．プレビュースレッドに停止を要求し，二重バッファの Bitmap を解放する．
        /// 【前提】プレビュースレッドは UI スレッドへの Invoke で待つことがあるため，
        /// 呼び出し元は UI スレッドのメッセージを回しながら IsPreviewRunning が false になるのを待ってから呼ぶこと．
        /// </summary>
        public override void Disconnect() {
            _stopPreview = true;
            for (var i = 0; i < _bmps.Length; i++) {
                _bmps[i]?.Dispose();
                _bmps[i] = null;
            }
            foreach (var mask in _chartMasks) mask.Dispose();
            _chartMasks.Clear();
            _isPreview = false;
        }

        /// <summary>
        /// プレビュー開始．SDK のコールバックスレッドの代わりに専用スレッドから合成フレームを流す．
        /// </summary>
        /// <param name="maxFrames">流すフレーム数の上限</param>
        /// <param name="intervalMs">フレーム間隔(ms)</param>
        /// <param name="stopWhenCalibrated">校正完了で停止するか</param>
        public void StartPreview(int maxFrames, int intervalMs, bool stopWhenCalibrated) {
            _stopPreview = false;
            _isPreview = true;
            _previewThread = new Thread(() => PreviewLoop(maxFrames, intervalMs, stopWhenCalibrated)) {
                IsBackground = true,
                Name = "SelfTestCamera.Preview",
            };
            _previewThread.Start();
        }

        /// <summary>プレビュー停止を要求する（待機は呼び出し元が UI メッセージを回しながら行う）</summary>
        public void RequestStopPreview() {
            _stopPreview = true;
        }

        private void PreviewLoop(int maxFrames, int intervalMs, bool stopWhenCalibrated) {
            try {
                for (var i = 0; i < maxFrames && !_stopPreview; i++) {
                    using (var imgt = CreateFrame()) {
                        PreviewCallback(imgt);
                    }
                    Interlocked.Increment(ref _framesDelivered);
                    if (stopWhenCalibrated && _calibrated) break;
                    if (intervalMs > 0) Thread.Sleep(intervalMs);
                }
            } catch (Exception ex) {
                // バックグラウンドスレッドの未処理例外はプロセスを落とすため，記録して判定側へ渡す
                Debug.WriteLine($"SelfTestCamera プレビュー例外: {ex}");
                PreviewError = ex;
            }
        }

        /// <summary>
        /// プレビューコールバック．Lucam.PreviewCallback の imgt 生成以降と同じ手順
        /// （二重バッファへ ToBitmap → 校正フレーム処理 → Form1.ShowImage → 反対側バッファを Dispose）．
        /// ShowImage に渡した Bitmap の所有権は本クラスに残り，Form1 は読むだけ（FastClone でコピーする）．
        /// </summary>
        private void PreviewCallback(Mat imgt) {
            var bmp = imgt.ToBitmap();
            _bmps[_bmpIndex] = bmp;
            lock (_producedLock) {
                _producedBitmaps.Add(bmp);
            }
            ProcessCalibrationFrame(imgt);
            _form.ShowImage(_bmps[_bmpIndex]);
            _bmpIndex = (_bmpIndex + 1) % 2;
            if (_bmps[_bmpIndex] != null) _bmps[_bmpIndex].Dispose();
        }

        /// <summary>指定の Image が本クラスの生成した入力 Bitmap そのもの（同一インスタンス）か</summary>
        public bool IsProducedBitmap(Image img) {
            if (img == null) return false;
            lock (_producedLock) {
                foreach (var b in _producedBitmaps) {
                    if (ReferenceEquals(b, img)) return true;
                }
            }
            return false;
        }

        /// <summary>現在の疑似ゲインを乗算した合成フレームを生成する（戻り値の所有権は呼び出し側）</summary>
        public Mat CreateFrame() {
            double gb, gg, gr;
            lock (_gainLock) {
                gb = _gainB;
                gg = _gainG;
                gr = _gainR;
            }
            var idx = Interlocked.Increment(ref _frameIndex) - 1;
            using (var baseFrame = _frameFactory(idx)) {
                var frame = new Mat();
                try {
                    Cv2.Multiply(baseFrame, new Scalar(gb, gg, gr), frame);
                    return frame;
                } catch {
                    frame.Dispose();
                    throw;
                }
            }
        }

        /// <summary>現在のチャートマスクで白色票（13 番）の平均値を計測する（校正完了後に使う）</summary>
        public Scalar MeasureWhitePatch() {
            using (var frame = CreateFrame()) {
                return Cv2.Mean(frame, _chartMasks[12]);
            }
        }

        /// <summary>現在の疑似ゲイン（B, G, R）</summary>
        public string DescribeGain() {
            lock (_gainLock) {
                return $"B={_gainB:0.000} G={_gainG:0.000} R={_gainR:0.000}";
            }
        }

        // ---- CameraBase のテンプレートメソッド（カメラ固有部分の代替） ----------------------

        protected override void ApplyWhiteBalance(Scalar whitePatch) {
            // Lucam と同じくゲインに更新比率（CameraBase.GetRatio＝実コード）を掛ける
            lock (_gainLock) {
                _gainB *= GetRatio((float)whitePatch.Val0, Config.GetFloat("Calib/Reference/B"));
                _gainG *= GetRatio((float)whitePatch.Val1, Config.GetFloat("Calib/Reference/G"));
                _gainR *= GetRatio((float)whitePatch.Val2, Config.GetFloat("Calib/Reference/R"));
            }
        }

        protected override void LogCalibrationConverged() {
            Debug.WriteLine($"SelfTestCamera ゲイン収束: {DescribeGain()}");
        }

        protected override void RecordCalibrationInfo(Mat imgt) {
            CalibrationFolder = _saveFolder;
            SetInfo("SelfTestGain", DescribeGain());
        }

        /// <summary>
        /// 撮影セッション本体．CameraBase.Shot の XML ドキュメントに記載のライフサイクル順序どおりに呼ぶ．
        /// </summary>
        protected override void Shot(int numImages = 1, int interval = 0) {
            WaitPreviousSaveThread();
            PrepareShotSession();
            ResetShotQueue();
            StartSaveThread(numImages);
            try {
                WaitSaveThreadReady();
                BeginShot();
                RunShotLoop(numImages, interval);
            } finally {
                EndShotSession();
            }
        }

        protected override void BeginShot() {
            Debug.WriteLine("SelfTestCamera 撮影開始");
        }

        protected override Mat CaptureFrame() {
            return CreateFrame();
        }

        protected override void EndShot() {
            Debug.WriteLine("SelfTestCamera 撮影終了");
        }
    }
}
#endif
