namespace XHWK.WKTool.system
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using AForge.Video.DirectShow;
    using Common.system;
    using VisioForge.Shared.NAudio.CoreAudioApi;
    using VisioForge.Shared.NAudio.Wave;

    /// <summary>
    /// ffmpeg帮助类 创建人：赵耀 创建时间：：2020年8月22日 需要安装\SSCR\Setup Screen Capturer Recorder v0.12.10.exe
    /// 本地调试需要配置环境变量 将ffmpeg.exe位置配置到环境变量的path中
    /// </summary>
    // ReSharper disable once InconsistentNaming
    public class FFMpeg
    {
        #region 变量

        public Process myProcess;

        /// <summary>
        /// 是否输出录课录屏日志
        /// </summary>
        public bool OutputVideoLog = FileToolsCommon.GetConfigValue("OutputVideoLog") != "0";

        /// <summary>
        /// ffmpeg输出日志文件地址 每个文件2MB左右
        /// </summary>
        private string _logPath = "";

        /// <summary>
        /// 是否可以录制扬声器
        /// </summary>
        private bool _isRecordSpeaker = true;

        /// <summary>
        /// 是否可以录制麦克风
        /// </summary>
        private bool _isRecordMicrophone = true;

        private readonly string _ffmpegPath = FileToolsCommon.GetFileAbsolutePath(@"/ffmpeg.exe");

        #endregion 变量

        /// <summary>
        /// 录制屏幕
        /// </summary>
        /// <param name="filePath">文件存储路径</param>
        /// <param name="size">大小</param>
        /// <param name="recordingSound">录制音频</param>
        /// <param name="recordingMicrophone">录制麦克风</param>
        /// <param name="errMessage">错误信息</param>
        /// <param name="microphoneName">麦克风名</param>
        /// <returns></returns>
        public bool StartRecordingVideo
        (
            string filePath,
            Size size,
            bool recordingSound,
            bool recordingMicrophone,
            out string errMessage,
            string microphoneName = null
        )
        {
            while (myProcess != null)
            {
                Thread.Sleep(100);
            }
            errMessage = null;
            //路径
            FileToolsCommon.GetDirectoryName(filePath);
            string extension = FileToolsCommon.GetIoExtension(filePath); //扩展名
            //文件保存路径
            string pathName = GetRsFilePathName(filePath);
            Process[] killProcessArray = Process.GetProcessesByName("ffmpeg");
            foreach (Process killProcess in killProcessArray)
            {
                killProcess.Kill();
            }
            myProcess = new Process();
            _logPath = CreateffmpegLog();
            if (string.IsNullOrWhiteSpace(microphoneName))
            {
                microphoneName = GetMicrophoneName();
            }

            #region 检测麦克风扬声器

            string audioPath = FileToolsCommon.GetFileAbsolutePath("/temp/audio/");
            FileToolsCommon.CreateDirectory(audioPath);
            string audioSpeakerPath = audioPath + "adoS" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".m"; //FileToolsCommon.GetFileAbsolutePath("adoS.m");
            string audioMicrophonePath = audioPath + "adoM" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".m"; //FileToolsCommon.GetFileAbsolutePath("adoM.m");
            try
            {
                FileToolsCommon.DeleteFile(audioSpeakerPath);
                FileToolsCommon.DeleteFile(audioMicrophonePath);
            }
            catch (Exception)
            {
                // ignored
            }
            //是否录制音频
            if (recordingSound)
            {
                if (StartRecordSpeakerAudio(audioSpeakerPath))
                {
                    _isRecordSpeaker = true;
                }
                else
                {
                    //无法录制扬声器音频
                    _isRecordSpeaker = false;
                }
                StopRecordAudio(2);
                Thread.Sleep(100);
            }
            //是否录制麦克风
            if (recordingMicrophone)
            {
                if (StartRecordAudio(audioMicrophonePath))
                {
                    _isRecordMicrophone = true;
                }
                else
                {
                    //无法录制麦克风
                    _isRecordMicrophone = false;
                }
                StopRecordAudio(1);
            }

            #endregion 检测麦克风扬声器

            myProcess.StartInfo.FileName = _ffmpegPath; //ffmpeg.exe的绝对路径
            string speakerStr = "";
            string microphoneStr = "";
            if (_isRecordSpeaker && recordingSound)
            {
                speakerStr = "-f dshow -i audio=\"virtual-audio-capturer\" ";
            }
            if (_isRecordMicrophone && recordingMicrophone)
            {
                if (!string.IsNullOrWhiteSpace(microphoneName))
                {
                    microphoneStr = "-f dshow -i audio=\"" + microphoneName + "\" -filter_complex amix=inputs=2:duration=first:dropout_transition=2 ";
                }
            }
            switch (extension.ToUpper())
            {
                case ".MP4":
                    myProcess.StartInfo.Arguments = speakerStr + microphoneStr + " -f gdigrab -framerate 6 -offset_x 0 -offset_y 0 -video_size " + size.Width + "x" + size.Height + " -i desktop -pix_fmt yuv420p -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -acodec aac " + pathName;
                    break;
                case ".AVI":
                case ".FLV":
                    myProcess.StartInfo.Arguments = speakerStr + microphoneStr + " -f gdigrab -framerate 6 -offset_x 0 -offset_y 0 -video_size " + size.Width + "x" + size.Height + " -i desktop -pix_fmt yuv420p -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -acodec aac -f " + extension.ToLower().Replace(".", "") + " " + pathName;
                    break;
                default:
                    myProcess.StartInfo.Arguments = speakerStr + microphoneStr + " -f gdigrab -framerate 6 -offset_x 0 -offset_y 0 -video_size " + size.Width + "x" + size.Height + " -i desktop -pix_fmt yuv420p -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -acodec aac " + pathName;
                    break;
            }
            if (OutputVideoLog)
            {
                LogHelper.WriteInfoLog("【录屏】：" + myProcess.StartInfo.Arguments);
            }
            FileToolsCommon.AppendText(_logPath, "【录屏】：" + myProcess.StartInfo.Arguments + "\r\n");
            myProcess.StartInfo.UseShellExecute = false; //不使用操作系统外壳程序启动
            myProcess.StartInfo.RedirectStandardError = true; //重定向标准错误输出
            myProcess.StartInfo.CreateNoWindow = true; //不显示程序窗口
            myProcess.StartInfo.RedirectStandardInput = true; //用于模拟该进程控制台的输入
            myProcess.ErrorDataReceived += Output;
            try
            {
                myProcess.Start();
                myProcess.BeginErrorReadLine();
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("访问"))
                {
                    errMessage = "访问被拒绝，请关闭杀毒软件或新任此软件后重试！";
                }
                else
                {
                    errMessage = ex.Message + " 请关闭杀毒软件或信任此软件后重试！";
                }
                LogHelper.WriteErrLog("【录音】（StartRecordingAudio）" + errMessage, ex);
                myProcess.Dispose();
                myProcess = null;
                return false;
            }
            return true;
        }

        /// <summary>
        /// 视频剪辑
        /// </summary>
        /// <param name="videoFilePath">视频路径</param>
        /// <param name="startTime">开始时间 格式 HH:mm:ss</param>
        /// <param name="endTime">结束时间 格式 HH:mm:ss</param>
        /// <param name="saveFilePath">保存文件路径</param>
        public void ClipVideo
        (
            string videoFilePath,
            string startTime,
            string endTime,
            string saveFilePath
        )
        {
            while (myProcess != null)
            {
                Thread.Sleep(100);
            }
            Process[] killProcessArray = Process.GetProcessesByName("ffmpeg");
            foreach (var killProcess in killProcessArray)
            {
                killProcess.Kill();
            }
            myProcess = new Process();
            _logPath = CreateffmpegLog();
            this.myProcess.StartInfo.FileName = _ffmpegPath; //ffmpeg.exe的绝对路径
            //ffmpeg -ss 开始时间 -to 结束时间 -accurate_seek -i mp4输入路径 -c:v libx264 -c:a aac -strict experimental -vf scale = 宽:高 -b 500k 输出路径.mp4
            myProcess.StartInfo.Arguments = "-i " + videoFilePath + " -vcodec copy -acodec copy -ss " + startTime + " -to " + endTime + " " + saveFilePath; //-filter:a "volume=0.5"
            myProcess.StartInfo.UseShellExecute = false; //不使用操作系统外壳程序启动
            myProcess.StartInfo.RedirectStandardError = true; //重定向标准错误输出
            myProcess.StartInfo.CreateNoWindow = true; //不显示程序窗口
            myProcess.StartInfo.RedirectStandardInput = true; //用于模拟该进程控制台的输入
            //外部程序(这里是FFMPEG)输出流时候产生的事件,这里是把流的处理过程转移到下面的方法中,详细请查阅MSDN
            myProcess.ErrorDataReceived += Output;
            myProcess.Start(); //启动线程
            myProcess.BeginErrorReadLine(); //开始异步读取
            myProcess.WaitForExit(); //阻塞等待进程结束
            myProcess.Close(); //关闭进程
            myProcess.Dispose(); //释放资源

            #region 进程是否已经释放

            bool isRunning = true;
            while (isRunning)
            {
                isRunning = false;
                Process[] processArray = Process.GetProcessesByName("ffmpeg");
                if (processArray.Length > 0)
                {
                    isRunning = true;
                    Thread.Sleep(100);
                }
            }

            #endregion 进程是否已经释放

            myProcess = null;
        }

        /// <summary>
        /// 获取文件完整路径 录屏
        /// </summary>
        /// <param name="filePath">文件路径</param>
        /// <returns></returns>
        private string GetRsFilePathName(string filePath)
        {
            //路径
            string path = FileToolsCommon.GetDirectoryName(filePath);
            //Path.GetFileName(FilePath);//获取文件名
            string extension = FileToolsCommon.GetIoExtension(filePath); //扩展名
            string tempFilePath = path + "_temppath/";
            //创建临时目录
            FileToolsCommon.CreateDirectory(tempFilePath);
            //创建记录文件
            if (!FileToolsCommon.IsExistFile(tempFilePath + "filelist.d"))
            {
                FileToolsCommon.CreateFile(tempFilePath + "filelist.d");
            }
            int num = 1;
            string completeFilePath = tempFilePath + num + extension;
            while (FileToolsCommon.IsExistFile(completeFilePath))
            {
                num++;
                completeFilePath = tempFilePath + num + extension;
            }
            FileToolsCommon.AppendText(tempFilePath + "filelist.d", "file ./" + num + extension + "\r\n");
            return completeFilePath;
        }

        /// <summary>
        /// 创建日志文件
        /// </summary>
        /// <returns></returns>
        private string CreateffmpegLog()
        {
            string logFileName = DateTime.Now.ToString("yyyyMMdd");
            string logFilePath = FileToolsCommon.GetFileAbsolutePath("/Log/FFMpegLog/");
            FileToolsCommon.CreateDirectory(logFilePath);
            string logName = logFilePath + logFileName + ".log";
            try
            {
                if (!FileToolsCommon.IsExistFile(logName))
                {
                    FileToolsCommon.CreateFile(logName);
                    FileToolsCommon.AppendText(logName, "\r\n****************************************************************************************************" + "****************************************************************************************************\r\n");
                    return logName;
                }
                else
                {
                    int num = 0;
                    while (FileToolsCommon.GetFileSizeByMb(logName) > 2)
                    {
                        num++;
                        logName = logFilePath + logFileName + "_" + num + ".log";
                        FileToolsCommon.CreateFile(logName);
                    }
                    FileToolsCommon.AppendText(logName, "\r\n****************************************************************************************************" + "****************************************************************************************************\r\n");
                    return logName;
                }
            }
            catch (Exception)
            {
                return logName;
            }
        }

        /// <summary>
        /// 输出结果
        /// </summary>
        /// <param name="sendProcess"></param>
        /// <param name="output"></param>
        private void Output(object sendProcess, DataReceivedEventArgs output)
        {
            if (!string.IsNullOrEmpty(output.Data))
            {
                FileToolsCommon.AppendText(_logPath, "【" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff") + "】:");
                FileToolsCommon.AppendText(_logPath, output.Data + "\r\n");
            }
        }

        #region 麦克风声卡检测

        #region 获取麦克风

        /// <summary>
        /// 获取麦克风名称
        /// </summary>
        /// <returns></returns>
        public string GetMicrophoneName()
        {
            return GetAudioInDevices();
        }

        /// <summary>
        /// AForge获取麦克风
        /// </summary>
        /// <returns></returns>
        public static string GetAudioInDevices()
        {
            List<string> devicesList = new List<string>();
            try
            {
                // ReSharper disable once CollectionNeverUpdated.Local
                FilterInfoCollection videoDevices = new FilterInfoCollection(FilterCategory.AudioInputDevice);
                foreach (FilterInfo device in videoDevices)
                {
                    devicesList.Add(device.Name);
                }
            }
            catch (ApplicationException)
            {
                //Console.WriteLine("No local capture devices");
            }
            if (devicesList.Count > 1)
            {
                return devicesList[1];
            }
            return "";
        }

        /// <summary>
        /// AForge获取麦克风列表
        /// </summary>
        /// <returns></returns>
        public static List<string> GetAudioInDevicesList()
        {
            List<string> devicesList = new List<string>();
            try
            {
                // ReSharper disable once CollectionNeverUpdated.Local
                FilterInfoCollection videoDevices = new FilterInfoCollection(FilterCategory.AudioInputDevice);
                foreach (FilterInfo device in videoDevices)
                {
                    if (device.Name == "virtual-audio-capturer")
                    {
                        continue;
                    }
                    devicesList.Add(device.Name);
                }
            }
            catch (ApplicationException)
            {
                //Console.WriteLine("No local capture devices");
            }
            return devicesList;
        }

        /// <summary>
        /// 获取声音输入设备名称 不完整
        /// </summary>
        /// <returns></returns>
        public static string GetInDeviceName()
        {
            List<WaveInCapabilities> devices = new List<WaveInCapabilities>();
            int waveInDevices = WaveIn.DeviceCount;
            for (int i = 0; i < waveInDevices; i++)
            {
                devices.Add(WaveIn.GetCapabilities(i));
            }
            var devs = devices.Select(item => item.ProductName).ToList();
            if (devs.Count > 0)
            {
                byte[] buffer = Encoding.UTF8.GetBytes(devs[0]);
                string microphoneName = Encoding.UTF8.GetString(buffer); // 统一使用UTF-8
                return microphoneName;
            }
            else
            {
                return "";
            }
        }

        /// <summary>
        /// 获取声音输入设备完整名称
        /// </summary>
        /// <returns></returns>
        public static string GetInDeviceFull()
        {
            List<string> devices = new List<string>();
            MMDeviceEnumerator enumberator = new MMDeviceEnumerator();
            MMDeviceCollection deviceCollection = enumberator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.All);
            for (int waveInDevice = 0; waveInDevice < WaveIn.DeviceCount; waveInDevice++)
            {
                WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice);
                foreach (MMDevice device in deviceCollection)
                {
                    try
                    {
                        if (device.FriendlyName.StartsWith(deviceInfo.ProductName))
                        {
                            devices.Add(device.FriendlyName);
                            break;
                        }
                    }
                    catch (Exception)
                    {
                        // ignored
                    }
                }
            }
            if (devices.Count > 0)
            {
                byte[] buffer = Encoding.UTF8.GetBytes(devices[0]);
                string microphoneName = Encoding.UTF8.GetString(buffer); // 统一使用UTF-8
                return microphoneName;
            }
            else
            {
                return "";
            }
        }

        /// <summary>
        /// 获取声音输入设备完整名称列表
        /// </summary>
        /// <returns></returns>
        public List<string> GetInDeviceListFull()
        {
            List<string> devices = new List<string>();
            MMDeviceEnumerator enumberator = new MMDeviceEnumerator();
            MMDeviceCollection deviceCollection = enumberator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.All);
            for (int waveInDevice = 0; waveInDevice < WaveIn.DeviceCount; waveInDevice++)
            {
                WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice);
                foreach (MMDevice device in deviceCollection)
                {
                    try
                    {
                        if (device.FriendlyName.StartsWith(deviceInfo.ProductName))
                        {
                            devices.Add(device.FriendlyName);
                            break;
                        }
                    }
                    catch (Exception)
                    {
                        // ignored
                    }
                }
            }
            return devices;
        }

        #endregion 获取麦克风

        #region 录音

        /// <summary>
        /// 开始录制麦克风
        /// </summary>
        public bool StartAudio(string audioFile, string deviceName = "")
        {
            try
            {
                int deviceIndex = 0;
                _waveIn = new WaveInEvent();
                List<string> inDeviceList = GetInDeviceListFull();
                if (inDeviceList.Exists(x => deviceName.Contains(x)))
                {
                    deviceIndex = inDeviceList.FindIndex(x => deviceName.Contains(x));
                }
                _waveIn.DeviceNumber = deviceIndex;
                //waveIn.DeviceNumber = 1;
                //生成音频文件的对象
                WaveFileWriter writer = new WaveFileWriter(audioFile, _waveIn.WaveFormat);
                //开始录音，写数据
                _waveIn.DataAvailable += (s, a) =>
                {
                    writer.Write
                    (
                        a.Buffer,
                        0,
                        a.BytesRecorded
                    );
                };
                return true;
            }
            catch (Exception ex)
            {
                LogHelper.WriteErrLog("【麦克风】麦克风不可用:" + ex.Message, ex);
                return false;
            }
        }

        #endregion 录音

        #region 检测是否可用

        /// <summary>
        /// 录制麦克风的声音
        /// </summary>
        private WaveInEvent _waveIn; //new WaveInEvent();

        /// <summary>
        /// 开始录制麦克风
        /// </summary>
        public bool StartRecordAudio
        (
            string audioFile,
            string deviceName = "",
            bool isStopDelFile = true
        )
        {
            try
            {
                int deviceIndex = 0;
                _waveIn = new WaveInEvent();
                List<string> inDeviceList = GetInDeviceListFull();
                if (inDeviceList.Exists(x => deviceName.Contains(x)))
                {
                    deviceIndex = inDeviceList.FindIndex(x => deviceName.Contains(x));
                }
                _waveIn.DeviceNumber = deviceIndex;
                //waveIn.DeviceNumber = 1;
                //生成音频文件的对象
                WaveFileWriter writer = new WaveFileWriter(audioFile, _waveIn.WaveFormat);
                //开始录音，写数据
                _waveIn.DataAvailable += (s, a) =>
                {
                    writer.Write
                    (
                        a.Buffer,
                        0,
                        a.BytesRecorded
                    );
                    short s1 = BitConverter.ToInt16(a.Buffer, 0); //这样采样比较少，反正是int16型的
                    int s2 = Math.Abs(s1 / 50);
                    Console.WriteLine(@"声音大小:" + s2);
                };

                //结束录音
                _waveIn.RecordingStopped += (s, a) =>
                {
                    writer.Dispose();
                    writer = null;
                    _waveIn.Dispose();
                    _waveIn = null;
                    try
                    {
                        if (isStopDelFile)
                        {
                            FileToolsCommon.DeleteFile(audioFile);
                        }
                    }
                    catch (Exception)
                    {
                        // ignored
                    }
                };
                _waveIn.StartRecording();
                return true;
            }
            catch (Exception ex)
            {
                LogHelper.WriteErrLog("【麦克风】麦克风不可用:" + ex.Message, ex);
                return false;
            }
        }

        /// <summary>
        /// 结束录制音频
        /// </summary>
        /// <param name="type">1麦克风 2扬声器</param>
        public void StopRecordAudio(int type)
        {
            try
            {
                if (type == 1)
                {
                    if (_waveIn != null)
                    {
                        try
                        {
                            _waveIn.StopRecording();
                        }
                        catch (Exception)
                        {
                            _waveIn = null;
                        }
                    }
                }
                else if (type == 2)
                {
                    if (_capture != null)
                    {
                        try
                        {
                            _capture.StopRecording();
                        }
                        catch (Exception)
                        {
                            _capture = null;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteErrLog("【麦克风】麦克风不可用:" + ex.Message, ex);
            }
        }

        /// <summary>
        /// 录制扬声器的声音
        /// </summary>
        private WasapiLoopbackCapture _capture; //new WasapiLoopbackCapture();

        /// <summary>
        /// 开始录制扬声器
        /// </summary>
        public bool StartRecordSpeakerAudio(string audioFile)
        {
            try
            {
                _capture = new WasapiLoopbackCapture();
                //生成音频文件的对象
                WaveFileWriter writer = new WaveFileWriter(audioFile, _capture.WaveFormat);
                _capture.DataAvailable += (s, a) =>
                {
                    writer.Write
                    (
                        a.Buffer,
                        0,
                        a.BytesRecorded
                    );
                };
                //结束录音
                _capture.RecordingStopped += (s, a) =>
                {
                    writer.Dispose();
                    writer = null;
                    _capture.Dispose();
                    _capture = null;
                    try
                    {
                        FileToolsCommon.DeleteFile(audioFile);
                    }
                    catch (Exception)
                    {
                        // ignored
                    }
                };
                _capture.StartRecording();
                return true;
            }
            catch (Exception ex)
            {
                LogHelper.WriteErrLog("【扬声器】扬声器不可用:" + ex.Message, ex);
                return false;
            }
        }

        #endregion 检测是否可用

        #endregion 麦克风声卡检测
    }
}
