using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Controls;
using pdftron.PDF;
using pdftron.SDF;

using UIPoint = System.Windows.Point;
using UIRect = System.Windows.Rect;
using PDFPoint = pdftron.PDF.Point;
using PDFRect = pdftron.PDF.Rect;
using PDFPage = pdftron.PDF.Page;



namespace pdftron.PDF.Tools
{
    public class CalloutCreate : SimpleShapeCreate
    {
        protected enum CalloutState
        {
            rectangle,
            point1,
            point2,
            point3,
            editingText,
        }
        protected CalloutState mCurrentState = CalloutState.rectangle;
        protected int mShapePageNumber = 0;

        protected System.Windows.Shapes.Path mShape;
        protected RectangleGeometry mRectangleGeometry;
        protected SolidColorBrush mTextBrush;
        protected double mFontSize;

        protected System.Windows.Shapes.Path mCalloutShape;
        protected UIPoint mFirstCalloutPoint;
        protected UIPoint mSecondCalloutPoint;
        protected UIPoint mThirdCalloutPoint;
        protected PathFigure mCalloutPathFigure;

        protected UIPoint aPt1, aPt2, aPt3; // the end points of the shorter lines that form the arrow
        protected double mArrowHeadLength;
        protected double mCos, mSin;

        protected Border mTextPopupBorder;
        protected TextBox mTextBox;
        protected PDFRect mTextRect;
        protected bool mIsTextPopupOpen = false;
        protected bool mCancelTextAnnot = false;

        protected bool mIsTextEdited = false;

        protected bool mIgnoreInputEvents = false;

        public CalloutCreate(PDFViewWPF view, ToolManager manager) 
            : base(view, manager)
        {
            mNextToolMode = ToolManager.ToolType.e_callout_create;
            mToolMode = ToolManager.ToolType.e_callout_create;
            mViewerCanvas = mToolManager.AnnotationCanvas;
        }

        internal override void OnCreate()
        {
            base.OnCreate();
            pdftron.PDF.Tools.Utilities.ColorSettings.ToolColor col = pdftron.PDF.Tools.Utilities.ColorSettings.TextLineColor;
            mUseStroke = col.Use;
            mStrokeBrush = new SolidColorBrush(Color.FromArgb(255, col.R, col.G, col.B));

            col = pdftron.PDF.Tools.Utilities.ColorSettings.TextFillColor;
            mUseFill = col.Use;
            mFillBrush = new SolidColorBrush(Color.FromArgb(255, col.R, col.G, col.B));

            col = pdftron.PDF.Tools.Utilities.ColorSettings.TextColor;
            mTextBrush = new SolidColorBrush(Color.FromArgb(255, col.R, col.G, col.B));

            mStrokeThickness = pdftron.PDF.Tools.Properties.Settings.Default.TextLineThickness;
            mOpacity = pdftron.PDF.Tools.Properties.Settings.Default.TextOpacity;
            mFontSize = pdftron.PDF.Tools.Properties.Settings.Default.FontSize;
            if (mFontSize <= 0)
            {
                mFontSize = 12;
            }
        }

        internal override void OnClose()
        {
            if (mIsTextPopupOpen)
            {
                CloseTextPopup();
            }
            if (!_InDelayRemoveList)
            {
                if (mToolManager.AnnotationCanvas.Children.Contains(this))
                {
                    mToolManager.AnnotationCanvas.Children.Remove(this);
                }
                this.Children.Clear();
            }
            base.OnClose();
        }

        internal override void MouseLeftButtonDownHandler(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (mIgnoreInputEvents)
            {
                base.MouseLeftButtonDownHandler(sender, e);
                return;
            }
            ProcessInputDown(e);
            if (mNextToolMode == mToolMode)
            {
                base.MouseLeftButtonDownHandler(sender, e);
            }
        }

        public override void TouchDownHandler(object sender, TouchEventArgs e)
        {
            if (mIgnoreInputEvents)
            {
                base.TouchDownHandler(sender, e);
                return;
            }
            ProcessInputDown(e);
            if (mNextToolMode == mToolMode)
            {
                base.TouchDownHandler(sender, e);
            }
        }

        private void ProcessInputDown(InputEventArgs e)
        {
            if (mIsTextPopupOpen)
            {
                mIsTextPopupOpen = false;
                if (mUseSameToolWhenDone)
                {
                    Reset();
                }
                else
                {
                    mNextToolMode = ToolManager.ToolType.e_pan;
                }

            }
        }

