using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows;
using System.IO;
using System.Windows.Input;
using System.Globalization;

using sdkdef = YoseenPTZ.SDKDef;
using YoseenPTZ.ResourceDef;

namespace YoseenPTZ.CoreDef
{
    public class IRCanvas : Canvas
    {
        readonly IRCanvasD2D _D2D;
        readonly VisualCollection _visualList;
        readonly MainItem _mainItem;
        readonly MouseItem _mouseItem;

        public IRCanvas()
        {
            _D2D = new IRCanvasD2D();
            _visualList = new VisualCollection(this);

            //
            _mainItem = new MainItem(this, _D2D);
            _visualList.Add(_mainItem);

            //
            _mouseItem = new MouseItem(this, _D2D);
            _visualList.Add(_mouseItem);

            this.MouseMove += IRCanvas_MouseMove;
        }

        #region canvas
        protected override int VisualChildrenCount
        {
            get
            {
                return _visualList.Count;
            }
        }

        protected override Visual GetVisualChild(int index)
        {
            return _visualList[index];
        }
        #endregion

        bool _isInited;
        public bool IsInited => _isInited;
        bool _useTemp;
        public int BeginUpdate(int width, int height, bool useTemp)
        {
            if (_isInited) return sdkdef.EError.EError_InvalidState;
            bool needChange = !(_D2D.DataWidth == width && _D2D.DataHeight == height);
            if (needChange)
            {
                _D2D.Init(width, height);
                this.Width = _D2D.OutputWidth;
                this.Height = _D2D.OutputHeight;
            }

            _useTemp = useTemp;
            _isInited = true;
            return 0;
        }

        public void EndUpdate()
        {
            if (!_isInited) return;

            _isInited = false;
            _D2D.PtrDfh = IntPtr.Zero;
            _D2D.PtrDfd = IntPtr.Zero;
            _D2D.PtrBfd = IntPtr.Zero;
            DrawingContext dc = _mainItem.RenderOpen();
            dc.Close();
        }

        public int UpdateData(IntPtr ptrDfh, IntPtr ptrDfd, IntPtr ptrBfd)
        {
            if (!_isInited) return sdkdef.EError.EError_InvalidState;

            if (_useTemp)
            {
                _D2D.PtrDfh = ptrDfh;
                _D2D.PtrDfd = ptrDfd;
            }
            else
            {
                _D2D.PtrDfh = IntPtr.Zero;
                _D2D.PtrDfd = IntPtr.Zero;
            }
            _D2D.PtrBfd = ptrBfd;
            _D2D.wbImage.WritePixels(_D2D.wbSrcRect, _D2D.PtrBfd, _D2D.wbBufferSize, _D2D.wbStride);

            //
            _mainItem.UpdateData();
            if (_enableMouse)
            {
                _mouseItem.UpdateData();
            }
            return 0;
        }

        #region save
        public unsafe int SaveFrame(string filename, bool withOverlay)
        {
            bool valid = _isInited && IntPtr.Zero != _D2D.PtrBfd && IntPtr.Zero != _D2D.saveBuffer;
            if (!valid) return sdkdef.EError.EError_InvalidState;
            string saveFilename = filename;
            bool saveWithOverlay = withOverlay;

            //coverImage
            uint coverImageSize = 0;
            try
            {
                JpegBitmapEncoder enc = new JpegBitmapEncoder();
                if (saveWithOverlay)
                {
                    RenderTargetBitmap rtb = _D2D.saveRtb;
                    rtb.Render(this);
                    enc.Frames.Add(BitmapFrame.Create(rtb));
                }
                else
                {
                    enc.Frames.Add(BitmapFrame.Create(_D2D.wbImage));
                }
                UnmanagedMemoryStream stream = new UnmanagedMemoryStream((byte*)_D2D.saveBuffer.ToPointer(),
                    _D2D.saveCoverSize, _D2D.saveCoverSize, FileAccess.Write);
                enc.Save(stream);
                coverImageSize = (uint)stream.Position;
                stream.Close();
                if (coverImageSize >= _D2D.saveCoverSize) coverImageSize = 0;
            }
            catch (Exception ex)
            {
                LogUtil.Error(ex.Message);
                return sdkdef.EError.EError_InternalError;
            }

            //
            IntPtr ptrFile = sdkdef.Win32Helper._wfopen(saveFilename, "wb");
            if (ptrFile == IntPtr.Zero) return sdkdef.EError.EError_FileOpen;
            sdkdef.Win32Helper.fwrite(_D2D.saveBuffer, coverImageSize, 1, ptrFile);
            sdkdef.Win32Helper.fclose(ptrFile);
            return 0;
        }
        #endregion

