
using System.Drawing;
using System.Drawing.Drawing2D;
using Hawkeye.VisionBuilder.Workflow;
using Hawkeye.VisionBuilder.Workflow.Datatypes;
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements;
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Origin;
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Poly;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using Point = System.Drawing.Point;
using PointF = System.Drawing.PointF;
using Matrix = Hawkeye.VisionBuilder.Workflow.Matrix;
using Vector2 = Hawkeye.VisionBuilder.Workflow.Vector2;

namespace Hawkeye.VisionBuilder.Features.ImagePreview
{
    public partial class ImagePreviewControl : UserControl,ICanvas
    {
        private Bitmap _backBuffer;
        public List<IGraphicsElement> GraphicsElements { get; set; }=new List<IGraphicsElement>();
        public EControlMode Mode { get; set; } = EControlMode.Preview;

        public Hawkeye.VisionBuilder.Workflow.Datatypes.HawkeyeImage Image
        {
            get => _image;
            set
            {
                var needsCentering = _image == null;
                _image = value;
                if(needsCentering) CenterImage();
            }
        }

        public void CenterImage()
        {
            TransformationMatrix= Workflow.Matrix.Identity;
            var hRatio =  Width/ (float)_image.ImageData.Width;
            var vRatio = Height / (float) _image.ImageData.Height;
            TransformationMatrix*=Matrix.CreateScale(Math.Min(hRatio,vRatio));
            var shift = new Vector2(Width,Height)- Vector2.Transform(new Vector2(_image.ImageData.Width, _image.ImageData.Height),
                TransformationMatrix);
            TransformationMatrix *= Matrix.CreateTranslation(shift.X / 2f, shift.Y / 2f, 0);

        }

        public ImagePreviewControl()
        {
            InitializeComponent();

            _backBuffer = new Bitmap(Width, Height);

            MouseWheel += ZoomPictureBoxWheel_MouseWheel;
            DoubleClick += ZoomPictureBoxWheel_DoubleClick;
            DoubleBuffered = true;
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        }

        private void ZoomPictureBoxWheel_DoubleClick(object sender, EventArgs e)
        {
            
            
            Invalidate();
        }

        private float _zoom = 1;
        private void ZoomPictureBoxWheel_MouseWheel(object sender, MouseEventArgs e)
        {
            _zoom *= (e.Delta > 0) ? 0.9f : (1f / 0.9f);
            TransformationMatrix = TransformationMatrix
                * Matrix.CreateTranslation(-_lastMousePosition.X, -_lastMousePosition.Y, 0)
                * Matrix.CreateScale((e.Delta > 0) ? 0.9f : (1f / 0.9f))
                * Matrix.CreateTranslation(_lastMousePosition.X, _lastMousePosition.Y, 0);
            
            Invalidate();
        }

        public void FitToScreen()
        {
            if (Image == null) return;
            var width = Image.ImageData.Width;
            var height = Image.ImageData.Height;
            var aspectRatio = (float)width / height;
            var screenAspectRatio = (float)Width / Height;

            if (aspectRatio > screenAspectRatio)
            {
                _zoom = Width / (float)width;
                TransformationMatrix = Matrix.Identity;
                TransformationMatrix *= Matrix.CreateScale(_zoom);
                var shift = new Vector2(Width, Height) - Vector2.Transform(new Vector2(width, height),
                    TransformationMatrix);
                TransformationMatrix *= Matrix.CreateTranslation(shift.X / 2f, shift.Y / 2f, 0);
            }
            else
            {
                _zoom = Height / (float)height;
                TransformationMatrix = Matrix.Identity;
                TransformationMatrix *= Matrix.CreateScale(_zoom);
                var shift = new Vector2(Width, Height) - Vector2.Transform(new Vector2(width, height),
                    TransformationMatrix);
                TransformationMatrix *= Matrix.CreateTranslation(shift.X / 2f, shift.Y / 2f, 0);
            }
            Invalidate();
        }
        
        private MouseButtons _mouseButton = MouseButtons.None;