        internal override void MouseMovedHandler(object sender, MouseEventArgs e)
        {
            base.MouseMovedHandler(sender, e);
            if (mCurrentState == CalloutState.point2 || mCurrentState == CalloutState.point3)
            {
                ProcessInputMove(e);
            }
        }

        public override void TouchMoveHandler(object sender, TouchEventArgs e)
        {
            base.TouchMoveHandler(sender, e);
        }


        internal override void MouseLeftButtonUpHandler(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            base.MouseLeftButtonUpHandler(sender, e);
            ProcessInputUp(e);
        }

        public override void TouchUpHandler(object sender, System.Windows.Input.TouchEventArgs e)
        {
            base.TouchUpHandler(sender, e);
            ProcessInputUp(e);
        }
        
        private void ProcessInputUp(InputEventArgs e)
        {
            if (mIsDrawing)
            {
                if (mCurrentState == CalloutState.rectangle)
                {
                    // rectangle in canvas space
                    mTextRect = new PDFRect(mDownPoint.X, mDownPoint.Y, mDragPoint.X, mDragPoint.Y);
                    mTextRect.Normalize();

                    mCurrentState = CalloutState.point2;

                    PathGeometry geom = new PathGeometry();
                    mCalloutShape = new Path();
                    mCalloutShape.StrokeLineJoin = PenLineJoin.Miter;
                    mCalloutShape.StrokeMiterLimit = 2;
                    mCalloutShape.Data = geom;
                    mCalloutShape.StrokeThickness = mDrawThickness;
                    mCalloutShape.Stroke = mStrokeBrush;
                    this.Children.Add(mCalloutShape);

                    PathFigureCollection collection = new PathFigureCollection();
                    geom.Figures = collection;

                    mFirstCalloutPoint = new UIPoint((mDownPoint.X + mDragPoint.X) / 2 - mPageCropOnClient.x1, (mDownPoint.Y + mDragPoint.Y) / 2 - mPageCropOnClient.y1);
                    mSecondCalloutPoint = new UIPoint(mDragPoint.X - mPageCropOnClient.x1, mDragPoint.Y - mPageCropOnClient.y1);
                    mFirstCalloutPoint = GetFirstCalloutPoint();

                    mCalloutPathFigure = new PathFigure();
                    mCalloutPathFigure.StartPoint = mFirstCalloutPoint;

                    collection.Add(mCalloutPathFigure);

                    mArrowHeadLength = mZoomLevel * GetLineEndingLength(mStrokeThickness);

                    mShapePageNumber = mDownPageNumber;
                }

                else if (mCurrentState == CalloutState.point2)
                {
                    mCos = Math.Cos(3.14159265 / 6); //30 degree
                    mSin = Math.Sin(3.14159265 / 6);
                    mCurrentState = CalloutState.point3;
                }

                else if (mCurrentState == CalloutState.point3)
                {
                    mDownPageNumber = mShapePageNumber;
                    mCurrentState = CalloutState.editingText;
                    CreateTextPopup();
                    //CreateTextAnnot();
                }
            }
        }

        internal override void PreviewMouseWheelHandler(object sender, MouseWheelEventArgs e)
        {
            if (mIsDrawing)
            {
                e.Handled = true;
            }
            else
            {
                base.PreviewMouseWheelHandler(sender, e);
            }
        }

        internal override void KeyDownAction(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Escape)
            {
                e.Handled = true;
                mCancelTextAnnot = true;
                if (mUseSameToolWhenDone)
                {
                    Reset();
                }
                else
                {
                    mNextToolMode = ToolManager.ToolType.e_pan;
                }
            }
            else
            {
                base.KeyDownAction(sender, e);
            }
        }

        protected override void CheckBounds(ref UIPoint point)
        {
            if (mPageCropOnClient != null)
            {
                if (point.X < mPageCropOnClient.x1 + mHalfThickness)
                {
                    point.X = mPageCropOnClient.x1 + mHalfThickness;
                }
                if (point.X > mPageCropOnClient.x2 - mHalfThickness)
                {
                    point.X = mPageCropOnClient.x2 - mHalfThickness;
                }
                if (point.Y < mPageCropOnClient.y1 + mHalfThickness)
                {
                    point.Y = mPageCropOnClient.y1 + mHalfThickness;
                }
                if (point.Y > mPageCropOnClient.y2 - mHalfThickness)
                {
                    point.Y = mPageCropOnClient.y2 - mHalfThickness;
                }
            }
        }

