﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Interop;
using System.Windows.Media.Media3D;
using System.Xml.Serialization;
using TIS.Imaging;

namespace TIASshot {
    public partial class Form1 : Form {

        private CameraBase _camera;
        public static string APP_NAME = "TIAS Shot";
        private LightSource _light = new LightSource();
        private bool _isLightOn = true;
        private PreviewMonitor _previewMonitor;
        private Bitmap _dispBuf = null;
        private bool _isUpdateBuf = false;
        private readonly object _dispBufLock = new object();

        // 本モニタ（picDisplay）の自ペース再描画用タイマー。
        // 以前はコールバックスレッドから毎フレーム Invalidate() していたが，Form1 の WM_PAINT が
        // 常時保留状態になり，同一 UI スレッドを共有する副モニタの WM_TIMER が
        // （WM_PAINT > WM_TIMER の優先度により）飢餓状態になっていた。
        // 本モニタも副モニタと同じタイマー駆動に統一し，コールバックスレッドからは
        // Invalidate を一切呼ばないことで WM_PAINT の常時保留（洪水源）を消す。
        private const int MainRefreshIntervalMs = 33; // ≒30fps（PreviewMonitor と同じ値）
        private readonly System.Windows.Forms.Timer _mainRefreshTimer = new System.Windows.Forms.Timer();


        /// <summary>
        /// コンストラクタ
        /// </summary>
        public Form1() {
            InitializeComponent();
#if WHITEBOARD
            InitializeWhiteBoardButton();
#endif
#if MEMMONITOR
            this.Shown += (s, e) => ShowMemoryMonitor();
            this.KeyPreview = true;
            this.KeyDown += Form1_MemMonitor_KeyDown;
#endif

            var version = Assembly.GetExecutingAssembly().GetName().Version;
            Text = $"{APP_NAME} ver.{version.Major}.{version.Minor}";
#if DEBUG
            Text += " [DEBUG]";
#endif
        }

        /// <summary>
        /// フォームロード
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e) {
            Config.Load();

            _camera = new Lucam(this, picPreview);
            if (!_camera.Connect()) {
                Debug.WriteLine(_camera.ErrorMsg);
                Debug.WriteLine("Lucamの接続に失敗しました");
                _camera = null;
            }

            if (_camera == null) {
                var cameraList = icImagingControl1.Devices.Select(d => d.Name.Replace(" ", ""));
                foreach (var cam in cameraList) {
                    _camera = new IScam(this, icImagingControl1, cam);
                    if (_camera.Connect()) break;
                    Debug.WriteLine(_camera.ErrorMsg);
                    Debug.WriteLine($"{cam}の接続に失敗しました");
                    _camera = null;
                }
            }

            if (_camera == null) {
                MessageBox.Show("カメラが見つかりません．終了します．", APP_NAME,
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                Close();
                return;
            }

            txtDeviceName.Text = _camera.DeviceName;
            txtSerialNo.Text = _camera.SerialNumber;
            txtNumMultiShots.Text = Config.GetString("Shot/MultiShotCount");
            txtMultiShotsInterval.Text = Config.GetString("Shot/MultiShotInterval");
            txtDataName.Text = Config.GetString("Shot/SubjectName");
            txtSaveFolder.Text = Config.GetString("File/SaveFolder");
            txtConvElemNum.Text = Config.GetString("Calib/ConversionChannels");
            EnableShots(false);
            //_lucam.StartStopPreview();

            if (_light.Open()) {
                _light.SetLight();
            } else {
                Debug.WriteLine("光源の接続に失敗しました");
                btnLightSW.Enabled = false;
            }

            _previewMonitor = new PreviewMonitor();
            if (_previewMonitor.IsShown) _previewMonitor.Show();

            _mainRefreshTimer.Interval = MainRefreshIntervalMs;
            _mainRefreshTimer.Tick += MainRefreshTimer_Tick;
            _mainRefreshTimer.Start();
        }

        /// <summary>
        /// フォームクローズ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
            _mainRefreshTimer.Stop();
            if (_camera == null) return;
            _camera.Disconnect();
        }

