#if MEMMONITOR
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace TIASshot {

    /// <summary>
    /// メモリ観測ウィンドウ（MEMMONITOR 限定）。
    /// TIASshot 本体プロセスのメモリ使用量をライブ折れ線グラフで表示する。
    /// 研究室デモ用：実使用時にメモリが増え続けないことを画面録画で示す。
    /// </summary>
    public partial class MemoryMonitorForm : Form {

        // ---- GDI/USER ハンドル数取得（GDI ハンドル枯渇の疑いを確認するため）------
        [DllImport("user32.dll")]
        private static extern uint GetGuiResources(IntPtr hProcess, uint uiFlags);
        private const uint GR_GDIOBJECTS = 0;
        private const uint GR_USEROBJECTS = 1;

        // ---- サンプリング記録 -------------------------------------------------
        private struct MemSample {
            public double ElapsedSec;
            public double PrivateMB;
            public double WorkingSetMB;

            // プレビュー計測（コピー負荷 vs UI 描画負荷の切り分け用。PreviewProfiler.Snapshot() の区間値）
            public double CallbackFps;     // ShowImage 実効フレームレート（コールバックスレッド律速の指標）
            public double MainPaintFps;    // MainRefreshTimer_Tick の実スワップレート
            public double SubUpdateFps;    // PreviewMonitor.UpdateImage 呼び出しレート
            public double SubPaintFps;     // PreviewMonitor_Paint の実スワップレート（副モニタ描画側の指標）
            public double SubDropFps;      // 副モニタ側の取りこぼしレート
            public double SubPaintMs;      // picPreview の実描画（base.OnPaint）平均所要時間
            public double MainCopyMs;      // 本モニタ用コピー（dispCopy）の平均所要時間
            public double SubCopyMs;       // 副モニタ用コピーの平均所要時間
            public uint GdiObjects;        // プロセスの GDI オブジェクト数（GetGuiResources GR_GDIOBJECTS）
            public uint UserObjects;       // プロセスの USER オブジェクト数（GetGuiResources GR_USEROBJECTS）
            public int HandleCount;        // プロセスのハンドル数（Process.HandleCount）
            public int ImgW;               // 直近に表示したフレームの幅
            public int ImgH;               // 直近に表示したフレームの高さ
            public bool SubShown;          // 区間内に副モニタが表示されていたか
            public string Event;           // "SUB_ON"/"SUB_OFF"/"CALIB_START"/"CALIB_END"/"SHOT_START"/"SHOT_END" を ";" 連結（無ければ ""）
        }

        private readonly List<MemSample> _samples = new List<MemSample>();
        private readonly System.Windows.Forms.Timer _timer;
        private readonly Stopwatch _stopwatch = new Stopwatch();
        private bool _isRunning;
        private double _lastTickElapsedSec = 0.0;

        // CSV 出力先
        private readonly string _outputDir;

        // ---- コンストラクタ ---------------------------------------------------

        public MemoryMonitorForm() {
            InitializeComponent();

            // F9 で副モニタ計測トグル（Form1.ToggleSubMonitorProfiling）を，
            // このウィンドウにフォーカスがある場合にも呼び出せるようにする。
            // このフォームは ShowMemoryMonitor() で Show(this) されるため Owner は Form1。
            this.KeyPreview = true;
            this.KeyDown += MemoryMonitorForm_KeyDown;

            // モードラベルとウィンドウタイトルをビルド種別に応じて設定
#if LEAKY_REPRO
            lblMode.Text = "モード: リーク再現（修正前）";
            lblMode.ForeColor = System.Drawing.Color.Red;
            this.Text = "メモリ観測ウィンドウ [MEMMONITOR] — リーク再現";
#else
            lblMode.Text = "モード: 修正後";
            lblMode.ForeColor = System.Drawing.Color.Green;
            this.Text = "メモリ観測ウィンドウ [MEMMONITOR] — 修正後";
#endif

            _outputDir = Path.Combine(
                AppDomain.CurrentDomain.BaseDirectory,
                "MemMonitor_Results");
            Directory.CreateDirectory(_outputDir);

            _timer = new System.Windows.Forms.Timer();
            _timer.Interval = 500;
            _timer.Tick += Timer_Tick;

            this.FormClosing += (s, e) => {
                _timer.Stop();
            };

            // ウィンドウを開いたら自動でサンプリング開始
            this.Shown += (s, e) => StartSampling();
        }

        // ---- キー操作 --------------------------------------------------------

        private void MemoryMonitorForm_KeyDown(object sender, KeyEventArgs e) {
            if (e.KeyCode == Keys.F9) {
                (this.Owner as Form1)?.ToggleSubMonitorProfiling();
                e.Handled = true;
            }
        }

        // ---- サンプリング制御 ------------------------------------------------

        private void StartSampling() {
            if (_isRunning) return;
            _isRunning = true;
            _stopwatch.Start();
            _timer.Start();
            btnStartStop.Text = "停止";
        }

        private void StopSampling() {
            if (!_isRunning) return;
            _isRunning = false;
            _stopwatch.Stop();
            _timer.Stop();
            btnStartStop.Text = "開始";
        }

        // ---- タイマー Tick（サンプリング本体）--------------------------------

        private void Timer_Tick(object sender, EventArgs e) {
            // Process.Refresh() で最新値を取得（GC 強制なし）
            using (var proc = Process.GetCurrentProcess()) {
                proc.Refresh();

                double elapsedSec = _stopwatch.Elapsed.TotalSeconds;
                double privateMB = proc.PrivateMemorySize64 / (1024.0 * 1024.0);
                double wsMB = proc.WorkingSet64 / (1024.0 * 1024.0);

                // GDI/USER ハンドル数・プロセスハンドル数（GDI ハンドル枯渇による描画劣化の疑いを確認する）
                uint gdiObjects = GetGuiResources(proc.Handle, GR_GDIOBJECTS);
                uint userObjects = GetGuiResources(proc.Handle, GR_USEROBJECTS);
                int handleCount = proc.HandleCount;

                // プレビュー計測区間の実経過秒（Timer.Interval のブレを吸収するため実測差分を使う）
                double intervalSec = elapsedSec - _lastTickElapsedSec;
                if (intervalSec <= 0) intervalSec = _timer.Interval / 1000.0;
                _lastTickElapsedSec = elapsedSec;

                // PreviewProfiler.Snapshot() は呼び出しごとにカウンタをゼロリセットして区間値を返す。
                // ここで各段の実効 FPS とコピー時間・描画時間平均を算出し，
                //   - 副モニタ ON で callback_fps が大きく落ちる → コピー／コールバックスレッド律速
                //   - callback_fps は高いのに sub_paint_fps が sub_update_fps より大幅に低い → 副モニタの UI 描画律速
                //   - CALIB_END/SHOT_END 直後に sub_paint_ms が跳ね上がる → 描画コスト増（副モニタ描画律速）
                //     跳ね上がらず sub_paint_fps だけ落ちる → メッセージ枯渇/別要因
                //   - gdi_objects/user_objects/handle_count が撮影のたびに増え続け sub_paint_ms 増加と相関
                //     → GDI ハンドル枯渇による描画劣化の疑い
                //   - img_w/img_h が校正前後で変化 → プレビュー画像サイズ肥大による拡大負荷増の疑い
                // を切り分けるための材料とする。
                var snap = PreviewProfiler.Snapshot();
                double callbackFps = snap.CallbackFrames / intervalSec;
                double mainPaintFps = snap.MainPaints / intervalSec;
                double subUpdateFps = snap.SubUpdates / intervalSec;
                double subPaintFps = snap.SubPaints / intervalSec;
                double subDropFps = snap.SubDrops / intervalSec;

                // 保留イベント（F9 トグル・校正・撮影）を消費する。
                // イベントは 500ms 区間内の直近サンプルに現れる（分析上この粒度で十分）。
                string eventLabel = PreviewProfiler.ConsumeEvents();

                // 記録
                _samples.Add(new MemSample {
                    ElapsedSec = elapsedSec,
                    PrivateMB = privateMB,
                    WorkingSetMB = wsMB,
                    CallbackFps = callbackFps,
                    MainPaintFps = mainPaintFps,
                    SubUpdateFps = subUpdateFps,
                    SubPaintFps = subPaintFps,
                    SubDropFps = subDropFps,
                    SubPaintMs = snap.SubPaintMs,
                    MainCopyMs = snap.MainCopyMs,
                    SubCopyMs = snap.SubCopyMs,
                    GdiObjects = gdiObjects,
                    UserObjects = userObjects,
                    HandleCount = handleCount,
                    ImgW = snap.ImgW,
                    ImgH = snap.ImgH,
                    SubShown = snap.SubShown,
                    Event = eventLabel,
                });

                // グラフ追加
                double privateGB = privateMB / 1024.0;
                double wsGB = wsMB / 1024.0;

                // 左 Y 軸: メモリ主指標 Private（1 本）。右 Y 軸（副軸）: 本／副モニタの実描画 FPS。
                // WorkingSet はグラフに描かず CSV にのみ残す（主指標 1 本を録画で強調するため）。
                chart.Series["Private"].Points.AddXY(elapsedSec, privateGB);
                chart.Series["MainFps"].Points.AddXY(elapsedSec, mainPaintFps);
                chart.Series["SubFps"].Points.AddXY(elapsedSec, subPaintFps);

                // Y 軸オートスケール追従（左右両軸）
                chart.ChartAreas["ChartArea1"].RecalculateAxesScale();

                // ラベル更新
                int n = (int)Math.Round(elapsedSec);
                lblCurrent.Text =
                    $"現在: Private {privateGB:F2} GB / WS {wsGB:F2} GB（経過 {n}s）";

                string eventSuffix = string.IsNullOrEmpty(eventLabel) ? "" : $" / event {eventLabel}";
                lblPreview.Text =
                    $"CB {callbackFps:F1} / mainPaint {mainPaintFps:F1} / subUpd {subUpdateFps:F1} subPaint {subPaintFps:F1} subDrop {subDropFps:F1} / " +
                    $"copy main {snap.MainCopyMs:F1}ms sub {snap.SubCopyMs:F1}ms / subPaint {snap.SubPaintMs:F1}ms / " +
                    $"gdi {gdiObjects} user {userObjects} handle {handleCount} / img {snap.ImgW}x{snap.ImgH} / " +
                    $"sub {(snap.SubShown ? "ON" : "OFF")}{eventSuffix} (F9: 副モニタ計測トグル)";
            }
        }

        // ---- ボタンイベント --------------------------------------------------

        private void btnStartStop_Click(object sender, EventArgs e) {
            if (_isRunning) {
                StopSampling();
            } else {
                StartSampling();
            }
        }

        private void btnReset_Click(object sender, EventArgs e) {
            StopSampling();
            _samples.Clear();
            _stopwatch.Reset();
            _lastTickElapsedSec = 0.0;
            PreviewProfiler.Snapshot();  // 未読の累積値を捨てて次回計測をゼロから開始する
            PreviewProfiler.ConsumeEvents();  // 未読のイベントも破棄する
            chart.Series["Private"].Points.Clear();
            chart.Series["MainFps"].Points.Clear();
            chart.Series["SubFps"].Points.Clear();
            lblCurrent.Text = "現在: --- (リセット)";
            lblPreview.Text = "プレビュー計測: ---";
        }

        private void btnSaveCsv_Click(object sender, EventArgs e) {
            if (_samples.Count == 0) {
                MessageBox.Show("記録データがありません。", "CSV 保存",
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            string path = NextCsvPath();
            var sb = new StringBuilder();
            sb.AppendLine(
                "elapsed_sec,private_MB,ws_MB,callback_fps,main_paint_fps,sub_update_fps,sub_paint_fps,sub_drop_fps," +
                "sub_paint_ms,main_copy_ms,sub_copy_ms,gdi_objects,user_objects,handle_count,img_w,img_h,sub_shown,event");
            foreach (var s in _samples) {
                sb.AppendLine(
                    $"{s.ElapsedSec:F3},{s.PrivateMB:F2},{s.WorkingSetMB:F2}," +
                    $"{s.CallbackFps:F2},{s.MainPaintFps:F2},{s.SubUpdateFps:F2},{s.SubPaintFps:F2},{s.SubDropFps:F2}," +
                    $"{s.SubPaintMs:F3},{s.MainCopyMs:F3},{s.SubCopyMs:F3}," +
                    $"{s.GdiObjects},{s.UserObjects},{s.HandleCount},{s.ImgW},{s.ImgH}," +
                    $"{(s.SubShown ? 1 : 0)},{s.Event}");
            }
            File.WriteAllText(path, sb.ToString(), new UTF8Encoding(false));

            MessageBox.Show($"保存しました:\n{path}", "CSV 保存",
                MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void btnOpenFolder_Click(object sender, EventArgs e) {
            Directory.CreateDirectory(_outputDir);
            Process.Start("explorer.exe", _outputDir);
        }

        // ---- ユーティリティ --------------------------------------------------

        private string NextCsvPath() {
            const string prefix = "mem_monitor_";
            int max = 0;
            if (Directory.Exists(_outputDir)) {
                foreach (var f in Directory.GetFiles(_outputDir, prefix + "*.csv")) {
                    string name = Path.GetFileNameWithoutExtension(f);
                    string numPart = name.Substring(prefix.Length);
                    if (int.TryParse(numPart, out int n) && n > max) max = n;
                }
            }
            return Path.Combine(_outputDir, $"{prefix}{max + 1:D4}.csv");
        }
    }
}
#endif
