using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Drawing.Drawing2D;

namespace MoveLineSegments
{
    public partial class frm27MoveLineSegments : Form
    {
        public frm27MoveLineSegments()
        {
            InitializeComponent();
        }

        // The "size" of an object for mouse over purposes.
        private const int ObjectRadius = 3;

        // We're over an object if the distance squared
        // between the mouse and the object is less than this.
        private const int OverDistSquared = ObjectRadius * ObjectRadius;

        // The points that make up the line segments.
        private List<Point> Pt1 = new List<Point>();
        private List<Point> Pt2 = new List<Point>();

        // Points for the new line.
        private bool IsDrawing = false;
        private Point NewPt1, NewPt2;

        // The mouse is up. See whether we're over an endpoint or segment.
        private void canvasPictureBox_MouseIsUp_MouseMove(object sender, MouseEventArgs e)
        {
            Cursor newCursor = Cursors.Cross;

            // See what we're over.
            Point hitPoint;
            int segmentNumber;

            if (MouseIsOverEndpoint(e.Location, out segmentNumber, out hitPoint))
                newCursor = Cursors.Arrow;
            else if (MouseIsOverSegment(e.Location, out segmentNumber))
                newCursor = Cursors.Hand;

            // Set the new cursor.
            if (canvasPictureBox.Cursor != newCursor)
                canvasPictureBox.Cursor = newCursor;
        }

        // See what we're over and start doing whatever is appropriate.
        private void canvasPictureBox_MouseDown(object sender, MouseEventArgs e)
        {
            // See what we're over.
            Point hitPoint;
            int segmentNumber;

            if (MouseIsOverEndpoint(e.Location, out segmentNumber, out hitPoint))
            {
                // Start moving this endpoint.
                canvasPictureBox.MouseMove -= canvasPictureBox_MouseIsUp_MouseMove;
                canvasPictureBox.MouseMove += canvasPictureBox_MovingEndPoint_MouseMove;
                canvasPictureBox.MouseUp += canvasPictureBox_MovingEndPoint_MouseUp;

                // Remember the segment number.
                MovingSegment = segmentNumber;

                // See if we're moving the start endpoint.
                MovingStartEndPoint = (Pt1[segmentNumber].Equals(hitPoint));

                // Remember the offset from the mouse to the point.
                OffsetX = hitPoint.X - e.X;
                OffsetY = hitPoint.Y - e.Y;
            }
            else if (MouseIsOverSegment(e.Location, out segmentNumber))
            {
                // Start moving this segment.
                canvasPictureBox.MouseMove -= canvasPictureBox_MouseIsUp_MouseMove;
                canvasPictureBox.MouseMove += canvasPictureBox_MovingSegment_MouseMove;
                canvasPictureBox.MouseUp += canvasPictureBox_MovingSegment_MouseUp;

                // Remember the segment number.
                MovingSegment = segmentNumber;

                // Remember the offset from the mouse to the segment's first point.
                OffsetX = Pt1[segmentNumber].X - e.X;
                OffsetY = Pt1[segmentNumber].Y - e.Y;
            }
            else
            {
                // Start drawing a new segment.
                canvasPictureBox.MouseMove -= canvasPictureBox_MouseIsUp_MouseMove;
                canvasPictureBox.MouseMove += canvasPictureBox_Drawing_MouseMove;
                canvasPictureBox.MouseUp += canvasPictureBox_Drawing_MouseUp;

                IsDrawing = true;
                NewPt1 = new Point(e.X, e.Y);
                NewPt2 = new Point(e.X, e.Y);
            }
        }

        #region "Drawing"

        // We're drawing a new segment.
        private void canvasPictureBox_Drawing_MouseMove(object sender, MouseEventArgs e)
        {
            // Save the new point.
            NewPt2 = new Point(e.X, e.Y);

            // Redraw.
            canvasPictureBox.Invalidate();
        }

        // Stop drawing.
        private void canvasPictureBox_Drawing_MouseUp(object sender, MouseEventArgs e)
        {
            IsDrawing = false;

            // Reset the event handlers.
            canvasPictureBox.MouseMove -= canvasPictureBox_Drawing_MouseMove;
            canvasPictureBox.MouseMove += canvasPictureBox_MouseIsUp_MouseMove;
            canvasPictureBox.MouseUp -= canvasPictureBox_Drawing_MouseUp;

            // Create the new segment.
            Pt1.Add(NewPt1);
            Pt2.Add(NewPt2);

            // Redraw.
            canvasPictureBox.Invalidate();
        }

