using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;

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

namespace pdftron.PDF.Tools
{
    public class AdvancedShapeCreate: Tool
    {
        protected int START_DRAWING_THRESHOLD = 5;

        protected bool mIsDrawing = false;
        protected UIPoint mDownPoint;
        protected UIPoint mDragPoint;
        protected int mDownPageNumber = -1;
        protected PDFRect mPageCropOnClient;

        // Visuals for shapes
        protected double mStrokeThickness = 1;
        protected double mZoomLevel = 1;
        protected double mDrawThickness;
        protected SolidColorBrush mStrokeBrush;
        protected SolidColorBrush mFillBrush;
        protected double mOpacity = 1.0;
        protected bool mUseStroke = true;
        protected bool mUseFill = false;
        protected bool mShapeHasBeenCreated = false;
        protected double mHalfThickness = 1;

        // Transforms (most shape creation tools will use these two)
        protected TransformGroup mShapeTransform;
        protected ScaleTransform mScaleTransform;
        protected TranslateTransform mTranslateTransform;

        protected bool mMultiTouch = false;

        protected Path mShape;
        protected PathGeometry mPathGeometry;

        protected PathGeometry mPolygonGeometry = new PathGeometry();
        protected PathFigure mFigure = new PathFigure();
        protected UIPoint mFirstPoint = new UIPoint();
        protected List<PDFPoint> mPdfPointList = new List<PDFPoint>();
        protected int mPreviousDrawPointIndex = 0;
        protected bool mCanSelectNewPoint = false;
        protected bool mStartDrawing = false;

        protected bool mMouseClicked = false;

        public AdvancedShapeCreate(PDFViewWPF view, ToolManager manager)
            : base(view, manager)
        {
            mViewerCanvas = mToolManager.AnnotationCanvas;
            DisallowTextSelection();
        }

        internal override void OnCreate()
        {
            // Fetch style data from Settings
            pdftron.PDF.Tools.Utilities.ColorSettings.ToolColor col = pdftron.PDF.Tools.Utilities.ColorSettings.StrokeColor;
            mUseStroke = col.Use;
            mStrokeBrush = new SolidColorBrush(Color.FromArgb(255, col.R, col.G, col.B));

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

            mStrokeThickness = pdftron.PDF.Tools.Properties.Settings.Default.StrokeThickness;

            mOpacity = pdftron.PDF.Tools.Properties.Settings.Default.MarkupOpacity;
        }

        internal override void LayoutChangedHandler(object sender, RoutedEventArgs e)
        {
            HandleResizing();
        }

        internal override void ZoomChangedHandler(object sender, RoutedEventArgs e)
        {
            HandleResizing();
        }

        internal override void MouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e)
        {
            base.MouseLeftButtonDownHandler(sender, e);
            ProcessInputDown(e);
            mViewerCanvas.CaptureMouse();
            mMouseClicked = true;
        }

        public override void TouchDownHandler(object sender, TouchEventArgs e)
        {
            base.TouchDownHandler(sender, e);
            e.Handled = true;
            if (mToolManager.TouchIDs.Count > 1)
            {
                mMultiTouch = true;
                MultiTouchDetected();
                return;
            }
            mMultiTouch = false;
            ProcessInputDown(e);

            mViewerCanvas.CaptureTouch(e.TouchDevice);
        }

        private void ProcessInputDown(InputEventArgs e)
        {
            if (mShape == null)
            {
                PDFDoc doc = mPDFView.GetDoc();
                if (doc == null)
                {
                    mNextToolMode = ToolManager.ToolType.e_pan;
                }

                UIPoint screenPoint = GetPosition(e, mPDFView);
                
                // Ensure we're on a page
                mDownPageNumber = mPDFView.GetPageNumberFromScreenPt(screenPoint.X, screenPoint.Y);
                if (mDownPageNumber < 1)
                {
                    EndCurrentTool(ToolManager.ToolType.e_pan);
                    return;
                }

                // Get the page's bounding box in canvas space
                mPageCropOnClient = BuildPageBoundBoxOnClient(mDownPageNumber);

                mZoomLevel = mPDFView.GetZoom();
                mDrawThickness = mStrokeThickness * mZoomLevel;
                mHalfThickness = mDrawThickness / 2;

                mDownPoint = GetPosition(e, mViewerCanvas);
                CheckBounds(ref mDownPoint);

                mDragPoint = new UIPoint(mDownPoint.X, mDownPoint.Y);

                CreatePath();

                mPreviousDrawPointIndex++;

                LineSegment segment = new LineSegment();
                mFigure.Segments.Add(segment);
                var mFirstPoint = new UIPoint(mDownPoint.X - mPageCropOnClient.x1, mDownPoint.Y - mPageCropOnClient.y1);
                mPdfPointList.Add(new PDFPoint(mFirstPoint.X, mFirstPoint.Y));
                segment.Point = mFirstPoint;
            }
            else
            {
                mDownPoint = GetPosition(e, mViewerCanvas);
                CheckBounds(ref mDownPoint);

                mDragPoint = new UIPoint(mDownPoint.X, mDownPoint.Y);

                var nextPoint = new UIPoint(mDownPoint.X - mPageCropOnClient.x1, mDownPoint.Y - mPageCropOnClient.y1);

                mPreviousDrawPointIndex++;

                mPdfPointList[mPdfPointList.Count - 1].x = nextPoint.X;
                mPdfPointList[mPdfPointList.Count - 1].y = nextPoint.Y;
                (mFigure.Segments[mFigure.Segments.Count - 1] as LineSegment).Point = new UIPoint(nextPoint.X, nextPoint.Y);
            }

        }

