using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Controls;

using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;

using MVVM;

using sdkdef = YoseenPTZ.SDKDef;
using YoseenPTZ.ResourceDef;
using System.Threading.Tasks;

namespace YoseenPTZ.CoreDef
{
    public partial class DeviceView : UserControl, IDisposable, INotifyPropertyChanged
    {
        //
        const int PresetCountMax = sdkdef.LibPTZ.PresetConfig_PresetCountMax;

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        //
        IntPtr _ptrDevice;

        #region flags
        sdkdef.DeviceContextFlags _flags;

        public bool IR_Login => _flags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_IR_Login);
        public bool IR_Preview => _flags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_IR_Preview);
        public bool IR_Record => _flags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_IR_Record);

        public bool VIS_Login => _flags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_VIS_Login);
        public bool VIS_Preview => _flags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_VIS_Preview);
        public bool VIS_Record => _flags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_VIS_Record);

        void SetFlags(int newFlags)
        {
            int oldFlags = (int)_flags;

            int change = oldFlags ^ newFlags;
            sdkdef.DeviceContextFlags changeFlags = (sdkdef.DeviceContextFlags)change;


            //
            _flags = (sdkdef.DeviceContextFlags)newFlags;
            if (changeFlags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_IR_Login))
            {
                OnPropertyChanged("IR_Login");
            }
            if (changeFlags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_IR_Preview))
            {
                OnPropertyChanged("IR_Preview");
            }
            if (changeFlags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_IR_Record))
            {
                OnPropertyChanged("IR_Record");
            }

            if (changeFlags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_VIS_Login))
            {
                OnPropertyChanged("VIS_Login");
            }
            if (changeFlags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_VIS_Preview))
            {
                OnPropertyChanged("VIS_Preview");
            }
            if (changeFlags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_VIS_Record))
            {
                OnPropertyChanged("VIS_Record");
            }

            //
            voiceTalk_initUI(_flags.HasFlag(sdkdef.DeviceContextFlags.DeviceContextFlags_VIS_Login), false);
        }
        #endregion

        public DeviceView()
        {
            InitializeComponent();

            //
            _ptrDevice = sdkdef.LibPTZ.Device_Create();
            if (IntPtr.Zero == _ptrDevice) throw new Exception("Device_Create, error");

            _deviceConfig = new ClsDeviceConfig();
            _imageConfig = new ClsImageConfig();
            _presetArray = new ClsPreset[PresetCountMax];
            int i;
            for (i = 0; i < PresetCountMax; i++)
            {
                ClsPreset preset = new ClsPreset();
                preset.ext_index = i;
                _presetArray[i] = preset;
            }

            //
            _ir_onFrameCB = new sdkdef.callback_onDeviceFrame(ir_onFrame);
            _ir_onFrameUICB = new sdkdef.callback_onDeviceFrame(ir_onFrameUI);
            _previewInfo.ir_streamType = (int)sdkdef.EStreamType.EStreamType_Temp;
            _previewInfo.ir_onFrame = _ir_onFrameCB;
            _previewInfo.ir_hwnd = IntPtr.Zero;

            //
            _vis_onFrameCB = new sdkdef.callback_onDeviceFrame(vis_onFrame);
            _vis_onFrameUICB = new sdkdef.callback_onDeviceFrame(vis_onFrameUI);
            _previewInfo.vis_streamType = (int)sdkdef.EStreamType.EStreamType_Video;
            _previewInfo.vis_onFrame = _vis_onFrameCB;
            _previewInfo.vis_hwnd = IntPtr.Zero;

            //
            voiceTalk_initUI(false, false);
        }

        public void Dispose()
        {
            if (IntPtr.Zero == _ptrDevice) return;
            StopPreview(true);
            sdkdef.LibPTZ.Device_Free(ref _ptrDevice);
        }

        #region login
        readonly ClsDeviceConfig _deviceConfig;

        readonly ClsImageConfig _imageConfig;

        public int SetDeviceConfig(ClsDeviceConfig deviceConfig)
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_SetDeviceConfig(_ptrDevice, ref deviceConfig._bin);
            if (0 > ret) return ret;
            _deviceConfig.Bin2Cls(ref deviceConfig._bin);
            return ret;
        }

        public int Login()
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_Login(_ptrDevice);
            if (0 <= ret)
            {
                SetFlags(ret);
            }
            return ret;
        }

        public int Logout()
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_Logout(_ptrDevice);
            if (0 <= ret)
            {
                SetFlags(ret);
            }
            return ret;
        }
        #endregion

        public int IRSetImageConfig(ClsImageConfig imageConfig)
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_IRSetImageConfig(_ptrDevice, ref imageConfig._bin);
            if (0 > ret) return ret;
            _imageConfig.Bin2Cls(ref imageConfig._bin);
            return ret;
        }

        public int IRSaveFrame(string fn)
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_IRSaveFrame(_ptrDevice, fn);
            return ret;
        }

        #region ptz
        public int PTZControlStart(int act, int arg)
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_PTZControlStart(_ptrDevice, act, arg);
            return ret;
        }

        public int PTZControlStop(int act, int arg)
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_PTZControlStop(_ptrDevice, act, arg);
            return ret;
        }

        public int PTZW4R4(int w, ref int r)
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_PTZW4R4(_ptrDevice, w, ref r);
            return ret;
        }

        public int SetPTZ(int pan, int tilt, int zoom)
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_SetPTZ(_ptrDevice, pan, tilt, zoom);
            return ret;
        }

        public int GetPTZ(ref int pan, ref int tilt, ref int zoom)
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_GetPTZ(_ptrDevice, ref pan, ref tilt, ref zoom);
            return ret;
        }
        #endregion

        #region preset
        readonly ClsPreset[] _presetArray;
        public ClsPreset[] PresetArray
        {
            get
            {
                return _presetArray;
            }
        }

        sdkdef.PresetConfig _presetConfig;

        public int PresetOpenConfig(string fn)
        {
            int ret;
            IntPtr tmp = IntPtr.Zero;
            ret = sdkdef.LibPTZ.Device_PresetOpenConfig(_ptrDevice, fn, ref tmp);
            if (0 <= ret)
            {
                _presetConfig = Marshal.PtrToStructure<sdkdef.PresetConfig>(tmp);
                int i;
                for (i = 0; i < PresetCountMax; i++)
                {
                    ClsPreset preset = _presetArray[i];
                    preset.Bin2Cls(ref _presetConfig.presetArray[i]);
                }
            }
            return 0;
        }

        public void PresetCloseConfig()
        {
            sdkdef.LibPTZ.Device_PresetCloseConfig(_ptrDevice);
        }

        public int PresetSave(ClsPreset preset, int presetId)
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_PresetSave(_ptrDevice, ref preset._bin, presetId);
            if (0 <= ret)
            {
                _presetArray[presetId].Bin2Cls(ref preset._bin);
            }
            return ret;
        }

        public int PresetGoto(int presetId)
        {
            int ret;
            ret = sdkdef.LibPTZ.Device_PresetGoto(_ptrDevice, presetId);
            return ret;
        }
        #endregion

        #region preview, ir
        bool _useTemp;

        sdkdef.DeviceFrame _ir_frame;
        sdkdef.callback_onDeviceFrame _ir_onFrameCB;
        sdkdef.callback_onDeviceFrame _ir_onFrameUICB;

        sdkdef.H264FrameHeaderV2 _h264Head;
        sdkdef.DataFrameHeader _dfhStruct;
        const int ConstFps_FrameCount = 25;
        DateTime _ir_fpsLast;
        int _ir_fpsCount;
        void ir_onFrame(int errorCode, IntPtr frame, IntPtr custom)
        {
            this.Dispatcher.BeginInvoke(_ir_onFrameUICB, errorCode, frame, custom);
        }
        void ir_onFrameUI(int errorCode, IntPtr frame, IntPtr custom)
        {
            if (!_enableFrameCallback) return;
            if (0 == errorCode)
            {
                //canvas
                _ir_frame = Marshal.PtrToStructure<sdkdef.DeviceFrame>(frame);
                ir_canvas.UpdateData(_ir_frame.tempHead, _ir_frame.temp, _ir_frame.bmp);

                //shell, fpa
                double shell = 0, fpa = 0;
                if (_useTemp)
                {
                    _dfhStruct = Marshal.PtrToStructure<sdkdef.DataFrameHeader>(_ir_frame.tempHead);
                    shell = _dfhStruct.ShellTemp; shell /= 1000;
                    fpa = _dfhStruct.FPATemp; fpa /= 1000;
                }
                else
                {
                    _h264Head = Marshal.PtrToStructure<sdkdef.H264FrameHeaderV2>(_ir_frame.tempHead);
                    shell = _h264Head.ShellTemp; shell /= 1000;
                }
                ir_txtSHELL.Text = string.Format("{0:F2}", shell);
                ir_txtFPA.Text = string.Format("{0:F2}", fpa);

                //fps
                _ir_fpsCount++;
                if (_ir_fpsCount >= ConstFps_FrameCount)
                {
                    DateTime dt = DateTime.Now;
                    double d;
                    d = (dt - _ir_fpsLast).TotalMilliseconds;
                    d = ConstFps_FrameCount * 1000 / d;
                    _ir_fpsCount = 0;
                    _ir_fpsLast = dt;
                    ir_txtFPS.Text = d.ToString("F1");
                }
            }
            else
            {
                if (errorCode == sdkdef.EError.EError_PreviewRecoverBegin)
                {
                    ir_txtFPS.Text = "掉线";
                }
            }
        }
        #endregion

        #region preview, vis
        sdkdef.DeviceFrame _vis_frame;
        sdkdef.callback_onDeviceFrame _vis_onFrameCB;
        sdkdef.callback_onDeviceFrame _vis_onFrameUICB;

        DateTime _vis_fpsLast;
        int _vis_fpsCount;
        void vis_onFrame(int errorCode, IntPtr frame, IntPtr custom)
        {
            this.Dispatcher.BeginInvoke(_vis_onFrameUICB, errorCode, frame, custom);
        }
        void vis_onFrameUI(int errorCode, IntPtr frame, IntPtr custom)
        {
            if (!_enableFrameCallback) return;
            if (0 == errorCode)
            {
                //canvas
                _vis_frame = Marshal.PtrToStructure<sdkdef.DeviceFrame>(frame);
                vis_canvas.UpdateData(_vis_frame.bmp);

                //fps
                _vis_fpsCount++;
                if (_vis_fpsCount >= ConstFps_FrameCount)
                {
                    DateTime dt = DateTime.Now;
                    double d;
                    d = (dt - _vis_fpsLast).TotalMilliseconds;
                    d = ConstFps_FrameCount * 1000 / d;
                    _vis_fpsCount = 0;
                    _vis_fpsLast = dt;
                    vis_txtFPS.Text = d.ToString("F1");
                }
            }
            else
            {
                if (errorCode == sdkdef.EError.EError_PreviewRecoverBegin)
                {
                    vis_txtFPS.Text = "掉线";
                }
            }
        }
        #endregion

        #region preview
        sdkdef.DevicePreviewInfo _previewInfo;
        bool _enableFrameCallback;
        bool _isCalling;

        public int StartPreview(bool autoLogin, bool useTemp)
        {
            //
            if (_isCalling) return sdkdef.EError.EError_InvalidState;
            _enableFrameCallback = false;
            _isCalling = true;
            Task task = Task.Factory.StartNew(() =>
            {
                _previewInfo.ir_streamType = useTemp ? (int)sdkdef.EStreamType.EStreamType_Temp : (int)sdkdef.EStreamType.EStreamType_Video;

                int ret;
                if (autoLogin)
                {
                    ret = sdkdef.LibPTZ.Device_Login(_ptrDevice);
                }
                ret = sdkdef.LibPTZ.Device_StartPreview(_ptrDevice, ref _previewInfo);
                return ret;
            }).ContinueWith(x =>
            {
                _isCalling = false;
                int ret = x.Result;
                if (0 <= ret)
                {
                    SetFlags(ret);
                    if (IR_Preview)
                    {
                        ir_canvas.BeginUpdate(_previewInfo.ir_width, _previewInfo.ir_height, useTemp);
                    }
                    if (VIS_Preview)
                    {
                        vis_canvas.BeginUpdate(_previewInfo.vis_width, _previewInfo.vis_height);
                    }
                    _useTemp = useTemp;
                    _enableFrameCallback = true;
                }
            }, TaskScheduler.FromCurrentSynchronizationContext());

            return 0;
        }

        public int StopPreview(bool autoLogout)
        {
            //
            if (_isCalling) return sdkdef.EError.EError_InvalidState;
            _enableFrameCallback = false;
            _isCalling = true;
            Task task = Task.Factory.StartNew(() =>
            {
                int ret;
                ret = sdkdef.LibPTZ.Device_StopPreview(_ptrDevice);
                if (autoLogout) ret = sdkdef.LibPTZ.Device_Logout(_ptrDevice);
                return ret;
            }).ContinueWith(x =>
            {
                _isCalling = false;
                int ret = x.Result;
                if (0 <= ret)
                {
                    SetFlags(ret);
                    ir_canvas.EndUpdate();
                    vis_canvas.EndUpdate();
                }
            }, TaskScheduler.FromCurrentSynchronizationContext());

            return 0;
        }

        public int BeginSave(string fn, int ft)
        {
            //
            if (_isCalling) return sdkdef.EError.EError_InvalidState;
            _isCalling = true;
            Task task = Task.Factory.StartNew(() =>
            {
                int ret;
                ret = sdkdef.LibPTZ.Device_BeginSave(_ptrDevice, fn, ft);
                return ret;
            }).ContinueWith(x =>
            {
                _isCalling = false;
                int ret = x.Result;
                if (0 <= ret)
                {
                    SetFlags(ret);

                }
            }, TaskScheduler.FromCurrentSynchronizationContext());

            return 0;
        }

        public int EndSave()
        {
            //
            if (_isCalling) return sdkdef.EError.EError_InvalidState;
            _isCalling = true;
            Task task = Task.Factory.StartNew(() =>
            {
                int ret;
                ret = sdkdef.LibPTZ.Device_EndSave(_ptrDevice);
                return ret;
            }).ContinueWith(x =>
            {
                _isCalling = false;
                int ret = x.Result;
                if (0 <= ret)
                {
                    SetFlags(ret);
                }
            }, TaskScheduler.FromCurrentSynchronizationContext());
            return 0;
        }
        #endregion

        public void Config_EnableMouse(bool b)
        {
            ir_canvas.Config_EnableMouse(b);
        }

        #region voice talk
        bool _talking;

        void voiceTalk_initUI(bool enable, bool talking)
        {
            //
            btnVoiceTalk.IsEnabled = enable;
            btnVoiceTalk.Content = talking ? "停止对讲" : "语音对讲";
            _talking = talking;
        }

        private void btnVoiceTalk_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            int ret;

            bool talking = false;
            if (!_talking)
            {
                ret = sdkdef.LibPTZ.Device_VISVoiceTalkStart(_ptrDevice);
                if (0 <= ret)
                {
                    talking = true;
                }
            }
            else
            {
                ret = sdkdef.LibPTZ.Device_VISVoiceTalkStop(_ptrDevice);
                talking = false;
            }
            voiceTalk_initUI(true, talking);
        }
        #endregion
    }
}
