#if HWTEST
#if !SELFTEST
#error HWTEST は SELFTEST の部品（SelfTestConfig・SelfTestReport・SelfTestPump・閉じる処理の段階の記録）を使うため，SELFTEST と同時に定義すること
#endif
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

namespace TIASshot {
    public partial class Form1 {

        /// <summary>
        /// 実機テスト用コンストラクタの目印型（HWTEST 限定）．
        /// </summary>
        internal sealed class HwTestMarker {
            internal static readonly HwTestMarker Instance = new HwTestMarker();
            private HwTestMarker() { }
        }

        // 実機テストのモードか（Form1_Load の自動接続をしない・ShowError をモーダル表示しない・メモリ観測窓を開かない）
        private bool _hwTestMode = false;

        // ShowImage に届いたフレームの数（コールバックスレッドが書き，UI スレッドが読む）
        private long _hwTestFrames = 0;
        private int _hwTestFrameWidth = 0;
        private int _hwTestFrameHeight = 0;
        // フレームの記録中の例外（最初の 1 件）
        private string _hwTestFrameError = null;

        // 平均輝度・飽和画素の割合の測定の要求と結果（_hwTestSampleLock で保護）
        private readonly object _hwTestSampleLock = new object();
        private int _hwTestSamplesWanted = 0;
        private readonly List<HwTestFrameStats> _hwTestSamples = new List<HwTestFrameStats>();

        // ShowError に渡されたメッセージ（_hwTestSampleLock で保護）
        private readonly List<string> _hwTestErrors = new List<string>();

        // 直近の接続失敗の理由（_hwTestSampleLock で保護）
        private readonly List<string> _hwTestConnectErrors = new List<string>();

        /// <summary>
        /// 実機テスト用コンストラクタ（HWTEST 限定）．通常のコンストラクタ（InitializeComponent を含む）を通したうえで，
        /// 実機テストのモードにする．表示（Show）すると Form1_Load が走るが，自動接続はしない（HwTestConnect で機種を指定して接続する）．
        /// </summary>
        internal Form1(HwTestMarker marker) : this() {
            _hwTestMode = true;
            Text += " [HWTEST]";
        }

        // ================================================================================
        // カメラ・光源の接続
        // ================================================================================

        /// <summary>接続されている Lumenera カメラの台数（SDK が無い・列挙に失敗した場合は例外）</summary>
        internal static int HwTestCountLucam() {
            return Lucam.HwTestCountCameras();
        }

        /// <summary>見つかった ImagingSource カメラの名前（IC Imaging Control の列挙）</summary>
        internal string[] HwTestListIScam() {
            return icImagingControl1.Devices.Select(d => d.Name).ToArray();
        }

        /// <summary>
        /// 指定した機種だけを接続する（通常起動の Form1_Load と同じ接続処理を使う．ただし Lucam 優先の自動選択はしない）．
        /// 接続できたら現在のカメラ（_camera）にする．
        /// </summary>
        /// <returns>接続できたカメラ．できなければ null</returns>
        internal CameraBase HwTestConnect(HwTestCameraKind kind) {
            var camera = kind == HwTestCameraKind.Lucam ? ConnectLucam() : ConnectIScam();
            _camera = camera;
            return camera;
        }

        /// <summary>
        /// 接続後の画面・光源・副モニタ・本モニタの再描画タイマーの開始（通常起動の Form1_Load と同じ処理）．
        /// </summary>
        internal void HwTestStartSession() {
            StartSession();
        }

        partial void OnConnectFailed(string device, string error) {
            lock (_hwTestSampleLock) {
                _hwTestConnectErrors.Add($"{device}: {error}");
            }
        }

        /// <summary>接続失敗の理由を取り出す（古い順．取り出した分は消す）</summary>
        internal List<string> HwTestTakeConnectErrors() {
            lock (_hwTestSampleLock) {
                var result = _hwTestConnectErrors.ToList();
                _hwTestConnectErrors.Clear();
                return result;
            }
        }

        /// <summary>現在のカメラ（未接続なら null）</summary>
        internal CameraBase HwTestCamera => _camera;

        /// <summary>現在のカメラを切断する（閉じる処理を通さない．再接続の検査・後始末用）</summary>
        internal void HwTestDisconnectCamera() {
            _camera?.Disconnect();
        }

        /// <summary>光源のポートが開いているか</summary>
        internal bool HwTestLightIsOpen => _light.IsOpen;

        /// <summary>光源の点灯状態（画面の照明ボタンが管理する状態）</summary>
        internal bool HwTestLightOn => _isLightOn;

        /// <summary>照明ボタンの表示文</summary>
        internal string HwTestLightButtonText => btnLightSW.Text;

        /// <summary>照明ボタンを押す（通常の操作と同じ処理．校正中は何もしない）</summary>
        internal void HwTestClickLightButton() {
            btnLightSW_Click(btnLightSW, EventArgs.Empty);
        }

