using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Diagnostics;
using TIS.Imaging;
using System.Windows.Forms;

namespace ISCamRecorder {
    internal class ISCamera {
        readonly string ICCF_FILE = @"dfk33ux290.iccf";         // 設定ファイル
        readonly string CAMERA_FORMAT = "RGB32 (1920x1080)";    // 設定ファイル無い時の解像度
        readonly float FRAME_RATE = 40F;                        // 設定ファイル無い時のFPS
        readonly int JPEG_QUALITY = 90;                         // JPEG保存品質

        MainForm _MF = null;                          // メインフォームインスタンス
        ICImagingControl _Cam;                              // カメラオブジェクト
        string _SerialNumber;                               // シリアル番号
        string _CamID;                                      // カメラID
        VCDButtonProperty _Trigger;                         // ソフトウェアトリガー発信オブジェクト
        FrameRateCounter _Fps = new FrameRateCounter(10);   // フレームレート計測
        FrameQueueSink _PreviewSink;                        // プレビューSink
        FrameQueueSink _RecSink;                            // 録画Sink
        SinkListener _SinkListener = new SinkListener();    // Sinkリスナー
        IFrameQueueBuffer[] _bufferlist = null;             // 録画バッファ
        bool _Recoding = false;                             // 録画中フラグ
        
        public ManualResetEvent Ready { get; private set; } // 録画準備完了シグナル
        public float CameraFPS { get; private set; } = 0;   // カメラFPS
        public bool DeviceValid { get { return _Cam?.DeviceValid ?? false; } } // デバイス有効・無効


        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="cameraControl"></param>
        /// <param name="serialNumber"></param>
        public ISCamera(MainForm mf, ICImagingControl cameraControl, string serialNumber) {
            _MF = mf;
            _Cam = cameraControl;
            _SerialNumber = serialNumber;
            _CamID = $"Cam{_SerialNumber.Substring(_SerialNumber.Length - 2)}";
            _RecSink = new FrameQueueSink(_SinkListener, MediaSubtypes.RGB32);
            _PreviewSink = new FrameQueueSink(CaptureFrame, MediaSubtypes.RGB32, 5);
            Ready = new ManualResetEvent(false);
        }

