#if SELFTEST
using System;
using OpenCvSharp;
using OpenCvSharp.Aruco;

namespace TIASshot {

    /// <summary>
    /// セルフテスト用の合成舌診チャート（SELFTEST 限定）．実画像を使わず，ChartDetector の
    /// チャート座標系（810x1545，ArUco Dict4X4_50 の ID 40（上）/ 41（下），24 色票）どおりに描く．
    /// 色票の色は TCC の sRGB 参照値（tcc_srgb.csv）をチャネルごとの 1 次式で「カメラ色」に歪めたもの．
    /// 歪みが 1 次式なので，校正で求まる変換行列を掛ければ参照値へほぼ戻る（色変換の検算に使える）．
    /// </summary>
    internal static class SelfTestChart {

        // ChartDetector のチャート座標系
        public const int ChartWidth = 810;
        public const int ChartHeight = 1545;
        private const int MarkerSize = 120;                // PointsDst40/41 の一辺（345→465）
        private static readonly Point Marker40 = new Point(345, 130);
        private static readonly Point Marker41 = new Point(345, 1200);
        private const int PatchRoiSize = 80;               // ChartDetector のマスク一辺（マーカー 2 個検出時）
        private const int PatchMargin = 15;                // ROI の外側に描く余白（検出誤差の吸収）

        // フレーム内でのチャートの配置（カメラ画像の中にチャートが写っている状況を模す）
        public const int FrameWidth = 960;
        public const int FrameHeight = 1700;
        public const int OffsetX = 75;
        public const int OffsetY = 78;

        // 「カメラ色」への歪み（チャネルごとの 1 次式 v' = a*v + b．B, G, R の順）
        private static readonly double[] DistortA = { 0.72, 0.80, 0.76 };
        private static readonly double[] DistortB = { 12.0, 8.0, 10.0 };

        /// <summary>
        /// i 番目の色票 ROI（ChartDetector と同じ式）のフレーム座標を返す．
        /// </summary>
        public static Rect PatchRoi(int i) {
            var row = i % 6;
            var col = i / 6;
            var x = 581 - col * 144;
            var y = 318 + row * 144;
            return new Rect(OffsetX + x, OffsetY + y, PatchRoiSize, PatchRoiSize);
        }

        /// <summary>
        /// 合成チャートを写したフレーム（CV_8UC3・BGR）を生成する．戻り値の所有権は呼び出し側．
        /// </summary>
        /// <param name="tccSrgb">TCC の sRGB 参照値（24x3・B,G,R 列）</param>
        public static Mat CreateChartFrame(Mat tccSrgb) {
            var frame = new Mat(FrameHeight, FrameWidth, MatType.CV_8UC3, new Scalar(60, 60, 60));
            try {
                // チャート台紙
                Cv2.Rectangle(frame, new Rect(OffsetX, OffsetY, ChartWidth, ChartHeight),
                    new Scalar(200, 200, 200), Cv2.FILLED);

                // ArUco マーカー（ID 40 が上，ID 41 が下）
                DrawMarker(frame, 40, Marker40);
                DrawMarker(frame, 41, Marker41);

                // 24 色票（ROI より一回り大きく塗る）
                for (var i = 0; i < 24; i++) {
                    var roi = PatchRoi(i);
                    var patch = new Rect(roi.X - PatchMargin, roi.Y - PatchMargin,
                        PatchRoiSize + PatchMargin * 2, PatchRoiSize + PatchMargin * 2);
                    Cv2.Rectangle(frame, patch, CameraColor(tccSrgb, i), Cv2.FILLED);
                }
                return frame;
            } catch {
                frame.Dispose();
                throw;
            }
        }

        /// <summary>i 番目の色票の「カメラ色」（参照値を 1 次式で歪めた BGR）</summary>
        public static Scalar CameraColor(Mat tccSrgb, int i) {
            var v = new double[3];
            for (var c = 0; c < 3; c++) {
                var ref_ = tccSrgb.At<double>(i, c);
                v[c] = Math.Max(0, Math.Min(255, DistortA[c] * ref_ + DistortB[c]));
            }
            return new Scalar(v[0], v[1], v[2]);
        }

        private static void DrawMarker(Mat frame, int id, Point chartPos) {
            using (var dict = CvAruco.GetPredefinedDictionary(PredefinedDictionaryType.Dict4X4_50))
            using (var marker = new Mat())
            using (var markerBgr = new Mat()) {
                dict.GenerateImageMarker(id, MarkerSize, marker, 1);
                Cv2.CvtColor(marker, markerBgr, ColorConversionCodes.GRAY2BGR);
                using (var dst = new Mat(frame, new Rect(OffsetX + chartPos.X, OffsetY + chartPos.Y, MarkerSize, MarkerSize))) {
                    markerBgr.CopyTo(dst);
                }
            }
        }

        /// <summary>
        /// プレビュー確認用の単色フレーム（CV_8UC3・BGR）を生成する．フレーム番号で色を変え，
        /// 表示されたのがどのフレームのコピーかを画素値で判別できるようにする．
        /// </summary>
        public static Mat CreatePlainFrame(int index, int width, int height) {
            return new Mat(height, width, MatType.CV_8UC3, PlainFrameColor(index));
        }

        /// <summary>CreatePlainFrame の index 番目の色（B, G, R）</summary>
        public static Scalar PlainFrameColor(int index) {
            return new Scalar(20 + (index * 10) % 200, 100, 220 - (index * 10) % 200);
        }
    }
}
#endif