        /// <summary>光源のポートを閉じる（閉じる処理と同じ LightSource.Close．消灯はしない）</summary>
        internal void HwTestCloseLight() {
            _light.Close();
        }

        /// <summary>
        /// 光源のポートを開き直して点灯する（Form1_Load と同じ手順）．画面の照明ボタンの状態も点灯に合わせる．
        /// </summary>
        /// <returns>開けたら true</returns>
        internal bool HwTestReopenLight() {
            if (!_light.Open()) return false;
            _light.SetLight();
            _isLightOn = true;
            btnLightSW.Text = "照明 OFF";
            btnLightSW.Enabled = true;
            return true;
        }

        /// <summary>
        /// ウォッチドッグ（別スレッド）からの光源のポートの緊急クローズ（タイムアウト時の後始末）．
        /// 消灯はしない（終了時と同じく光源は点いたまま残る）．LightSource.Close はクローズの失敗をログに残して戻る．
        /// </summary>
        internal void HwTestEmergencyCloseLight() {
            _light.Close();
        }

        // ================================================================================
        // 閉じる処理
        // ================================================================================

        /// <summary>
        /// ユーザーが閉じようとしたときの処理（Form1_FormClosing）を呼ぶ（UI スレッドで呼ぶこと）．
        /// 閉じると決まった場合は後始末（閉じ始めのフラグ → タイマー停止 → カメラ切断 → 光源）まで行う．
        /// フォームは破棄しない（後続の再接続の検査で使う）．
        /// </summary>
        /// <returns>閉じると決まったら true，取り消されたら（保存中）false</returns>
        internal bool HwTestRequestClose() {
            var e = new FormClosingEventArgs(CloseReason.UserClosing, false);
            Form1_FormClosing(this, e);
            return !e.Cancel;
        }

        /// <summary>閉じるときの後始末が通過した段階（SELFTEST の記録を使う）</summary>
        internal IReadOnlyList<string> HwTestCloseSteps => SelfTestCloseSteps;

        /// <summary>閉じ始めたか</summary>
        internal bool HwTestIsClosing => _isClosing;

        /// <summary>本モニタの再描画タイマーが動作中か</summary>
        internal bool HwTestRefreshTimerRunning => _mainRefreshTimer.Enabled;

        /// <summary>本モニタ（picDisplay）に表示中の画像</summary>
        internal Image HwTestDisplayedImage => picDisplay.Image;

        /// <summary>
        /// 閉じる処理で後始末まで済んだ後に，次の検査のために画面の状態を接続直後に戻す（閉じる処理を 2 回以上検査するため）．
        /// 閉じ始めのフラグを下ろし（以降の ShowMessage・EnableShots を通常どおり効かせる），撮影ボタンを無効にし，
        /// 本モニタの再描画タイマーを動かし直し，光源のポートを開き直して点灯する（StartSession と同じ状態．
        /// StartSession を 2 回呼ぶとタイマーの Tick の登録と副モニタが重複するため，ここで必要な分だけ戻す）．
        /// カメラは接続しない（呼び出し側が HwTestConnect で接続する）．
        /// </summary>
        /// <returns>光源のポートを開き直せたら true</returns>
        internal bool HwTestResumeAfterClose() {
            _isClosing = false;
            EnableShots(false);
            _mainRefreshTimer.Start();
            // 開いたままのポートを開き直すと SerialPort が例外を投げるため，閉じているときだけ開く
            if (_light.IsOpen) return true;
            return HwTestReopenLight();
        }

        // ================================================================================
        // 撮影
        // ================================================================================

        /// <summary>データ名（txtDataName．保存フォルダ名の一部）を設定する</summary>
        internal void HwTestSetDataName(string dataName) {
            txtDataName.Text = dataName;
        }

        /// <summary>連続撮影の枚数・間隔(ms)の入力欄を設定する</summary>
        internal void HwTestSetMultiShots(int numShots, int intervalMs) {
            txtNumMultiShots.Text = numShots.ToString(System.Globalization.CultureInfo.InvariantCulture);
            txtMultiShotsInterval.Text = intervalMs.ToString(System.Globalization.CultureInfo.InvariantCulture);
        }

        /// <summary>撮影ボタン（単発・連続）が両方とも有効か</summary>
        internal bool HwTestShotsEnabled => btnShotOne.Enabled && btnShotMulti.Enabled;

        /// <summary>ステータス表示（txtMessage）の現在値</summary>
        internal string HwTestMessage => txtMessage.Text;

        /// <summary>単発撮影のボタンを押す（通常の操作と同じ処理．撮影は UI スレッドで同期に行われ，保存は保存スレッドで続く）</summary>
        internal void HwTestClickShotOne() {
            btnShot1_Click(btnShotOne, EventArgs.Empty);
        }