        /// <summary>
        /// 画像撮影1枚
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnShot1_Click(object sender, EventArgs e) {
            EnableShots(false);
            _camera.ShotOne();
        }

        /// <summary>
        /// 複数画像撮影
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnShotMulti_Click(object sender, EventArgs e) {
            EnableShots(false);
            _camera.ShotMulti();
        }

        /// <summary>
        /// 校正
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCalib_Click(object sender, EventArgs e) {
            //_lucam.Calibration();
        }

        /// <summary>
        /// 画像表示
        /// ワーカースレッドから呼び出される
        /// </summary>
        /// <param name="bmp"></param>
        public void ShowImage(Bitmap bmp) {
#if MEMMONITOR
            PreviewProfiler.IncrementCallbackFrames();
            PreviewProfiler.SetLastImageSize(bmp.Width, bmp.Height);
            long mainCopyStart = System.Diagnostics.Stopwatch.GetTimestamp();
#endif
            // Lucam 経路は二重バッファ（_bmps[0]/_bmps[1]）をコールバックスレッド上で Dispose するため，
            // 入力 bmp をそのまま picDisplay.Image に載せると，UIスレッドの OnPaint が Width 等を
            // 読んでいる最中にコールバックスレッドが Dispose して別スレッド同時アクセスでクラッシュしうる。
            // そのため ShowImage は入力 bmp を「読むだけ」とし，表示用には呼び出しスレッド上で
            // Form1 所有のコピーを生成する（bmp の所有権は呼び出し側に残す）。
            var dispCopy = new Bitmap(bmp);
#if MEMMONITOR
            PreviewProfiler.AddMainCopyTicks(System.Diagnostics.Stopwatch.GetTimestamp() - mainCopyStart);
#endif

            // 原因2: タイマー Tick に反映されなかった取りこぼしフレームを Dispose する
            // _dispBufLock で ShowImage（コールバックスレッド）と MainRefreshTimer_Tick（UIスレッド）の
            // 同時アクセスを防ぎ、二重 Dispose・使用中バッファの誤 Dispose を回避する
            Bitmap toDispose = null;
            lock (_dispBufLock) {
                if (_isUpdateBuf && _dispBuf != null) {
                    toDispose = _dispBuf;   // タイマー未反映の旧バッファを破棄予約
                }
                _dispBuf = dispCopy;
                _isUpdateBuf = true;
            }
            toDispose?.Dispose();   // ロック外で GDI 解放（ロック保持時間を最小化）
            // Invalidate() は呼ばない。毎フレーム呼ぶと Form1 の WM_PAINT が常時保留状態になり，
            // 同一 UI スレッドを共有する副モニタの WM_TIMER（WM_PAINT より優先度が低い）が
            // 飢餓状態になるため（本モニタも MainRefreshTimer_Tick による自ペース再描画に統一した）。

            // 原因1: 副モニタが表示されているときだけフルコピーを生成する
            // （非表示時に毎フレーム bmp2 を生成しリークしていた問題を修正）
            bool feedSub = _previewMonitor != null && _previewMonitor.IsShown;
#if MEMMONITOR
            // F9 トグル（ToggleSubMonitorProfiling）による計測用の一時抑制。
            // 抑制中は副モニタ用コピー生成も UpdateImage 呼び出しも走らせず，
            // コールバックスレッドのコピー回数を本モニタ用の 1 回だけに戻す。
            if (_subMonitorSuppressed) feedSub = false;
#endif
            if (feedSub) {
#if MEMMONITOR
                PreviewProfiler.IsSubShown = true;
                long subCopyStart = System.Diagnostics.Stopwatch.GetTimestamp();
                var subCopy = new Bitmap(bmp);
                PreviewProfiler.AddSubCopyTicks(System.Diagnostics.Stopwatch.GetTimestamp() - subCopyStart);
                _previewMonitor.UpdateImage(subCopy);
#else
                _previewMonitor.UpdateImage(new Bitmap(bmp));
#endif
            }
#if MEMMONITOR
            else {
                PreviewProfiler.IsSubShown = false;
            }
#endif
        }