        /// <summary>
        /// カメラ接続とプレビュー開始
        /// </summary>
        /// <returns></returns>
        public bool Connect() {
            if (_Cam.DeviceValid) return true;

            // 接続
            var dev = _Cam.Devices.FirstOrDefault(
                c => c.GetSerialNumber().Equals(_SerialNumber));
            if (dev == null) return false;
            _Cam.Device = dev;
            if (!_Cam.DeviceValid) return false;

            // 撮影条件設定
            try {
                if (File.Exists(ICCF_FILE)){
                    _Cam.LoadDeviceState(ICCFImport.Import(ICCF_FILE), false);
                    Common.DebugOut($"{_CamID} loaded config {ICCF_FILE}");
                } else {
                    //Common.DebugOut($"Can't find config {ICCF_FILE}");
                    _Cam.VideoFormat = _Cam.VideoFormats.FirstOrDefault(
                        c => c.Name.Equals(CAMERA_FORMAT));
                    _Cam.DeviceFrameRate = _Cam.DeviceFrameRates.FirstOrDefault(
                        c => (c > FRAME_RATE - 0.1F && c < FRAME_RATE + 0.1F));
                    Common.DebugOut($"{_CamID} sets {_Cam.VideoFormat}, {_Cam.DeviceFrameRate:0.0}fps");
                }
            } catch (ICException iex) {
                MessageBox.Show(iex.Message, $"{_CamID} Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return false;
            }
            CameraFPS = _Cam.DeviceFrameRate;
            _Cam.DeviceTrigger = false; // 起動時はトリガーOFF
            _Trigger = _Cam.VCDPropertyItems.Find<VCDButtonProperty>(
                VCDGUIDs.VCDID_TriggerMode, VCDGUIDs.VCDElement_SoftwareTrigger);

            // 表示設定
            _Cam.LiveDisplayDefault = false;
            SetDisplaySize();
            _Cam.Sink = _PreviewSink;

            _Cam.LiveStart();

            return true;
        }

        /// <summary>
        /// Sink変更
        /// </summary>
        /// <param name="isRec">true:録画 false:プレビュー</param>
        public void ChangeSink(bool isRec) {
            if (!_Cam.DeviceValid) return;
            _Cam.LiveStop();
            if (isRec) _Cam.Sink = _RecSink;
            else _Cam.Sink = _PreviewSink;
            _Cam.LiveStart();
        }

        /// <summary>
        /// 画像撮影
        /// </summary>
        public void SnapImage() {
            if (!_Cam.DeviceValid) return;
            TIS.Imaging.FrameSnapSink snapSink = new TIS.Imaging.FrameSnapSink();
            _Cam.LiveStop();
            _Cam.Sink = snapSink;
            _Cam.LiveStart();

            TIS.Imaging.IFrameQueueBuffer frm = snapSink.SnapSingle(TimeSpan.FromSeconds(5));
            var outDir = Path.Combine(_MF.Setting.SaveDir, "image");
            var filename = Path.Combine(outDir, $"{_CamID}_{DateTime.Now.ToString("yyyyMMdd_HHmmss")}.{_MF.Setting.ImageType}");
            switch (_MF.Setting.ImageType) {
            case "jpg":
                frm.SaveAsJpeg(filename, JPEG_QUALITY);
                break;
            case "bmp":
                frm.SaveAsBitmap(filename);
                break;
            case "tif":
                frm.SaveAsTiff(filename);
                break;
            }

            _Cam.LiveStop();
            _Cam.Sink = _PreviewSink;
            _Cam.LiveStart();
        }

        /// <summary>
        /// 録画
        /// </summary>
        public void RecordToMemory(float recodingLimit) {
            if (!_Cam.DeviceValid) return;

            // 準備
            _Recoding = false;
            ClearBuffer();
            var framesToCapture = (int)(recodingLimit * CameraFPS) + 1;
            Ready.Set();
            Common.DebugOut($"{_CamID} ready for recoding.");

            // 撮影待機
            while (!_Recoding) { Thread.Sleep(0); }
            Ready.Reset();

            // 撮影開始
            Common.DebugOut($"{_CamID} starts recoding.");
            _RecSink.AllocAndQueueBuffers(framesToCapture);
            while (_Recoding) {
                if (framesToCapture <= _RecSink.OutputQueueSize + 1) break;
            }
            // 画像バッファに変換
            _bufferlist = _RecSink.PopAllOutputQueueBuffers();
            _Recoding = false;
            Common.DebugOut($"{_CamID} ends recoding with {_bufferlist.Length} frames.");
        }

        // 録画開始
        public void StartRecoding() {
            _Recoding = true;
        }

        /// <summary>
        /// ファイル保存
        /// </summary>
        /// <param name="outDir"></param>
        public void SaveToFile() {
            if (!_Cam.DeviceValid) return;
            Common.DebugOut($"{_CamID} starts saving with {CameraFPS}fps.");
            // 保存先確保
            var outDir2 = Path.Combine(_MF.OutputDir, _CamID);
            Directory.CreateDirectory(outDir2);
            // 動画保存準備
            var movieFile = Path.Combine(_MF.OutputDir, $"{_CamID}_{_MF.RecodingTimeStr}.mp4");
            var writer = new H264Writer(movieFile, 
                _RecSink.OutputFrameType, (int)CameraFPS, _MF.Setting.MovieRate * 1000);
            writer.Begin();
            // ファイル保存
            var firstDriverTime = _bufferlist[0].FrameMetadata.DriverFrameFirstPacketTime;
            for (int i = 0; i < _bufferlist.Length; i++) {
                writer.Write(_bufferlist[i]);
                var driverTime = _bufferlist[i].FrameMetadata.DriverFrameFirstPacketTime;
                var frameTime = _MF.RecodingTime.Add(driverTime - firstDriverTime); // パケットをドライバが受信した時刻
                string strSampleTime = frameTime.ToString(@"HHmmss\.fff");
                var fileName = $"{_CamID}_{strSampleTime}.{_MF.Setting.FrameType}";
                var filePath = Path.Combine(outDir2, fileName);
                try {
                    switch (_MF.Setting.FrameType) {
                    case "jpg":
                        FrameExtensions.SaveAsJpeg(_bufferlist[i], filePath, JPEG_QUALITY);
                        break;
                    case "bmp":
                        FrameExtensions.SaveAsBitmap(_bufferlist[i], filePath);
                        break;
                    case "tif":
                        FrameExtensions.SaveAsTiff(_bufferlist[i], filePath);
                        break;
                    }
                } catch {

                }
            }
            // 終了処理
            writer.End();
            ClearBuffer();
            Common.DebugOut($"{_CamID} ends saving.");
        }

        /// <summary>
        /// 連続フレーム記録バッファの解放
        /// </summary>
        private void ClearBuffer() {
            if (_bufferlist != null) {
                _bufferlist = null;
                GC.Collect();
            }
        }

        /// <summary>
        /// 録画中断
        /// </summary>
        public void StopRecoding() {
            _Recoding = false;
        }

        /// <summary>
        /// 表示サイズ設定
        /// </summary>
        public void SetDisplaySize() {
            if (!_Cam.DeviceValid) return;

            var vf = _Cam.VideoFormatCurrent;
            if (vf.Height * _Cam.Width < vf.Width * _Cam.Height) {
                _Cam.LiveDisplayLeft = 0;
                _Cam.LiveDisplayWidth = _Cam.Width;
                var h = vf.Height * _Cam.Width / vf.Width;
                _Cam.LiveDisplayTop = (_Cam.Height - h) / 2;
                _Cam.LiveDisplayHeight = h;
            } else {
                _Cam.LiveDisplayTop = 0;
                _Cam.LiveDisplayHeight = _Cam.Height;
                var w = vf.Width * _Cam.Height / vf.Height;
                _Cam.LiveDisplayLeft = (_Cam.Width - w) / 2;
                _Cam.LiveDisplayWidth = w;
            }
        }

        /// <summary>
        /// フレーム取得時
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        private FrameQueuedResult CaptureFrame(IFrameQueueBuffer buffer) {
            _Fps.Shot();
            return FrameQueuedResult.ReQueue;
        }

        /// <summary>
        /// トリガーモード変更
        /// </summary>
        /// <param name="enable"></param>
        public void SetTriggerMode(bool enable) {
            if (!_Cam.DeviceValid) return;
            _Cam.LiveStop();
            _Cam.DeviceTrigger = enable;
            _Cam.LiveStart();
        }

        /// <summary>
        /// ソフトウェアトリガー発信
        /// </summary>
        public void SWTrigger() {
            if (_Cam.DeviceValid && _Trigger != null) _Trigger.Push();
        }

        /// <summary>
        /// プロパティ設定（ダイアログ）
        /// </summary>
        /// <returns></returns>
        public string SetProperty() {
            if (!_Cam.DeviceValid) return "";
            _Cam.ShowPropertyDialog();
            return _Cam.SaveDeviceState();
        }

        /// <summary>
        /// プロパティ設定（条件指定）
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        public string SetProperty(string config) {
            if (!_Cam.DeviceValid) return "";
            _Cam.LiveStop();
            _Cam.LoadDeviceState(config, false);
            _Cam.LiveStart();
            return config;
        }

        /// <summary>
        /// カメラ情報文字列
        /// </summary>
        /// <returns></returns>
        public string CameraInfo() {
            return $"({_SerialNumber}) {_Fps.FrameRate:0.0} fps";
        }

        /// <summary>
        /// １秒録画に要するメモリ量
        /// </summary>
        /// <returns></returns>
        public float ConsumedMemoryPerSecond() {
            if (!_Cam.DeviceValid) return 0;
            var vf = _Cam.VideoFormatCurrent;
            int frameSize = vf.BitsPerPixel / 8 * vf.Width * vf.Height;
            int bcs = 1024 * 1024;
            return _Fps.FrameRate * frameSize / bcs;
        }
    }
}