        /// <summary>連続撮影のボタンを押す（通常の操作と同じ処理）</summary>
        internal void HwTestClickShotMulti() {
            btnShotMulti_Click(btnShotMulti, EventArgs.Empty);
        }

        // ================================================================================
        // フレームの観測
        // ================================================================================

        /// <summary>ShowImage に届いたフレームの数</summary>
        internal long HwTestFrameCount => Interlocked.Read(ref _hwTestFrames);

        /// <summary>最後に届いたフレームの大きさ</summary>
        internal Size HwTestLastFrameSize => new Size(Volatile.Read(ref _hwTestFrameWidth), Volatile.Read(ref _hwTestFrameHeight));

        /// <summary>フレームの記録中に起きた例外（無ければ null）</summary>
        internal string HwTestFrameError => Volatile.Read(ref _hwTestFrameError);

        /// <summary>ShowError に渡されたメッセージ（古い順のコピー）</summary>
        internal IReadOnlyList<string> HwTestErrors {
            get { lock (_hwTestSampleLock) return _hwTestErrors.ToArray(); }
        }

        /// <summary>
        /// 次に届く count 枚のフレームの平均輝度・飽和画素の割合の測定を要求する（それまでの測定結果は捨てる）．
        /// </summary>
        internal void HwTestRequestSamples(int count) {
            lock (_hwTestSampleLock) {
                _hwTestSamples.Clear();
                _hwTestSamplesWanted = count;
            }
        }

        /// <summary>測定済みのフレーム数</summary>
        internal int HwTestSampleCount {
            get { lock (_hwTestSampleLock) return _hwTestSamples.Count; }
        }

        /// <summary>測定結果を取り出す（未達の要求は取り消す）</summary>
        internal List<HwTestFrameStats> HwTestTakeSamples() {
            lock (_hwTestSampleLock) {
                _hwTestSamplesWanted = 0;
                var result = _hwTestSamples.ToList();
                _hwTestSamples.Clear();
                return result;
            }
        }

        /// <summary>
        /// ShowImage（カメラのコールバックスレッド）から呼ばれるフレームの記録．入力 bmp は読むだけ（所有権は呼び出し側）．
        /// 例外をコールバックへ伝えるとプレビューが止まるため，記録して戻る（実機テストの結果で FAIL として報告する）．
        /// </summary>
        private void HwTestOnFrame(Bitmap bmp) {
            if (!_hwTestMode) return;
            try {
                Interlocked.Increment(ref _hwTestFrames);
                Volatile.Write(ref _hwTestFrameWidth, bmp.Width);
                Volatile.Write(ref _hwTestFrameHeight, bmp.Height);

                bool wanted;
                lock (_hwTestSampleLock) wanted = _hwTestSamplesWanted > 0;
                if (!wanted) return;

                var stats = MeasureFrameStats(bmp);
                lock (_hwTestSampleLock) {
                    if (_hwTestSamplesWanted > 0) {
                        _hwTestSamples.Add(stats);
                        _hwTestSamplesWanted--;
                    }
                }
            } catch (Exception ex) {
                Debug.WriteLine($"[HWTEST] フレームの記録に失敗しました: {ex}");
                Interlocked.CompareExchange(ref _hwTestFrameError, $"{ex.GetType().FullName}: {ex.Message}", null);
            }
        }

        /// <summary>
        /// フレームの平均輝度と飽和画素の割合を求める．間引いた行だけを複製して純粋関数（HwTestJudge.FrameStats）に渡す．
        /// </summary>
        private static HwTestFrameStats MeasureFrameStats(Bitmap bmp) {
            var bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
            if (bytesPerPixel < 1 || bytesPerPixel > 4) {
                throw new NotSupportedException($"平均輝度を求められない画素形式です: {bmp.PixelFormat}");
            }
            var step = HwTestThresholds.PixelStep;
            var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
            try {
                var rowCount = (bmp.Height + step - 1) / step;
                var rowBytes = bmp.Width * bytesPerPixel;
                var rows = new byte[(long)rowCount * rowBytes];
                for (var r = 0; r < rowCount; r++) {
                    var src = IntPtr.Add(data.Scan0, r * step * data.Stride);
                    Marshal.Copy(src, rows, r * rowBytes, rowBytes);
                }
                return HwTestJudge.FrameStats(rows, rowCount, bmp.Width, rowBytes, bytesPerPixel, step, HwTestThresholds.SaturatedPixelLevel);
            } finally {
                bmp.UnlockBits(data);
            }
        }

        /// <summary>ShowError の実機テスト版（モーダル表示すると無人実行が止まるため記録のみ行う）</summary>
        private void HwTestRecordError(string msg) {
            Debug.WriteLine($"[HWTEST] ShowError: {msg}");
            lock (_hwTestSampleLock) {
                _hwTestErrors.Add(msg);
            }
        }
    }
}
#endif
