#if HWTEST
using System;
using TIS.Imaging;

namespace TIASshot {

    /// <summary>
    /// IScam の実機テスト用の口（HWTEST 限定）．SDK を呼ぶだけの薄い処理に留める．
    /// 露光・ゲインはカメラ（ドライバ）上の値だけを変え，デバイス状態ファイル（{DeviceName}.xml）には書き込まない．
    /// 本番の撮影・校正が使う VCDRangeProperty（_exposure・_gain）の読み書きには触れない．
    /// </summary>
    /// <remarks>
    /// 露光・ゲインは VCDAbsoluteValueProperty（絶対値．double）で読み書きする．
    /// 根拠:
    /// <list type="bullet">
    /// <item>IC Imaging Control 3.5 のヘルプ（dotnethelp.chm「VCD Properties」tech_VCDProperties.htm）:
    /// 「The VCDAbsoluteValueProperty allows the property to be accessed in terms of a physical dimension with high accuracy」．
    /// 一方 VCDRangeProperty は「This interface does not provide a meaning for the value」で，値は Int32（参照 DLL のリフレクションで
    /// VCDRangeProperty.Value・RangeMin・RangeMax は System.Int32，VCDAbsoluteValueProperty.Value・RangeMin・RangeMax・Default は System.Double，
    /// DimType は System.String）．</item>
    /// <item>取得は VCDPropertyItems.Find&lt;VCDAbsoluteValueProperty&gt;(VCDGUIDs.VCDID_Exposure, VCDGUIDs.VCDElement_Value)
    /// （ヘルプ meth_descVCDPropertyItems_Find.htm．該当する口が無ければ null を返す）．</item>
    /// <item>デバイス状態ファイル（DFK33UX178.xml）の Exposure/Value は interface GUID {99B44940-BFE1-4083-ADA1-BE703F4B8E08}
    /// （= VCDGUIDs.VCDInterface_AbsoluteValue．リフレクションで確認）で value="0.016667"（1/60 秒）と保存されている．
    /// つまり起動時の露光は絶対値で決まっており，VCDRangeProperty の整数値（-5 と読める．値 n ≒ 2^n 秒の段）では表せない．
    /// 前回の実機テストは整数値 -5 を「元の値」として書き戻したため 1/32 秒（元の 1.875 倍）になり，読み戻しもどちらも -5 で見逃した．</item>
    /// <item>単位: 露光の絶対値は秒（状態ファイルの 0.016667 が 1/60 秒に当たる．実測の平均輝度の比 0.936 ≒ 60/64 とも一致）．
    /// 実行時の DimType も結果ファイルに出す．</item>
    /// </list>
    /// ゲインも同じ状態ファイルで絶対値の口（...8E08．value="0.000000"）で保存されているため，絶対値が取れれば絶対値で読み書きする．
    /// 絶対値の口が無い機種に限り VCDRangeProperty の整数値で読み書きする（整数値で読んだ値をそのまま書き戻すので整数値の範囲では元に戻る）．
    /// 露光は絶対値の口が無いと元の値へ正確に戻せない（上記の不具合）ため，口が無ければ例外にする（exposure-gain が FAIL になる）．
    /// </remarks>
    internal partial class IScam : IHwTestAdjustable {

        public string HwTestExposureUnit {
            get {
                var abs = FindAbsolute(VCDGUIDs.VCDID_Exposure);
                return abs != null
                    ? $"秒（VCDAbsoluteValueProperty の Exposure．DimType「{abs.DimType}」）"
                    : "（VCDAbsoluteValueProperty の Exposure が取得できません）";
            }
        }

        /// <summary>
        /// IScam の露光（VCDAbsoluteValueProperty．秒）は線形の量だが，検査では 2^n 秒のきりのよい段で動かす
        /// （基準探しの前に最も近い 2^n 秒に合わせ，1 段 = 時間で 2 倍・半分）．元の値への復元は元の絶対値で行う．
        /// 本番の撮影・校正の露光の決め方には関係しない（検査で動かす量にだけ使う）．
        /// </summary>
        public HwTestValueScale HwTestExposureScale => HwTestValueScale.Log2Step;

        public string HwTestGainUnit {
            get {
                var abs = FindAbsolute(VCDGUIDs.VCDID_Gain);
                return abs != null
                    ? $"VCDAbsoluteValueProperty の Gain（DimType「{abs.DimType}」）"
                    : "VCDRangeProperty の値（Gain．絶対値の口が無いため整数値）";
            }
        }

        public HwTestSetting HwTestGetExposure() {
            var abs = RequireExposureAbsolute();
            return new HwTestSetting(abs.Value, abs.RangeMin, abs.RangeMax);
        }

        public void HwTestSetExposure(double value) {
            var abs = RequireExposureAbsolute();
            abs.Value = HwTestJudge.Clamp(value, abs.RangeMin, abs.RangeMax);
        }

        public HwTestSetting HwTestGetGain() {
            var abs = FindAbsolute(VCDGUIDs.VCDID_Gain);
            if (abs != null) return new HwTestSetting(abs.Value, abs.RangeMin, abs.RangeMax);
            var range = RequireRange(_gain, "Gain");
            return new HwTestSetting(range.Value, range.RangeMin, range.RangeMax);
        }

        public void HwTestSetGain(double value) {
            var abs = FindAbsolute(VCDGUIDs.VCDID_Gain);
            if (abs != null) {
                abs.Value = HwTestJudge.Clamp(value, abs.RangeMin, abs.RangeMax);
                return;
            }
            var range = RequireRange(_gain, "Gain");
            range.Value = (int)Math.Round(HwTestJudge.Clamp(value, range.RangeMin, range.RangeMax));
        }

        /// <summary>
        /// 値の要素（VCDElement_Value）の絶対値の口を探す（無ければ null）．呼ぶたびに探す
        /// （再接続で古い口を使わないため．検査の 1 段あたり数回なので負荷は問題にならない）．
        /// </summary>
        private VCDAbsoluteValueProperty FindAbsolute(Guid itemId) {
            if (_ic == null || !_ic.DeviceValid) return null;
            return _ic.VCDPropertyItems.Find<VCDAbsoluteValueProperty>(itemId, VCDGUIDs.VCDElement_Value);
        }

        private VCDAbsoluteValueProperty RequireExposureAbsolute() {
            if (_ic == null || !_ic.DeviceValid) throw new InvalidOperationException("Exposure プロパティを取得できません（未接続）");
            var abs = FindAbsolute(VCDGUIDs.VCDID_Exposure);
            if (abs == null) {
                throw new InvalidOperationException("Exposure の絶対値（VCDAbsoluteValueProperty）を取得できません（整数値では元の露光へ正確に戻せないため検査しません）");
            }
            return abs;
        }

        private static VCDRangeProperty RequireRange(VCDRangeProperty property, string name) {
            if (property == null) throw new InvalidOperationException($"{name} プロパティを取得できていません（未接続）");
            return property;
        }
    }
}
#endif
