using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System.Drawing;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;

namespace ISCamRecorder {

    /// <summary>
    /// 撮影フレーム情報クラス
    /// 撮影画像と撮影時間をペアで記録
    /// </summary>
    struct FrameInfo {
        public DateTime FrameTime { get; private set; }
        public Mat FrameImage { get; private set; }

        public FrameInfo(Mat frame) {
            FrameImage = frame;
            FrameTime = DateTime.Now;
        }
    }

    /// <summary>
    /// 汎用カメラクラス
    /// OpenCVカメラで撮影する
    /// </summary>
    class CvCamera {
        readonly string _CamID = "CvCam";                   // 記録用デバイス名
        VideoCapture camera = null;                         // カメラオブジェクト
        FrameRateCounter _Fps = new FrameRateCounter(10);   // フレームレート計測
        List<FrameInfo> _RecFrames = new List<FrameInfo>(); // 録画画像
        MainForm _MF = null;                          // メインフォームインスタンス
        bool _Snap = false;                                 // 静止画撮影フラグ

        public float CameraFPS { get; private set; } = 0;
        public float CurrentFPS { get { return _Fps.FrameRate; } }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="mf">メインフォーム</param>
        public CvCamera(MainForm mf) {
            _MF = mf;
        }

        /// <summary>
        /// カメラ接続
        /// </summary>
        /// <returns></returns>
        public bool Open(int id, int frameWidth, int frameHeight) {
            camera = new VideoCapture(id);
            if (!camera.IsOpened()) {
                camera.Dispose();
                camera = null;
                MessageBox.Show("追加カメラに接続できません", "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return false;
            }
            camera.FrameWidth = frameWidth;
            camera.FrameHeight = frameHeight;
            CameraFPS = (float)camera.Fps;
            return true;
        }

        /// <summary>
        /// 撮影ループ
        /// </summary>
        public void CameraLoop() {
            var rblabel = Cv2.ImRead("RBlabel398x133.png");
            var roi = new Rect(10, 10, rblabel.Width, rblabel.Height);

            // 撮影ループ
            while (_MF.State != STATE.Exit) {
                using (var frame = new Mat()) {
                    // 撮影
                    camera.Read(frame);
                    if (_MF.IsReverseBlood) {
                        rblabel.CopyTo(new Mat(frame, roi));
                    }
                    var bmp = BitmapConverter.ToBitmap(frame);

                    // 録画
                    if (_MF.State == STATE.Recoding && _MF.Setting.RecCameras[4]) {
                        _RecFrames.Add(new FrameInfo(frame.Clone()));
                    }

                    // 静止画保存
                    if (_Snap) {
                        var outDir = Path.Combine(_MF.Setting.SaveDir, "image");
                        var filename = Path.Combine(outDir, 
                            $"{_CamID}_{DateTime.Now.ToString("yyyyMMdd_HHmmss")}.{_MF.Setting.ImageType.ToLower()}");
                        Cv2.ImWrite(filename, frame);
                        _Snap = false;
                    }

                    // 表示
                    if (_MF.State != STATE.Exit) {
                        _MF.CvCameraPic.Invoke((MethodInvoker)delegate {
                            _MF.CvCameraPic.Image = bmp;
                        });
                    }

                    _Fps.Shot();
                }
            }

            // 終了処理（メモリ解放）
            if (camera != null) camera.Dispose();
        }

        /// <summary>
        /// 記録データをファイルに保存
        /// </summary>
        /// <param name="outDir"></param>
        /// <param name="imageType"></param>
        /// <param name="recTime"></param>
        public void SaveToFile() {
            if (_RecFrames.Count < 1) return;
            Common.DebugOut($"{_CamID} starts saving with {CameraFPS:0.0}fps.");
            // 保存先確保
            var outDir2 = Path.Combine(_MF.OutputDir, _CamID);
            Directory.CreateDirectory(outDir2);
            // 動画保存準備
            var movieFile = Path.Combine(_MF.OutputDir, $"{_CamID}_{_MF.RecodingTimeStr}.mp4");
            var writer = new VideoWriter(movieFile, FourCC.H264,
                CameraFPS, _RecFrames[0].FrameImage.Size());
            for (int i = 0; i < _RecFrames.Count; i++) {
                string strSampleTime = _RecFrames[i].FrameTime.ToString(@"HHmmss\.fff");
                var fileName = $"{_CamID}_{strSampleTime}.{_MF.Setting.FrameType}";
                var filePath = Path.Combine(outDir2, fileName);
                _RecFrames[i].FrameImage.SaveImage(filePath);
                writer.Write(_RecFrames[i].FrameImage);
            }
            writer.Release();

            _RecFrames.Clear();
            Common.DebugOut($"{_CamID} ends saving.");
        }

        /// <summary>
        /// カメラ情報文字列
        /// </summary>
        /// <returns></returns>
        public string CameraInfo() {
            if (camera == null) return "接続中";
            try {
                return $"{camera.FrameWidth}x{camera.FrameHeight} {_Fps.FrameRate:0.0} fps";
            } catch (Exception _) {
                return "";
            }
        }

        /// <summary>
        /// １秒録画に要するメモリ量
        /// </summary>
        /// <returns></returns>
        public float ConsumedMemoryPerSecond() {
            if (camera == null) return 0;
            int frameSize = 3 * camera.FrameWidth * camera.FrameHeight;
            int bcs = 1024 * 1024;
            return CameraFPS * frameSize / bcs;
        }

        /// <summary>
        /// 静止画撮影
        /// </summary>
        public void Snap() {
            _Snap = true;
        }
    }
}