        /// <summary>
        /// 本モニタ（picDisplay）の自ペース再描画（約30fps）。
        /// 新フレームがあるときだけ picDisplay.Image を差し替える（静止フレームは無駄に再描画しない）。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainRefreshTimer_Tick(object sender, EventArgs e) {
            // 原因2: _dispBufLock で ShowImage との競合を防ぎながらバッファを取り出す
            // _dispBuf を null に戻すことで ShowImage 側が「表示中バッファ」を誤って Dispose しないようにする
            Bitmap toDisplay;
            lock (_dispBufLock) {
                if (!_isUpdateBuf || _dispBuf == null) return;
                toDisplay = _dispBuf;
                _dispBuf = null;        // 参照を手放し、次の ShowImage で二重 Dispose されないようにする
                _isUpdateBuf = false;
            }

            // picDisplay.Image の Dispose・差し替えは UI スレッド（このタイマー Tick）でのみ行う。
            picDisplay.Image?.Dispose();    // 直前に表示していたバッファを解放
            picDisplay.Image = toDisplay;
#if MEMMONITOR
            PreviewProfiler.IncrementMainPaints();
#endif
        }

        /// <summary>
        /// メッセージ表示
        /// </summary>
        /// <param name="msg"></param>
        public void ShowMessage(string msg) {
            if (InvokeRequired) {
                Invoke((MethodInvoker)delegate { ShowMessage(msg); });
                return;
            }
            txtMessage.Text = msg;
            txtMessage.Update();
        }

        /// <summary>
        /// データ名取得
        /// </summary>
        /// <returns></returns>
        public string GetDataName() {
            if (InvokeRequired) {
                return (string)Invoke((MethodInvoker)delegate { GetDataName(); });
            }
            return txtDataName.Text;
        }

        /// <summary>
        /// マルチショット撮影の枚数取得
        /// </summary>
        /// <returns></returns>
        public int GetNumMultiShots() {
            if (InvokeRequired) {
                return (int)Invoke((MethodInvoker)delegate { GetNumMultiShots(); });
            }
            if (int.TryParse(txtNumMultiShots.Text, out int result)) return result;
            return 1;
        }

        /// <summary>
        /// マルチショット撮影の間隔取得
        /// </summary>
        /// <returns></returns>
        public int GetMultiShotsInterval() {
            if (InvokeRequired) {
                return (int)Invoke((MethodInvoker)delegate { GetMultiShotsInterval(); });
            }
            if (int.TryParse(txtMultiShotsInterval.Text, out int result)) return result;
            return 1000;
        }

        /// <summary>
        /// 撮影ボタン有効化
        /// </summary>
        /// <param name="enable"></param>
        public void EnableShots(bool enable = true) {
            if (InvokeRequired) {
                Invoke((MethodInvoker)delegate { EnableShots(enable); });
                return;
            }
            btnShotOne.Enabled = enable;
            btnShotMulti.Enabled = enable;
        }

        /// <summary>
        /// データフォルダを開く
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOpenDataFolder_Click(object sender, EventArgs e) {
            System.Diagnostics.Process.Start(txtSaveFolder.Text);
        }

        /// <summary>
        /// 光源のON/OFF切り替え
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnLightSW_Click(object sender, EventArgs e) {
            if (!_light.IsOpen || _camera.IsCalibrating) return;
            if (_isLightOn) {
                _light.TurnOff();
                btnLightSW.Text = "照明 ON";
                _isLightOn = false;
            } else {
                _light.SetLight();
                btnLightSW.Text = "照明 OFF";
                _isLightOn = true;
            }
        }
    }
}