        protected override void Draw()
        {
            if (!mShapeHasBeenCreated)
            {
                mShapeHasBeenCreated = true;

                mRectangleGeometry = new RectangleGeometry();
                mShape = new Path();
                mShape.Data = mRectangleGeometry;
                if (mStrokeThickness > 0)
                {
                    mShape.Stroke = mStrokeBrush;
                }
                else
                {
                    mDrawThickness = 1;
                    mShape.Stroke = new SolidColorBrush(Colors.Black);
                    mShape.StrokeDashArray.Add(2);
                    mShape.StrokeDashArray.Add(2);
                }
                if (mUseFill)
                {
                    mShape.Fill = mFillBrush;
                }

                this.Children.Add(mShape);
            }
            else
            {
                if (mCurrentState == CalloutState.rectangle)
                {
                    double tempThickness = Math.Min(mDrawThickness, Math.Min(this.Width, this.Height) / 2);
                    mShape.StrokeThickness = tempThickness;

                    double minX = Math.Min(mDownPoint.X, mDragPoint.X) - mPageCropOnClient.x1;
                    double minY = Math.Min(mDownPoint.Y, mDragPoint.Y) - mPageCropOnClient.y1;
                    double xDist = Math.Abs(mDownPoint.X - mDragPoint.X);
                    double yDist = Math.Abs(mDownPoint.Y - mDragPoint.Y);

                    mRectangleGeometry.Rect = new UIRect(minX, minY, xDist, yDist);
                }
                else if (mCurrentState == CalloutState.point2)
                {
                    mCalloutPathFigure.Segments.Clear();
                    mSecondCalloutPoint.X = mDragPoint.X - mPageCropOnClient.x1;
                    mSecondCalloutPoint.Y = mDragPoint.Y - mPageCropOnClient.y1;
                    mFirstCalloutPoint = GetFirstCalloutPoint();
                    mCalloutPathFigure.StartPoint = mFirstCalloutPoint;
                    mCalloutPathFigure.Segments.Add(new LineSegment() 
                    { Point = new UIPoint(mSecondCalloutPoint.X, mSecondCalloutPoint.Y) });
                }
                else if (mCurrentState == CalloutState.point3)
                {
                    mThirdCalloutPoint.X = mDragPoint.X - mPageCropOnClient.x1;
                    mThirdCalloutPoint.Y = mDragPoint.Y - mPageCropOnClient.y1;

                    CalcArrow();

                    PathFigureCollection arrowPoints = new PathFigureCollection();

                    // arrow head
                    PathFigure a_head = new PathFigure();
                    a_head.StartPoint = aPt1;
                    a_head.Segments.Add(new LineSegment() { Point = mThirdCalloutPoint });
                    a_head.Segments.Add(new LineSegment() { Point = aPt2 });
                    arrowPoints.Add(a_head);

                    // arrow shaft
                    PathFigure a_shaft = new PathFigure();
                    a_shaft.StartPoint = aPt3;
                    a_shaft.Segments.Add(new LineSegment() { Point = mSecondCalloutPoint });
                    a_shaft.Segments.Add(new LineSegment() { Point = mFirstCalloutPoint });
                    arrowPoints.Add(a_shaft);

                    (mCalloutShape.Data as PathGeometry).Figures = arrowPoints;
                }
            }
        }

        protected void CreateTextPopup()
        {
            ShouldHandleKeyEvents = false;

            if (2 * mTextRect.Width() <= mDrawThickness || 2 * mTextRect.Height() <= mDrawThickness)
            {
                return;
            }

            // create the control to host the text canvas
            mTextPopupBorder = new Border();
            mTextPopupBorder.Width = mTextRect.Width();
            mTextPopupBorder.Height = mTextRect.Height();
            mTextPopupBorder.Opacity = mOpacity;
            mTextPopupBorder.BorderThickness = new System.Windows.Thickness(mDrawThickness);
            mTextPopupBorder.BorderBrush = mStrokeBrush;

            // create text box
            mTextBox = new TextBox();
            mTextBox.TextWrapping = System.Windows.TextWrapping.Wrap;
            mTextBox.AcceptsReturn = true;
            mTextBox.AcceptsTab = true;
            mTextBox.AutoWordSelection = true;

            mTextBox.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(TextBoxScrollChanged), true);