        public Matrix TransformationMatrix { get; set; }=Matrix.Identity;

        
        private Vector2 _lastMousePosition;
        private const float NODE_SIZE = 10;
        protected override void OnMouseDown(MouseEventArgs e)
        {
            _mouseButton = e.Button;
            _lastMousePosition = new Vector2(e.X, e.Y);
            var threshold = NODE_SIZE / _zoom ;
            
            var inverted = Matrix.Invert(TransformationMatrix);

            var imageCoords = Vector2.Transform(new Vector2(e.X, e.Y), inverted);
            _selectedNode=GraphicsElements.Where(x=>x.Editable).Select(x => x.GetNode(imageCoords, threshold)).FirstOrDefault(x => x != null);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            _mouseButton = MouseButtons.None;
        }
        ToolTip _toolTip = new ToolTip();

        static bool RectangleContainsPoint(int x, int y, int width, int height, int px, int py)
        {
            return px >= x && px <= x + width && py >= y && py <= y + height;
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            _toolTip.Hide(this);
        }

        string GetContourInfo(Mat image, int x, int y)
        {
            var contours = image.FindContoursAsArray(RetrievalModes.List, ContourApproximationModes.ApproxSimple);
            var contour = contours.FirstOrDefault(c => Cv2.PointPolygonTest(c, new Point2f(x, y), false) >= 0);
            if (contour == null) return "No contour";
            var area = Cv2.ContourArea(contour);
            var rect = Cv2.BoundingRect(contour);
            var width = rect.Width;
            var height = rect.Height;
            return $"Area: {area}, Width: {width}, Height: {height}";
            
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            // if Ctrl is pressed
            


            if ((ModifierKeys & Keys.Control) != 0&&_mouseButton == MouseButtons.None)
            {
                if(Image==null||Image.ImageData==null) return;
                var inverted = Matrix.Invert(TransformationMatrix);
                var imageCoords = Vector2.Transform(new Vector2(e.X, e.Y), inverted);
                if(!RectangleContainsPoint(0,0,Image.ImageData.Width,Image.ImageData.Height,(int)imageCoords.X,(int)imageCoords.Y)) return;
                if (Image.ImageData.Type() == MatType.CV_8UC3)
                {
                    var pixel = Image.ImageData.Get<Vec3b>((int)imageCoords.Y,(int)imageCoords.X);
                    _toolTip.Show($"R:{pixel.Item0} G:{pixel.Item1} B:{pixel.Item2}", this, e.X + 15, e.Y + 15, 5000);
                    return;
                }

                if (Image.ImageData.Type() == MatType.CV_8UC1)
                {
                    var pixel = Image.ImageData.Get<byte>((int)imageCoords.Y,(int)imageCoords.X);
                    var info= GetContourInfo(Image.ImageData, (int)imageCoords.X, (int)imageCoords.Y);
                    _toolTip.Show($"Gray:{pixel}\n"+info, this, e.X+15, e.Y+15,5000);
                    return;
                }
                
                return;
            }

            if (Mode == EControlMode.Preview)
            {
                if (_mouseButton == MouseButtons.Middle)
                {
                    TransformationMatrix *=
                        Matrix.CreateTranslation(e.X - _lastMousePosition.X, e.Y - _lastMousePosition.Y, 0);

                }
            }
            
            _mouseShift = new Vector2(e.X, e.Y) - _lastMousePosition;
            if (_mouseButton == MouseButtons.Left&& _selectedNode != null)
            {
                var inverted = Matrix.Invert(TransformationMatrix);
                var transformed = Vector2.Transform(new Vector2(e.X, e.Y), inverted);

                
                _selectedNode.SetPosition(transformed);
                

            }
            
            _lastMousePosition = new Vector2(e.X, e.Y);
            Invalidate();
        }

        private Graphics _currentGraphics;
        private Workflow.Datatypes.HawkeyeImage? _image;
        private Vector2 _mouseShift;
        private INode _selectedNode;