        #region mouse
        bool _enableMouse;
        public void Config_EnableMouse(bool b)
        {
            if (b == _enableMouse) return;
            _enableMouse = b;
            _mouseItem.ClearData();
        }

        private void IRCanvas_MouseMove(object sender, MouseEventArgs e)
        {
            bool b = _enableMouse && IntPtr.Zero != _D2D.PtrDfh;
            if (!b) return;

            Point p = e.GetPosition(this);
            _mouseItem.UpdateData(ref p);
        }
        #endregion
    }

    public class IRCanvasD2D : IDisposable
    {
        //
        public int DataWidth;
        public int DataHeight;
        public int DataPixels;
        public ushort DataXMax;
        public ushort DataYMax;

        public WriteableBitmap wbImage;
        public Int32Rect wbSrcRect;
        public int wbBufferSize;
        public int wbStride;

        //
        public int OutputWidth;
        public int OutputHeight;
        public Rect RectImage;
        public double Scale;

        //
        public IntPtr PtrDfh;
        public IntPtr PtrDfd;
        public IntPtr PtrBfd;

        //save
        public IntPtr saveBuffer;
        public int saveCoverSize;
        public RenderTargetBitmap saveRtb;

        public IRCanvasD2D()
        {
        }

        public void Dispose()
        {
            if (saveBuffer == IntPtr.Zero) return;
            sdkdef.Win32Helper.free(saveBuffer);
            saveBuffer = IntPtr.Zero;
        }

        public void Init(int dataWidth, int dataHeight)
        {
            bool needChange = !(DataWidth == dataWidth && DataHeight == dataHeight);
            if (!needChange) return;
            if (saveBuffer != IntPtr.Zero)
            {
                sdkdef.Win32Helper.free(saveBuffer);
                saveBuffer = IntPtr.Zero;
            }

            //
            DataWidth = dataWidth;
            DataHeight = dataHeight;
            DataPixels = dataWidth * dataHeight;
            DataXMax = (ushort)(dataWidth - 1); DataYMax = (ushort)(dataHeight - 1);

            wbImage = new WriteableBitmap(dataWidth, dataHeight, 96, 96, PixelFormats.Bgr32, null);
            wbSrcRect = new Int32Rect(0, 0, dataWidth, dataHeight);
            wbBufferSize = DataPixels * 4;
            wbStride = dataWidth * 4;

            //
            int dispWidth = dataWidth;
            int dispHeight = dataHeight;
            int dispPixels = dispWidth * dispHeight;
            OutputWidth = dispWidth; OutputHeight = dispHeight;
            Scale = 1.0;
            RectImage.X = 0; RectImage.Y = 0;
            RectImage.Width = dispWidth; RectImage.Height = dispHeight;

            //
            saveRtb = new RenderTargetBitmap(dispWidth, dispHeight, 96, 96, PixelFormats.Pbgra32);
            saveCoverSize = dispPixels * 4;
            int saveBufferSize = saveCoverSize;
            saveBuffer = sdkdef.Win32Helper.calloc(1, (uint)saveBufferSize);
            if (saveBuffer == IntPtr.Zero)
            {
                LogUtil.Error("no mem");
            }
        }

        /// <summary>
        /// canvas to data
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="p"></param>
        public void C2D(ref ushort x, ref ushort y, ref Point p)
        {
            if (p.X < 0) p.X = 0;
            else if (p.X > RectImage.Right) p.X = RectImage.Right;
            x = Convert.ToUInt16(p.X / Scale);
            if (x > DataXMax) x = DataXMax;

            //
            if (p.Y < 0) p.Y = 0;
            else if (p.Y > RectImage.Bottom) p.Y = RectImage.Bottom;
            y = Convert.ToUInt16(p.Y / Scale);
            if (y > DataYMax) y = DataYMax;
        }

        /// <summary>
        /// data to canvas
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="p"></param>
        public void D2C(ushort x, ushort y, ref Point p)
        {
            p.X = x * Scale;
            p.Y = y * Scale;
        }
    }

    class MouseItem : DrawingVisual
    {
        readonly IRCanvas _canvas;
        readonly IRCanvasD2D _canvasD2D;

        sdkdef.xxxmeasure _mo;
        sdkdef.xxxresult _mr;

        //
        const double ConstTextDelta = 6.0;
        Rect _textRect;
        Point _point;
        FormattedText _ft;

