using System;
using System.Runtime.InteropServices;

namespace NZSDK_CSharpSample
{
    public partial class NZSDK : DynamicLibraryLoader
    {
        #region Constraints

        public enum SDKKinds
        {
            RHINO = 0,
            NRS,
            NVR
        }

        public enum PtzDirection
        {
            LeftUp = 0,
            Up,
            RightUp,
            Left,
            Right,
            LeftDown,
            Down,
            RightDown
        }

        public enum ZoomMethod
        {
            Tele = 0,
            Wide
        }

        public enum ExportVideoFormat : int
        {
            Avi = 0,
            Exe
        }

        public enum ExportImageFormat : int
        {
            Bmp = 0,
            Png,
            Jpg,
            Tif
        }

        #endregion

        #region Fields

        IntPtr ptrHandle;
        SDKKinds sdkKind;

        #endregion

        #region Constructors

        public NZSDK(SDKKinds kind)
            : base()
        {
            sdkKind = kind;
            releaseFileName = "NZSDK.dll";
            if (Load())
                Create();
        }

        #endregion

        #region Properties

        public bool IsCreated
        {
            get
            {
                return (ptrHandle != IntPtr.Zero);
            }
        }


        public SDKKinds SDKKind
        {
            get { return sdkKind; }
        }

        protected IntPtr SdkHandle
        {
            get
            {
                if (ptrHandle != IntPtr.Zero)
                    return ptrHandle;

                switch (sdkKind)
                {
                    case SDKKinds.RHINO:
                        ptrHandle = _NZCreateEx(SessionTypes.Live, Models.Rhino);
                        break;
                    case SDKKinds.NRS:
                        ptrHandle = _NZCreateEx(SessionTypes.Live, Models.Leopard);
                        break;
                    case SDKKinds.NVR:
                        ptrHandle = _NZVMSCreate();
                        break;
                }

                return ptrHandle;
            }
        }

        #endregion

        #region Private / Protected Methods

        protected void Create()
        {
            Release();

            switch (sdkKind)
            {
                case SDKKinds.RHINO:
                    ptrHandle = _NZCreateEx(SessionTypes.Live, Models.Rhino);
                    break;
                case SDKKinds.NRS:
                    ptrHandle = _NZCreateEx(SessionTypes.Live, Models.Leopard);
                    break;
                case SDKKinds.NVR:
                    ptrHandle = _NZVMSCreate();
                    break;
            }
        }

        protected void Release()
        {
            if (ptrHandle == IntPtr.Zero)
                return;

            Disconnect();

            switch (sdkKind)
            {
                case SDKKinds.RHINO:
                case SDKKinds.NRS:
                    _NZDelete(ptrHandle);
                    break;
                case SDKKinds.NVR:
                    _NZVMSDelete(ptrHandle);
                    break;
            }
            ptrHandle = IntPtr.Zero;
        }