        internal override void MouseMovedHandler(object sender, MouseEventArgs e)
        {
            base.MouseMovedHandler(sender, e);
            if (mMouseClicked)
            {
                ProcessInputMove(e);
            }
        }

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

        protected void ProcessInputMove(InputEventArgs e)
        {
            mDragPoint = GetPosition(e, mViewerCanvas);
            CheckBounds(ref mDragPoint);
            CheckScreenBounds(ref mDragPoint);
            if (!mIsDrawing && mDownPageNumber > 0)
            {
                // wait until you're at least START_DRAWING_THRESHOLD distance from the start
                if ((Math.Abs(mDownPoint.X - mDragPoint.X) > START_DRAWING_THRESHOLD)
                    || (Math.Abs(mDownPoint.Y - mDragPoint.Y) > START_DRAWING_THRESHOLD))
                {
                    mIsDrawing = true;
                    mViewerCanvas.Children.Insert(0, this);
                    this.SetValue(Canvas.LeftProperty, mPageCropOnClient.x1);
                    this.SetValue(Canvas.TopProperty, mPageCropOnClient.y1);
                    this.Width = mPageCropOnClient.x2 - mPageCropOnClient.x1;
                    this.Height = mPageCropOnClient.y2 - mPageCropOnClient.y1;
                    this.Opacity = mOpacity;
                    Draw();
                }
            }
            if (mIsDrawing)
            {
                Draw();
            }
        }

        internal override void MouseLeftButtonUpHandler(object sender, MouseButtonEventArgs e)
        {
            mCanSelectNewPoint = true;
            base.MouseLeftButtonUpHandler(sender, e);
            mViewerCanvas.ReleaseMouseCapture();
        }

        internal override void MouseRightButtonDownHandler(object sender, MouseButtonEventArgs e)
        {
            base.MouseRightButtonDownHandler(sender, e);
            mViewerCanvas.ReleaseMouseCapture();
            ProcessEnd(e);
        }

        public override void TouchUpHandler(object sender, System.Windows.Input.TouchEventArgs e)
        {
            mCanSelectNewPoint = true;
            base.TouchUpHandler(sender, e);
            mViewerCanvas.ReleaseTouchCapture(e.TouchDevice);
            ProcessEnd(e);
        }

        /// <summary>
        /// If ctrl is pressed, this will zoom in or out on the PDFViewCtrl.
        /// 
        /// Note: This only works if the PDFViewCtrl is in focus, otherwise, it won't.
        /// The reason is that the modifier keys (ctrl) are hooked up only to the PDFViewWPF to keep the tool more modular.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        internal override void PreviewMouseWheelHandler(object sender, System.Windows.Input.MouseWheelEventArgs e)
        {
            if (mIsDragging)
            {
                e.Handled = true;
            }
            else
            {
                base.PreviewMouseWheelHandler(sender, e);
            }
        }

        private void ProcessEnd(InputEventArgs e)
        {
            if (mIsDrawing)
            {
                CommitAnnotation();
                mToolManager.DelayRemoveTimers.Add(new PDFViewWPFToolsCS2013.Utilities.DelayRemoveTimer(mViewerCanvas, this, this, mDownPageNumber));
            }
            else
            {
                mViewerCanvas.Children.Remove(this);
            }
        }


        //////////////////////////////////////////////////////////////////////////
        // These functions can be extended by the specific tools if the behavior is not desired
        // In some cases, it has to be extended.
        #region Virtual Functions

        /// <summary>
        /// This needs to be implemented by the specific tool to determine what the tool should look like when drawn.
        /// </summary>
        protected virtual void Draw()
        {
            return;
        }


        /// <summary>
        /// This needs to be implemented by the specific tool in order to add the annotation to the document.
        /// </summary>
        protected virtual void Create()
        {
            return;
        }

        public virtual void CommitAnnotation()
        {
            try
            {
                Create();
                EndCurrentTool(ToolManager.ToolType.e_pan);
            }
            catch (Exception ex)
            {

            }
        }