        #endregion // Drawing

        #region "Moving End Point"

        // The segment we're moving or the segment whose endpoint we're moving.
        private int MovingSegment = -1;

        // The endpoint we're moving.
        private bool MovingStartEndPoint = false;

        // The offset from the mouse to the object being moved.
        private int OffsetX, OffsetY;

        // We're moving an endpoint.
        private void canvasPictureBox_MovingEndPoint_MouseMove(object sender, MouseEventArgs e)
        {
            // Move the point to its new location.
            if (MovingStartEndPoint)
                Pt1[MovingSegment] =
                    new Point(e.X + OffsetX, e.Y + OffsetY);
            else
                Pt2[MovingSegment] =
                    new Point(e.X + OffsetX, e.Y + OffsetY);

            // Redraw.
            canvasPictureBox.Invalidate();
        }

        // Stop moving the endpoint.
        private void canvasPictureBox_MovingEndPoint_MouseUp(object sender, MouseEventArgs e)
        {
            // Reset the event handlers.
            canvasPictureBox.MouseMove += canvasPictureBox_MouseIsUp_MouseMove;
            canvasPictureBox.MouseMove -= canvasPictureBox_MovingEndPoint_MouseMove;
            canvasPictureBox.MouseUp -= canvasPictureBox_MovingEndPoint_MouseUp;

            // Redraw.
            canvasPictureBox.Invalidate();
        }

        #endregion // Moving End Point

        #region "Moving Segment"

        // We're moving a segment.
        private void canvasPictureBox_MovingSegment_MouseMove(object sender, MouseEventArgs e)
        {
            // See how far the first point will move.
            int newX1 = e.X + OffsetX;
            int newY1 = e.Y + OffsetY;

            int dx = newX1 - Pt1[MovingSegment].X;
            int dy = newY1 - Pt1[MovingSegment].Y;

            if (dx == 0 && dy == 0) return;

            // Move the segment to its new location.
            Pt1[MovingSegment] = new Point(newX1, newY1);
            Pt2[MovingSegment] = new Point(
                Pt2[MovingSegment].X + dx,
                Pt2[MovingSegment].Y + dy);

            // Redraw.
            canvasPictureBox.Invalidate();
        }

        // Stop moving the segment.
        private void canvasPictureBox_MovingSegment_MouseUp(object sender, MouseEventArgs e)
        {
            // Reset the event handlers.
            canvasPictureBox.MouseMove += canvasPictureBox_MouseIsUp_MouseMove;
            canvasPictureBox.MouseMove -= canvasPictureBox_MovingSegment_MouseMove;
            canvasPictureBox.MouseUp -= canvasPictureBox_MovingSegment_MouseUp;

            // Redraw.
            canvasPictureBox.Invalidate();
        }

        #endregion // Moving Segment

        // See if the mouse is over an endpoint.
        private bool MouseIsOverEndpoint(Point mousePoint, out int segmentNumber, out Point hitPoint)
        {
            for (int i = 0; i < Pt1.Count; i++)
            {
                // Check the starting point.
                if (FindDistanceToPointSquared(mousePoint, Pt1[i]) < OverDistSquared)
                {
                    // We're over this point.
                    segmentNumber = i;
                    hitPoint = Pt1[i];
                    return true;
                }

                // Check the endpoint.
                if (FindDistanceToPointSquared(mousePoint, Pt2[i]) < OverDistSquared)
                {
                    // We're over this point.
                    segmentNumber = i;
                    hitPoint = Pt2[i];
                    return true;
                }
            }

            segmentNumber = -1;
            hitPoint = new Point(-1, -1);
            return false;
        }

        // See if the mouse is over a line segment.
        private bool MouseIsOverSegment(Point mousePoint, out int segmentNumber)
        {
            for (int i = 0; i < Pt1.Count; i++)
            {
                // See if we're over the segment.
                PointF closest;
                if (FindDistanceToSegmentSquared(
                    mousePoint, Pt1[i], Pt2[i], out closest)
                        < OverDistSquared)
                {
                    // We're over this segment.
                    segmentNumber = i;
                    return true;
                }
            }

            segmentNumber = -1;
            return false;
        }