        protected bool Command(Commands command)
        {
            return Command(command, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
        }

        protected bool Command(Commands command, IntPtr arg1)
        {
            return Command(command, arg1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
        }

        protected bool Command(Commands command, IntPtr arg1, IntPtr arg2)
        {
            return Command(command, arg1, arg2, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
        }

        protected bool Command(Commands command, IntPtr arg1, IntPtr arg2, IntPtr arg3)
        {
            return Command(command, arg1, arg2, arg3, IntPtr.Zero, IntPtr.Zero);
        }

        protected bool Command(Commands command, IntPtr arg1, IntPtr arg2, IntPtr arg3, IntPtr arg4)
        {
            return Command(command, arg1, arg2, arg3, arg4, IntPtr.Zero);
        }

        protected bool Command(Commands command, IntPtr arg1, IntPtr arg2, IntPtr arg3, IntPtr arg4, IntPtr arg5)
        {
            switch (sdkKind)
            {
                case SDKKinds.RHINO:
                case SDKKinds.NRS:
                    return _NZCommand(SdkHandle, command, arg1, arg2, arg3, arg4, arg5);
                case SDKKinds.NVR:
                    return _NZVMSCommand(SdkHandle, command, arg1, arg2, arg3, arg4, arg5);
            }

            return false;
        }

        #endregion

        #region Public Methods

        public bool Connect(string address, string port, string loginId, string password)
        {
            IntPtr ptrAddress = Marshal.StringToHGlobalUni(address);
            IntPtr ptrLoginId = Marshal.StringToHGlobalUni(loginId);
            IntPtr ptrPassword = Marshal.StringToHGlobalUni(password);
            bool bResult = false;

            switch (sdkKind)
            {
                case SDKKinds.RHINO:
                case SDKKinds.NRS:
                    {
                        IntPtr ptrPort = Marshal.StringToHGlobalUni(port);
                        bResult = Command(Commands.Connect, ptrAddress, ptrPort, ptrLoginId, ptrPassword);
                        Marshal.FreeHGlobal(ptrPort);
                    }
                    break;
                case SDKKinds.NVR:
                    bResult = Command(Commands.Connect, ptrAddress, new IntPtr(Int32.Parse(port)), ptrLoginId, ptrPassword);
                    break;
            }
            Marshal.FreeHGlobal(ptrAddress);
            Marshal.FreeHGlobal(ptrLoginId);
            Marshal.FreeHGlobal(ptrPassword);
            return bResult;
        }

        public void Disconnect()
        {
            Command(Commands.Disconnect);
        }

        public bool GetDatas(VmsDataTypes type, ref string data)
        {
            IntPtr ptrLength = Marshal.AllocHGlobal(4);
            Marshal.WriteInt32(ptrLength, 0);

            GCHandle gch = GCHandle.Alloc(IntPtr.Size, GCHandleType.Pinned);
            IntPtr ppUnmanagedIntegerArray = (IntPtr)gch.AddrOfPinnedObject();
            bool result = Command(Commands.GetDatas, new IntPtr((int)type), ppUnmanagedIntegerArray, ptrLength);
            int nLength = Marshal.ReadInt32(ptrLength);
            IntPtr ptrBuffer = Marshal.ReadIntPtr(ppUnmanagedIntegerArray);
            data = Marshal.PtrToStringAnsi(ptrBuffer);
            _NZVMSReleaseData(ptrBuffer);
            gch.Free();
            Marshal.FreeHGlobal(ptrLength);

            return result;
        }

        public bool OpenVideo(int channel, int streamNumber, IntPtr ptrWindowHandle)
        {
            return Command(Commands.OpenVideo, new IntPtr(channel), new IntPtr(streamNumber), new IntPtr(ptrWindowHandle == IntPtr.Zero ? 0 : 1), ptrWindowHandle);
        }

        public void CloseVideo(int channel, int streamNumber)
        {
            Command(Commands.CloseVideo, new IntPtr(channel), new IntPtr(streamNumber));
        }

        public void ViewResize(int channel, int streamNumber)
        {
            Command(Commands.ViewResize, new IntPtr(channel), new IntPtr(streamNumber));
        }

        public void ViewShowSelection(int channel, int streamNumber, bool visible)
        {
            Command(Commands.ViewShowSelection, new IntPtr(channel), new IntPtr(streamNumber), new IntPtr(Convert.ToInt32(visible)));
        }

        public void ViewShowName(int channel, int streamNumber, string name, int fontHeight)
        {
            IntPtr ptrName = Marshal.StringToHGlobalUni(name);
            Command(Commands.ViewShowName, new IntPtr(channel), new IntPtr(streamNumber), ptrName, new IntPtr(fontHeight));
            Marshal.FreeHGlobal(ptrName);
        }

        public void PanTiltControl(PtzDirection direction, int channel, int speed)
        {
            switch (direction)
            {
                case PtzDirection.LeftUp:
                    Command(Commands.PtzMoveLeftUp, new IntPtr(channel), new IntPtr(1), new IntPtr(speed), new IntPtr(speed == 0 ? 1 : 0));
                    break;
                case PtzDirection.Up:
                    Command(Commands.PtzMoveUp, new IntPtr(channel), new IntPtr(1), new IntPtr(speed), new IntPtr(speed == 0 ? 1 : 0));
                    break;
                case PtzDirection.RightUp:
                    Command(Commands.PtzMoveRightUp, new IntPtr(channel), new IntPtr(1), new IntPtr(speed), new IntPtr(speed == 0 ? 1 : 0));
                    break;
                case PtzDirection.Left:
                    Command(Commands.PtzMoveLeft, new IntPtr(channel), new IntPtr(1), new IntPtr(speed), new IntPtr(speed == 0 ? 1 : 0));
                    break;
                case PtzDirection.Right:
                    Command(Commands.PtzMoveRight, new IntPtr(channel), new IntPtr(1), new IntPtr(speed), new IntPtr(speed == 0 ? 1 : 0));
                    break;
                case PtzDirection.LeftDown:
                    Command(Commands.PtzMoveLeftDown, new IntPtr(channel), new IntPtr(1), new IntPtr(speed), new IntPtr(speed == 0 ? 1 : 0));
                    break;
                case PtzDirection.Down:
                    Command(Commands.PtzMoveDown, new IntPtr(channel), new IntPtr(1), new IntPtr(speed), new IntPtr(speed == 0 ? 1 : 0));
                    break;
                case PtzDirection.RightDown:
                    Command(Commands.PtzMoveRightDown, new IntPtr(channel), new IntPtr(1), new IntPtr(speed), new IntPtr(speed == 0 ? 1 : 0));
                    break;
            }
        }

        public void ZoomControl(ZoomMethod method, int channel, int speed)
        {
            switch (method)
            {
                case ZoomMethod.Tele:
                    Command(Commands.PtzZoomTele, new IntPtr(channel), new IntPtr(1), new IntPtr(speed), new IntPtr(speed == 0 ? 1 : 0));
                    break;
                case ZoomMethod.Wide:
                    Command(Commands.PtzZoomWide, new IntPtr(channel), new IntPtr(1), new IntPtr(speed), new IntPtr(speed == 0 ? 1 : 0));
                    break;
            }
        }

        public void SetPtzPreset(int channel, int preset)
        {
            Command(Commands.PtzSetPreset, new IntPtr(channel), new IntPtr(1), new IntPtr(preset));
        }

        public void MovePtzPreset(int channel, int preset)
        {
            Command(Commands.PtzMovePreset, new IntPtr(channel), new IntPtr(1), new IntPtr(preset));
        }

        public void StartPresetTour(int channel)
        {
            Command(Commands.PtzTourStart, new IntPtr(channel), new IntPtr(1));
        }

        public void StopPresetTour(int channel)
        {
            Command(Commands.PtzTourStop, new IntPtr(channel), new IntPtr(1));
        }

        public bool OpenAudio(int channel, int streamNumber, bool play)
        {
            return Command(Commands.OpenAudio, new IntPtr(channel), new IntPtr(streamNumber), new IntPtr(Convert.ToInt32(play)));
        }

        public void CloseAudio(int channel, int streamNumber)
        {
            Command(Commands.CloseAudio, new IntPtr(channel), new IntPtr(streamNumber));
        }

        public PtzPositionSpecification GetPtzPositionSpecification(int channel)
        {
            PtzPositionSpecification spec = new PtzPositionSpecification();
            IntPtr ptrSpec = Marshal.AllocHGlobal(Marshal.SizeOf(spec));
            bool result = Command(Commands.PtzGetPositionSpecification, new IntPtr(channel), new IntPtr(1), ptrSpec);
            if (result)
                spec = (PtzPositionSpecification)Marshal.PtrToStructure(ptrSpec, typeof(PtzPositionSpecification));
            Marshal.FreeHGlobal(ptrSpec);
            return spec;
        }

        public PtzPosition GetPtzPosition(int channel)
        {
            PtzPosition position = new PtzPosition();
            IntPtr ptrPosition = Marshal.AllocHGlobal(Marshal.SizeOf(position));
            bool result = Command(Commands.PtzGetPosition, new IntPtr(channel), new IntPtr(1), ptrPosition);
            if (result)
                position = (PtzPosition)Marshal.PtrToStructure(ptrPosition, typeof(PtzPosition));
            Marshal.FreeHGlobal(ptrPosition);
            return position;
        }

        public void SetPtzPosition(int channel, PtzPosition position)
        {
            IntPtr ptrPosition = Marshal.AllocHGlobal(Marshal.SizeOf(position));
            Marshal.StructureToPtr(position, ptrPosition, true);
            bool result = Command(Commands.PtzSetPosition, new IntPtr(channel), new IntPtr(1), ptrPosition);
            Marshal.FreeHGlobal(ptrPosition);
        }

        public void OpenEvent(string channels)
        {
            IntPtr ptrChannels = Marshal.StringToHGlobalUni(channels);
            Command(Commands.OpenEvent, ptrChannels);
            Marshal.FreeHGlobal(ptrChannels);
        }

        public void CloseEvent()
        {
            Command(Commands.CloseEvent);
        }

        public bool GetStream(NZSDK.SessionTypes session, int nrsId, IntPtr ptrStream)
        {
            if (SDKKind != SDKKinds.NVR)
                return false;

            return Command(Commands.GetStream, new IntPtr(nrsId), new IntPtr((int)session), ptrStream);
        }

        public bool GetStream(IntPtr ptrStream)
        {
            if (SDKKind == SDKKinds.NVR)
                return false;

            return Command(Commands.GetStream, ptrStream);
        }

        public void ReleaseStream(IntPtr ptrStream)
        {
            Command(Commands.ReleaseStream, ptrStream);
        }

        public bool StartTalk(int channel, int streamNumber)
        {
            return Command(Commands.StartTalk, new IntPtr(channel), new IntPtr(streamNumber));
        }

        public void StopTalk(int channel, int streamNumber)
        {
            Command(Commands.StopTalk, new IntPtr(channel), new IntPtr(streamNumber));
        }

        public bool StartSendWave(int channel, int streamNumber, string waveFilePath)
        {
            IntPtr ptrFilePath = Marshal.StringToHGlobalUni(waveFilePath);
            bool result = Command(Commands.SendWave, new IntPtr(channel), new IntPtr(streamNumber), ptrFilePath, new IntPtr(1));
            Marshal.FreeHGlobal(ptrFilePath);
            return result;
        }

        public void StopSendWave(int channel, int streamNumber)
        {
            IntPtr ptrFilePath = Marshal.StringToHGlobalUni(string.Empty);
            Command(Commands.SendWave, new IntPtr(channel), new IntPtr(streamNumber), ptrFilePath, new IntPtr(0));
            Marshal.FreeHGlobal(ptrFilePath);
        }

        public bool OpenPlaybackVideo(int deviceId, IntPtr ptrWindowHandle)
        {
            return Command(Commands.PlaybackOpenVideo, new IntPtr(deviceId), IntPtr.Zero, new IntPtr(ptrWindowHandle != IntPtr.Zero ? 1 : 0), ptrWindowHandle);
        }

        public void ClosePlaybackVideo(int deviceId)
        {
            Command(Commands.PlaybackCloseVideo, new IntPtr(deviceId));
        }

        public void PlaybackPause(int deviceId)
        {
            Command(Commands.PlaybackPause, new IntPtr(deviceId));
        }

        public bool PlaybackPlayForward(int deviceId, DateTime playTime, int speed, bool refresh)
        {
            string argument = String.Format("refresh={0}&speed={1}&method=forward&date={2}&time={3}",
                refresh ? 1 : 0, speed, playTime.ToString("yyyy-MM-dd"), playTime.ToString("HH-mm-ss"));
            IntPtr ptrArgument = Marshal.StringToHGlobalUni(argument);
            bool result = Command(Commands.PlaybackPlay, new IntPtr(deviceId), IntPtr.Zero, ptrArgument);
            Marshal.FreeHGlobal(ptrArgument);
            return result;
        }

        public bool PlaybackPlayBackward(int deviceId, DateTime playTime, int speed, bool refresh)
        {
            string argument = String.Format("refresh={0}&speed={1}&method=backward&date={2}&time={3}",
                refresh ? 1 : 0, speed, playTime.ToString("yyyy-MM-dd"), playTime.ToString("HH-mm-ss"));
            IntPtr ptrArgument = Marshal.StringToHGlobalUni(argument);
            bool result = Command(Commands.PlaybackPlay, new IntPtr(deviceId), IntPtr.Zero, ptrArgument);
            Marshal.FreeHGlobal(ptrArgument);
            return result;
        }

        public bool StartExportVideo(int deviceId, ExportVideoFormat format, DateTime fromTime, DateTime toTime, string path, IntPtr ptrHandler, IntPtr ptrHandlerParam)
        {
            string option = "<Export>\r\n";
            option += String.Format("\t<ExportKind>{0}</ExportKind>\r\n", (int)format);
            option += String.Format("\t<FromTime>{0}</FromTime>\r\n", fromTime.ToString("yyyyMMddHHmmss"));
            option += String.Format("\t<ToTime>{0}</ToTime>\r\n", toTime.ToString("yyyyMMddHHmmss"));
            option += String.Format("\t<Path>{0}</Path>\r\n", path);
            option += "\t<MaxSize>2104533975</MaxSize>\r\n";
            option += "\t<Timeout>60</Timeout>\r\n";
            option += String.Format("\t<Cameras><Camera><CameraID>{0}</CameraID><FileName>Camera_{0}_{1}_{2}</FileName></Camera></Cameras>\r\n",
                deviceId, fromTime.ToString("yyyyMMddHHmmss"), toTime.ToString("yyyyMMddHHmmss"));
            option += "</Export>\r\n";
            IntPtr ptrOption = Marshal.StringToHGlobalUni(option);

            bool result = Command(Commands.ExportVideo, ptrOption, ptrHandler, ptrHandlerParam);

            Marshal.FreeHGlobal(ptrOption);
            return result;
        }

        public void StopExportVideo()
        {
            Command(Commands.CancelExportVideo);
        }

        public bool ExportImage(int deviceId, ExportImageFormat format, DateTime fromTime, DateTime toTime, string path, IntPtr ptrHandler, IntPtr ptrHandlerParam)
        {
            string option = "<Export>\r\n";
            option += String.Format("\t<FromTime>{0}</FromTime>\r\n", fromTime.ToString("yyyyMMddHHmmss"));
            option += String.Format("\t<ToTime>{0}</ToTime>\r\n", toTime.ToString("yyyyMMddHHmmss"));
            option += String.Format("\t<Path>{0}</Path>\r\n", path);
            option += String.Format("\t<ImageFormat>{0}</ImageFormat>\r\n", format.ToString().ToLower());
            option += String.Format("\t<Cameras><Camera><CameraID>{0}</CameraID><FileName>Camera_{0}_{1}</FileName></Camera></Cameras>\r\n",
                deviceId, fromTime.ToString("yyyyMMddHHmmss"));
            option += "</Export>\r\n";
            IntPtr ptrOption = Marshal.StringToHGlobalUni(option);

            bool result = Command(Commands.ExportImage, ptrOption, ptrHandler, ptrHandlerParam);

            Marshal.FreeHGlobal(ptrOption);
            return result;
        }

        public override void Dispose()
        {
            Release();

            base.Dispose();
        }

        #endregion
    }
}