        /// <summary>
        /// Creates a bounding box that is tightly bounding mDownPoint and mDragPoint
        /// </summary>
        /// <returns>A tight PDFRect</returns>
        protected virtual PDFRect GetShapeBBox()
        {
            //computes the bounding box of the rubber band in page space.
            double sx = mPDFView.GetHScrollPos();
            double sy = mPDFView.GetVScrollPos();

            double x1 = mDownPoint.X - sx;
            double y1 = mDownPoint.Y - sy;
            double x2 = mDragPoint.X - sx;
            double y2 = mDragPoint.Y - sy;

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

            pdftron.PDF.Rect rect;
            rect = new pdftron.PDF.Rect(x1, y1, x2, y2);
            rect.Normalize();
            return rect;
        }


        protected virtual 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 virtual void HandleResizing()
        {
            return;
        }


        protected virtual void MultiTouchDetected()
        {
            EndCurrentTool(ToolManager.ToolType.e_pan);
        }

        protected virtual void CreatePath()
        {
            try
            {
                if (!mShapeHasBeenCreated)
                {
                    mShapeHasBeenCreated = true;
                    if (mDrawThickness == 0)
                    {
                        mDrawThickness = 1;
                    }

                    mShape = new Path();
                    mShape.Data = mPolygonGeometry;
                    mFigure.StartPoint = new UIPoint(mDownPoint.X - mPageCropOnClient.x1, mDownPoint.Y - mPageCropOnClient.y1);
                    mPolygonGeometry.Figures.Add(mFigure);
                    mShape.StrokeThickness = mDrawThickness;
                    mShape.Stroke = mStrokeBrush;
                    this.Children.Add(mShape);
                }
            }
            catch (Exception ex)
            {

            }
        }

        #endregion Virtual Functions

        #region Utility Functions

        /// <summary>
        /// Expects a point relative to the canvas, and will ensure that it's not larger than the screen point.
        /// </summary>
        /// <param name="point"></param>
        protected void CheckScreenBounds(ref UIPoint point)
        {
            double sx = mPDFView.GetHScrollPos();
            double sy = mPDFView.GetVScrollPos();
            if (point.X < sx)
            {
                point.X = sx;
            }
            if (point.X > sx + mPDFView.ActualWidth)
            {
                point.X = sx + mPDFView.ActualWidth;
            }
            if (point.Y < sy)
            {
                point.Y = sy;
            }
            if (point.Y > sy + mPDFView.ActualHeight)
            {
                point.Y = sy + mPDFView.ActualHeight;
            }
        }


        /// <summary>
        /// Sets the look of a markup annotation.
        /// </summary>
        /// <param name="annot">The annotation</param>
        /// <param name="hasFill">Whether the annotation has a fill color or not</param>
        protected void SetStyle(pdftron.PDF.Annots.Markup annot, bool hasFill = false)
        {
            double r = mStrokeBrush.Color.R / 255.0;
            double g = mStrokeBrush.Color.G / 255.0;
            double b = mStrokeBrush.Color.B / 255.0;
            ColorPt color = new ColorPt(r, g, b);
            if (!hasFill || mUseStroke)
            {
                annot.SetColor(color, 3);
            }
            else
            {
                annot.SetColor(color, 0); // 0 for transparent
            }
            if (hasFill)
            {
                r = mFillBrush.Color.R / 255.0;
                g = mFillBrush.Color.G / 255.0;
                b = mFillBrush.Color.B / 255.0;
                color = new ColorPt(r, g, b);
                if (mUseFill)
                {
                    annot.SetInteriorColor(color, 3);
                }
                else
                {
                    annot.SetInteriorColor(color, 0); // 0 for transparent
                }
            }
            pdftron.PDF.Annot.BorderStyle bs = annot.GetBorderStyle();
            bs.width = mStrokeThickness;
            annot.SetBorderStyle(bs);
            annot.SetOpacity(mOpacity);
        }

        protected PDFRect GetAdvancedShapeBBox(List<PDFPoint> pointList)
        {
            try
            {
                if ((pointList == null) || (pointList.Count == 0))
                {
                    return new PDFRect(0, 0, 0, 0);
                }

                double minX = Double.MaxValue;
                double minY = Double.MaxValue;
                double maxX = Double.MinValue;
                double maxY = Double.MinValue;

                foreach (PDFPoint point in pointList)
                {
                    if (point.x < minX)
                    {
                        minX = point.x;
                    }

                    if (point.y < minY)
                    {
                        minY = point.y;
                    }

                    if (point.x > maxX)
                    {
                        maxX = point.x;
                    }

                    if (point.y > maxY)
                    {
                        maxY = point.y;
                    }
                }

                // transform the 2 points to page space
                double sx = mPDFView.GetHScrollPos();
                double sy = mPDFView.GetVScrollPos();

                double x1 = minX - sx + mPageCropOnClient.x1;
                double y1 = minY - sy + mPageCropOnClient.y1;
                double x2 = maxX - sx + mPageCropOnClient.x1;
                double y2 = maxY - sy + mPageCropOnClient.y1;

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

                // get a bounding rectangle
                PDFRect rect = new PDFRect(x1, y1, x2, y2);                
                return rect;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        #endregion Utility Functions
    }
}