        // Calculate the distance squared between two points.
        private int FindDistanceToPointSquared(Point pt1, Point pt2)
        {
            int dx = pt1.X - pt2.X;
            int dy = pt1.Y - pt2.Y;
            return dx * dx + dy * dy;
        }

        // Calculate the distance squared between point pt and the segment p1 --> p2.
        private double FindDistanceToSegmentSquared(Point pt, Point p1, Point p2, out PointF closest)
        {
            float dx = p2.X - p1.X;
            float dy = p2.Y - p1.Y;
            if ((dx == 0) && (dy == 0))
            {
                // It's a point not a line segment.
                closest = p1;
                dx = pt.X - p1.X;
                dy = pt.Y - p1.Y;
                return dx * dx + dy * dy;
            }

            // Calculate the t that minimizes the distance.
            float t = ((pt.X - p1.X) * dx + (pt.Y - p1.Y) * dy) / (dx * dx + dy * dy);

            // See if this represents one of the segment's
            // endpoints or a point in the middle.
            if (t < 0)
            {
                closest = new PointF(p1.X, p1.Y);
                dx = pt.X - p1.X;
                dy = pt.Y - p1.Y;
            }
            else if (t > 1)
            {
                closest = new PointF(p2.X, p2.Y);
                dx = pt.X - p2.X;
                dy = pt.Y - p2.Y;
            }
            else
            {
                closest = new PointF(p1.X + t * dx, p1.Y + t * dy);
                dx = pt.X - closest.X;
                dy = pt.Y - closest.Y;
            }

            return dx * dx + dy * dy;
        }

        // Draw the lines.
        private void picCanvas_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

            // Draw the segments.
            for (int i = 0; i < Pt1.Count; i++)
                e.Graphics.DrawLine(Pens.Blue, Pt1[i], Pt2[i]);

            // Draw the endpoints.
            foreach (Point pt in Pt1)
            {
                Rectangle rect = new Rectangle(
                    pt.X - ObjectRadius, pt.Y - ObjectRadius,
                    2 * ObjectRadius + 1, 2 * ObjectRadius + 1);
                e.Graphics.FillEllipse(Brushes.White, rect);
                e.Graphics.DrawEllipse(Pens.Black, rect);
            }
            foreach (Point pt in Pt2)
            {
                Rectangle rect = new Rectangle(
                    pt.X - ObjectRadius, pt.Y - ObjectRadius,
                    2 * ObjectRadius + 1, 2 * ObjectRadius + 1);
                e.Graphics.FillEllipse(Brushes.White, rect);
                e.Graphics.DrawEllipse(Pens.Black, rect);
            }

            // If there's a new segment under constructions, draw it.
            if (IsDrawing)
            {
                e.Graphics.DrawLine(Pens.Red, NewPt1, NewPt2);
            }
        }


        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.canvasPictureBox = new System.Windows.Forms.PictureBox();
            ((System.ComponentModel.ISupportInitialize)(this.canvasPictureBox)).BeginInit();
            this.SuspendLayout();
            // 
            // canvasPictureBox
            // 
            this.canvasPictureBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
            | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.canvasPictureBox.BackColor = System.Drawing.Color.White;
            this.canvasPictureBox.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
            this.canvasPictureBox.Location = new System.Drawing.Point(12, 12);
            this.canvasPictureBox.Name = "canvasPictureBox";
            this.canvasPictureBox.Size = new System.Drawing.Size(260, 237);
            this.canvasPictureBox.TabIndex = 1;
            this.canvasPictureBox.TabStop = false;
            this.canvasPictureBox.Paint += new System.Windows.Forms.PaintEventHandler(this.picCanvas_Paint);
            this.canvasPictureBox.MouseDown += new System.Windows.Forms.MouseEventHandler(this.canvasPictureBox_MouseDown);
            this.canvasPictureBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.canvasPictureBox_MouseIsUp_MouseMove);
            // 
            // frm27MoveLineSegments
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 261);
            this.Controls.Add(this.canvasPictureBox);
            this.Name = "frm27MoveLineSegments";
            this.Text = "MoveLineSegments";
            ((System.ComponentModel.ISupportInitialize)(this.canvasPictureBox)).EndInit();
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.PictureBox canvasPictureBox;
    }
}