            mTextBox.Foreground = mTextBrush;
            if (mUseFill)
            {
                mTextBox.Background = mFillBrush;
            }
            else
            {
                mTextBox.Background = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));
                mTextBox.CaretBrush = mTextBrush;
            }
            mTextBox.FontSize = mFontSize * mPDFView.GetZoom();
            mTextBox.FontFamily = new FontFamily("Arial");
            mTextBox.Text = "";
            mTextBox.Loaded += mTextBox_Loaded;
            mTextBox.KeyDown += mTextBox_KeyDown;

            mTextPopupBorder.SetValue(Canvas.LeftProperty, mTextRect.x1 - mPageCropOnClient.x1);
            mTextPopupBorder.SetValue(Canvas.TopProperty, mTextRect.y1 - mPageCropOnClient.y1);

            mTextPopupBorder.Child = mTextBox;
            this.Children.Add(mTextPopupBorder);
            mIsTextPopupOpen = true;
        }

        private void TextBoxScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            e.Handled = true; // this will prevent the entire view from scrolling to accommodate the text box
        }

        void mTextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == System.Windows.Input.Key.Escape)
            {
                mCancelTextAnnot = true;
                CloseTextPopup();
            }
        }

        void mTextBox_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            mTextBox.Focus();
        }

        private bool _InDelayRemoveList = false;
        private void CloseTextPopup()
        {
            ShouldHandleKeyEvents = true;
            if (mTextBox != null && mTextBox.Text.Trim().Length > 0)
            {

                try
                {
                    CreateTextAnnot();

                    mPDFView.DocLock(true);
                    DateTime now = DateTime.Now;
                    Date date = new Date((short)now.Year, (byte)now.Month, (byte)now.Day, (byte)now.Hour, (byte)now.Minute, (byte)now.Second);
                    mAnnot.SetDate(date);
                    mAnnot.SetContents(mTextBox.Text);
                    mAnnot.RefreshAppearance();
                }
                catch (System.Exception)
                {
                }
                finally
                {
                    mPDFView.DocUnlock();
                }
                mToolManager.RaiseAnnotationAddedEvent(mAnnot);

                mPDFView.Update(mAnnot, mDownPageNumber);
            }
            if (mCancelTextAnnot)
            {
                if (mTextPopupBorder != null)
                {
                    if (this.Children.Contains(mTextPopupBorder))
                    {
                        this.Children.Remove(mTextPopupBorder);
                    }
                    mTextPopupBorder = null;
                    mTextBox = null;
                }
                this.Children.Clear();
            }
            else
            {
                _InDelayRemoveList = true;
                mToolManager.DelayRemoveTimers.Add(new PDFViewWPFToolsCS2013.Utilities.DelayRemoveTimer(mViewerCanvas, this, this, mDownPageNumber));
            }
            mIsTextPopupOpen = false;
        }

        protected void CreateTextAnnot()
        {
            double sx = mPDFView.GetHScrollPos();
            double sy = mPDFView.GetVScrollPos();

            double xPos = mTextRect.x1 - sx;
            double yPos = mTextRect.y1 - sy;

            double x1 = mTextRect.x1 - sx;
            double y1 = mTextRect.y1 - sy;
            double x2 = mTextRect.x2 - sx;
            double y2 = mTextRect.y2 - sy;

            // we need to give the text box a minimum size, to make sure that at least some text fits in it.
            double marg = 50;
            if (x2 - x1 < marg)
            {
                x2 = x1 + marg;
            }
            if (y2 - y1 < marg)
            {
                y2 = y1 + marg;
            }

            double cpx1 = mFirstCalloutPoint.X + mPageCropOnClient.x1 - sx;
            double cpy1 = mFirstCalloutPoint.Y + mPageCropOnClient.y1 - sy;

            double cpx2 = mSecondCalloutPoint.X + mPageCropOnClient.x1 - sx;
            double cpy2 = mSecondCalloutPoint.Y + mPageCropOnClient.y1 - sy;

            double cpx3 = mThirdCalloutPoint.X + mPageCropOnClient.x1 - sx;
            double cpy3 = mThirdCalloutPoint.Y + mPageCropOnClient.y1 - sy;

            double minX = Math.Min(Math.Min(x1, cpx1), Math.Min(cpx2, cpx3));
            double maxX = Math.Max(Math.Max(x2, cpx1), Math.Max(cpx2, cpx3));
            double minY = Math.Min(Math.Min(y1, cpy1), Math.Min(cpy2, cpy3));
            double maxY = Math.Max(Math.Max(y2, cpy1), Math.Max(cpy2, cpy3));

            double ax1 = minX;
            double ax2 = maxX;
            double ay1 = minY;
            double ay2 = maxY;

            mPDFView.ConvScreenPtToPagePt(ref x1, ref y1, mDownPageNumber);
            mPDFView.ConvScreenPtToPagePt(ref x2, ref y2, mDownPageNumber);

            mPDFView.ConvScreenPtToPagePt(ref ax1, ref ay1, mDownPageNumber);
            mPDFView.ConvScreenPtToPagePt(ref ax2, ref ay2, mDownPageNumber);

            try
            {
                mPDFView.DocLock(true);

                pdftron.PDF.Page.Rotate pr = mPDFView.GetDoc().GetPage(mDownPageNumber).GetRotation();
                double xDist, yDist;
                if (pr == pdftron.PDF.Page.Rotate.e_90 || pr == pdftron.PDF.Page.Rotate.e_270)
                {
                    xDist = Math.Abs(y1 - y2);
                    yDist = Math.Abs(x1 - x2);
                }
                else
                {
                    xDist = Math.Abs(x1 - x2);
                    yDist = Math.Abs(y1 - y2);
                }
                PDFRect annotRect = new pdftron.PDF.Rect(ax1, ay1, ax2, ay2);
                PDFRect textRect = new pdftron.PDF.Rect(x1, y1, x2, y2);
                pdftron.PDF.Page page4rect = mPDFView.GetDoc().GetPage(mDownPageNumber);

                pdftron.PDF.Annots.FreeText textAnnot = pdftron.PDF.Annots.FreeText.Create(mPDFView.GetDoc().GetSDFDoc(), annotRect);

                annotRect.Normalize();
                textRect.Normalize();

                // set text color
                double red = mTextBrush.Color.R / 255.0;
                double green = mTextBrush.Color.G / 255.0;
                double blue = mTextBrush.Color.B / 255.0;
                ColorPt color = new ColorPt(red, green, blue);
                textAnnot.SetTextColor(color, 3);

                // Set background color if necessary
                if (mUseFill)
                {
                    red = mFillBrush.Color.R;
                    green = mFillBrush.Color.G;
                    blue = mFillBrush.Color.B;
                    color = new ColorPt(red / 255.0, green / 255.0, blue / 255.0);
                    textAnnot.SetColor(color, 3);
                }

                Annot.BorderStyle bs = textAnnot.GetBorderStyle();
                bs.width = mStrokeThickness;
                textAnnot.SetBorderStyle(bs);
                red = mStrokeBrush.Color.R;
                green = mStrokeBrush.Color.G;
                blue = mStrokeBrush.Color.B;
                color = new ColorPt(red / 255.0, green / 255.0, blue / 255.0);
                textAnnot.SetLineColor(color, 3);

                mPDFView.ConvScreenPtToPagePt(ref cpx1, ref cpy1, mDownPageNumber);
                PDFPoint cp1 = new PDFPoint(cpx1, cpy1);

                mPDFView.ConvScreenPtToPagePt(ref cpx2, ref cpy2, mDownPageNumber);
                PDFPoint cp2 = new PDFPoint(cpx2, cpy2);

                mPDFView.ConvScreenPtToPagePt(ref cpx3, ref cpy3, mDownPageNumber);
                PDFPoint cp3 = new PDFPoint(cpx3, cpy3);

                textAnnot.SetCalloutLinePoints(cp3, cp2, cp1);
                textAnnot.SetEndingStyle(Annots.Line.EndingStyle.e_OpenArrow);

                textAnnot.SetPadding(new PDFRect(textRect.x1 - annotRect.x1, textRect.y1 - annotRect.y1, annotRect.x2 - textRect.x2, annotRect.y2 - textRect.y2));

                // set appearance and contents
                textAnnot.SetOpacity(mOpacity);
                textAnnot.SetFontSize(mFontSize);

                pdftron.PDF.Page page = mPDFView.GetDoc().GetPage(mDownPageNumber);
                page.AnnotPushBack(textAnnot);
                textAnnot.SetRotation(Page.RotationToDegree(Page.AddRotations(pr, mPDFView.GetRotation())));
                textAnnot.RefreshAppearance();

                mAnnot = textAnnot;
            }
            catch (Exception)
            {
            }
            finally
            {
                mPDFView.DocUnlock();
            }
        }

        protected Rect GetRectUnion(Rect r1, Rect r2)
        {
            Rect rectUnion = new Rect();
            rectUnion.x1 = Math.Min(r1.x1, r2.x1);
            rectUnion.y1 = Math.Min(r1.y1, r2.y1);
            rectUnion.x2 = Math.Max(r1.x2, r2.x2);
            rectUnion.y2 = Math.Max(r1.y2, r2.y2);
            return rectUnion;
        }

        protected void Reset()
        {
            mIsDrawing = false;
            mShapeHasBeenCreated = false;
            CloseTextPopup();
            EndCurrentTool(ToolManager.ToolType.e_pan);
        }

        #region Utility Functions

        /// <summary>
        /// Assumes we have mSecondCalloutPoint and a mTextRect
        /// </summary>
        /// <returns></returns>
        private UIPoint GetFirstCalloutPoint()
        {
            UIPoint pointOnLine = new UIPoint();

            PDFRect drawingTextRect = new PDFRect(mTextRect.x1 - mPageCropOnClient.x1, mTextRect.y1 - mPageCropOnClient.y1,
                mTextRect.x2 - mPageCropOnClient.x1, mTextRect.y2 - mPageCropOnClient.y1);
            UIPoint center = new UIPoint((drawingTextRect.x1 + drawingTextRect.x2) / 2, (drawingTextRect.y1 + drawingTextRect.y2) / 2);
            double dx = mSecondCalloutPoint.X - center.X;
            double dy = mSecondCalloutPoint.Y - center.Y;

            if (Math.Abs(dx) < 0.01)
            {
                dx = 0;
                dy = dy > 0 ? 1 : -1;
            }
            else if (Math.Abs(dy) < 0.01)
            {
                dx = dx > 0 ? 1 : -1;
                dy = 0;
            }
            else
            {
                double len = Math.Sqrt((dx * dx) + (dy * dy));
                dx = dx / len;
                dy = dy / len;
            }

            double xDist = drawingTextRect.x2 - center.X;
            double yDist = drawingTextRect.y2 - center.Y;

            if (xDist * Math.Abs(dy) < yDist * Math.Abs(dx)) // this means we intersect top or bottom first
            {
                if (dx > 0)
                {
                    pointOnLine.X = drawingTextRect.x2;
                }
                else
                {
                    pointOnLine.X = drawingTextRect.x1;
                }
                pointOnLine.Y = center.Y + (xDist / Math.Abs(dx)) * dy;
            }
            else
            {
                if (dy > 0)
                {
                    pointOnLine.Y = drawingTextRect.y2;
                }
                else
                {
                    pointOnLine.Y = drawingTextRect.y1;
                }
                pointOnLine.X = center.X + (yDist / Math.Abs(dy)) * dx;
            }

            return pointOnLine;
        }

        private void CalcArrow()
        {
            // aPt1 and aPt2 are the ends of the two lines forming the arrow head.
            double dx = mSecondCalloutPoint.X - mThirdCalloutPoint.X;
            double dy = mSecondCalloutPoint.Y - mThirdCalloutPoint.Y;
            double len = (dx * dx) + (dy * dy);

            if (len != 0)
            {
                len = Math.Sqrt(len);
                dx /= len;
                dy /= len;

                double dx1 = (dx * mCos) - (dy * mSin);
                double dy1 = (dy * mCos) + (dx * mSin);
                aPt1.X = (mArrowHeadLength * dx1) + mThirdCalloutPoint.X;
                aPt1.Y = (mArrowHeadLength * dy1) + mThirdCalloutPoint.Y;


                double dx2 = (dx * mCos) + (dy * mSin);
                double dy2 = (dy * mCos) - (dx * mSin);
                aPt2.X = (mArrowHeadLength * dx2) + mThirdCalloutPoint.X;
                aPt2.Y = (mArrowHeadLength * dy2) + mThirdCalloutPoint.Y;

                // offset the top of the shaft by mDrawThickness, so that it's thickness doesn't blunt the tip.
                // mDrawThickness works because we have exactly 30 degree offset for the arrows.
                aPt3.X = (mDrawThickness * dx) + mThirdCalloutPoint.X;
                aPt3.Y = (mDrawThickness * dy) + mThirdCalloutPoint.Y;
            }
        }

        #endregion Utility Functions
    }
}