        protected override void OnPaint(PaintEventArgs pe)
        {
            if (Image == null)
            {
                pe.Graphics.Clear(Color.Black);
                return;
            }
            try
            {
                using (Graphics g = Graphics.FromImage(_backBuffer))
                {
                    _currentGraphics = g;

                    g.Clear(Color.Black);
                    var img = Image.ImageData.ToBitmap();
                    var pos = Vector2.Transform(new Vector2(0, 0), TransformationMatrix);
                    var size = Vector2.Transform(new Vector2(img.Width, img.Height), TransformationMatrix);
                    g.DrawImage(img, pos.X, pos.Y, size.X - pos.X, size.Y - pos.Y);


                    foreach (IGraphicsElement element in GraphicsElements)
                    {
                        element.Draw(this);
                    }


                }

                pe.Graphics.DrawImage(_backBuffer, 0, 0);
            }
            catch
            {

            }

        }

        

        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
            //base.OnPaintBackground(pevent);
        }

        protected override void OnResize(EventArgs e)
        {
            if (Width == 0) return;
            _backBuffer = new Bitmap(Width, Height);
        }


        
        public void DrawNode(Vector2 location)
        {
            
            var imageCoordSpace = Vector2.Transform(location, TransformationMatrix);
            var start = imageCoordSpace - new Vector2(NODE_SIZE/2, NODE_SIZE / 2);
            var end = (imageCoordSpace + new Vector2(NODE_SIZE / 2, NODE_SIZE / 2))-start;
            

            _currentGraphics.FillRectangle(Brushes.White,start.X,start.Y,end.X,end.Y);
            _currentGraphics.DrawRectangle(Pens.Black,start.X,start.Y,end.X,end.Y);

        }

        public void DrawArrow(Vector2 location, Vector2 direction)
        {
            var imageCoordSpaceLocation = Vector2.Transform(location, TransformationMatrix);
            var imageCoordSpaceSize = Vector2.Transform(location + direction, TransformationMatrix);
            var arrowPen = new Pen(Color.Aqua, 2);
            arrowPen.EndCap = LineCap.Custom;
            arrowPen.CustomEndCap = new AdjustableArrowCap(5,5,true);
            
            _currentGraphics.DrawLine(arrowPen, imageCoordSpaceLocation.X, imageCoordSpaceLocation.Y, imageCoordSpaceSize.X, imageCoordSpaceSize.Y);
        }


        public void DrawRectangle(Vector2 start, Vector2 size, bool isGood)
        {
            var imageCoordSpaceLocation = Vector2.Transform(start, TransformationMatrix);
            var imageCoordSpaceSize = Vector2.Transform(start+size, TransformationMatrix)-imageCoordSpaceLocation;

            var pen = new Pen(isGood ? Color.Green : Color.Red, 2);
            _currentGraphics.DrawRectangle(pen, imageCoordSpaceLocation.X, imageCoordSpaceLocation.Y, imageCoordSpaceSize.X, imageCoordSpaceSize.Y);
        }

        public void DrawLine(Vector2 start, Vector2 end,LineType type)
        {
            var coordStart = Vector2.Transform(start, TransformationMatrix);
            var coordEnd = Vector2.Transform(end, TransformationMatrix);
            var pen = new Pen(type ==LineType.Good? Color.Green:(type==LineType.Bad? Color.Red:Color.Aqua),2);
            _currentGraphics.DrawLine(pen,coordStart.X,coordStart.Y,coordEnd.X,coordEnd.Y);
        }

        public void DrawPoly(Vector2[] points, bool isGood)
        {
            var pen = new Pen(isGood ? Color.Green : Color.Red, 3);
            var coords = points.Select(x=> Vector2.Transform(x, TransformationMatrix)).Select(x => new PointF(x.X, x.Y)).ToArray();
            _currentGraphics.DrawPolygon(pen, coords);
        }

        public void DrawCross(Vector2 location)
        {
            
            var startH=Vector2.Transform(location-new Vector2(10,0),TransformationMatrix);
            var endH = Vector2.Transform(location+new Vector2(10,0), TransformationMatrix);
            var startV =Vector2.Transform(location-new Vector2(0,20), TransformationMatrix);
            var endV =Vector2.Transform(location+new Vector2(0,20), TransformationMatrix);
            var pen = new Pen(Color.Aqua, 1);
            _currentGraphics.DrawLine(pen, startH.X, startH.Y, endH.X, endH.Y);
            _currentGraphics.DrawLine(pen, startV.X, startV.Y, endV.X, endV.Y);
        }
    }
}