        public MouseItem(IRCanvas canvas, IRCanvasD2D canvasD2D)
        {
            _canvas = canvas;
            _canvasD2D = canvasD2D;
            _mo = sdkdef.xxxmeasure.Create();
            _mo.measuretype = sdkdef.xxxmeasuretype.point;
            _mo.xydata[2] = 1;
        }

        public void ClearData()
        {
            DrawingContext dc = this.RenderOpen();
            dc.Close();
        }

        public void UpdateData()
        {
            if (IntPtr.Zero == _canvasD2D.PtrDfh) return;

            //
            _canvasD2D.C2D(ref _mo.xydata[0], ref _mo.xydata[1], ref _point);
            sdkdef.YoseenAlg.mtGetResult(_canvasD2D.PtrDfh, _canvasD2D.PtrDfd, ref _mo, ref _mr);

            //
            _ft = new FormattedText(string.Format("{0:F1}", _mr.avg),
                CultureInfo.InvariantCulture, FlowDirection.LeftToRight,
                CanvasStatic.TextTypeface, CanvasStatic.TextFontSize1, CanvasStatic.BrushTextFg);

            //mouse-xy
            double w = _ft.Width + ConstTextDelta;
            double h = _ft.Height + ConstTextDelta;
            _textRect.X = _point.X - w;
            _textRect.Y = _point.Y - h;
            _textRect.Width = w;
            _textRect.Height = h;
            bool xyOffset = _textRect.X < 0 && _textRect.Y < 0;
            if (_textRect.X < 0) _textRect.X = _point.X;
            if (_textRect.Y < 0) _textRect.Y = _point.Y;
            if (xyOffset)
            {
                _textRect.X += 16;
                _textRect.Y += 16;
            }
            _textRect.X += ConstTextDelta / 2;
            _textRect.Y += ConstTextDelta / 2;
            _textRect.Width -= ConstTextDelta;
            _textRect.Height -= ConstTextDelta;

            //
            DrawingContext dc = this.RenderOpen();
            dc.DrawRectangle(CanvasStatic.BrushTextBk, null, _textRect);
            dc.DrawText(_ft, _textRect.Location);
            dc.Close();
        }

        public void UpdateData(ref Point position)
        {
            _point = position;
            UpdateData();
        }
    }

    class MainItem : DrawingVisual
    {
        readonly IRCanvas _canvas;
        readonly IRCanvasD2D _canvasD2D;

        const double ConstTextDelta = 6.0;

        sdkdef.xxxresult _mr;

        Rect _textRect;
        Point _point;
        FormattedText _ft;

        public MainItem(IRCanvas canvas, IRCanvasD2D canvasD2D)
        {
            _canvas = canvas;
            _canvasD2D = canvasD2D;
        }

        void drawInfo(DrawingContext dc)
        {
            //
            sdkdef.YoseenAlg.mtGetResult_GMO(_canvasD2D.PtrDfh, _canvasD2D.PtrDfd, ref _mr);
            _canvasD2D.D2C(_mr.maxX, _mr.maxY, ref _point);

            //
            _ft = new FormattedText(string.Format("{0:F1}", _mr.max),
                CultureInfo.InvariantCulture, FlowDirection.LeftToRight,
                CanvasStatic.TextTypeface, CanvasStatic.TextFontSize1, CanvasStatic.BrushTextFg);

            //track-xy
            double textw = _ft.Width;
            double texth = _ft.Height;

            double xmax = _canvasD2D.RectImage.Width;
            double x = _point.X + ConstTextDelta;
            if (x + textw > xmax) x = _point.X - ConstTextDelta - textw;

            double ymax = _canvasD2D.RectImage.Height;
            double y = _point.Y + ConstTextDelta;
            if (y + texth > ymax) y = _point.Y - ConstTextDelta - texth;
            _textRect.X = x;
            _textRect.Y = y;
            _textRect.Width = textw;
            _textRect.Height = texth;

            //
            dc.DrawRectangle(CanvasStatic.BrushTextBk, null, _textRect);
            dc.DrawText(_ft, _textRect.Location);
            CanvasStatic.DrawTrack(dc, _point, CanvasStatic.PenRed);
        }

        public void UpdateData()
        {
            DrawingContext dc = this.RenderOpen();
            dc.DrawImage(_canvasD2D.wbImage, _canvasD2D.RectImage);

            if (_canvasD2D.PtrDfh != IntPtr.Zero)
            {
                drawInfo(dc);
            }
            dc.Close();
        }
    }
}
