using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using pdftron.PDF;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Shapes;
using System.Diagnostics;

using UIPoint = System.Windows.Point;
using System.Windows.Threading;
using PDFViewWPFToolsCS2013.Utilities;

namespace pdftron.PDF.Tools
{
    
    public delegate void AnnotationModificationHandler(Annot annotation);
    public delegate void AnnotationSelectionChanged();

    public delegate void ToolModeChangedHandler(ToolManager.ToolType newToolType);


    /// <summary>
    /// This is the main class that manages all the tools that interact directly with the viewer through user input.
    /// Generally, this class should be the only point at which a user should interact with the tools library,
    /// although there are Exceptions, such as when the FreeHand tool is used to draw shapes with multiple strokes.
    /// </summary>
    public class ToolManager : IDisposable
    {
        /// <summary>
        /// The various types of tools available for use.
        /// </summary>
        public enum ToolType
        {
            e_none, e_pan, e_annot_edit, e_line_create, e_arrow_create,
            e_rect_create, e_oval_create, e_ink_create, e_callout_create, e_text_annot_create, e_link_action,
            e_text_select, e_text_select_rectangular, e_form_fill, e_sticky_note_create,
            e_text_highlight, e_text_underline, e_text_strikeout, e_text_squiggly,e_line_edit, e_polyline_create,
            e_polygon_create,
        };



        private bool mSuppressContextMenu = false;
        /// <summary>
        /// Sets whether or not the tool should bring up a context menu on right clicks.
        /// Use this in conjugation with PDFViewWPF.PreviewMouseRightButtonUp in order to
        /// determine at runtime whether or not to show the menu.
        /// </summary>
        public bool SuppressContextMenu
        {
            set { mSuppressContextMenu = value; }
            internal get { return mSuppressContextMenu; }
        }

        private bool mSuppressSelectedTextHighlighting = false;
        /// <summary>
        /// Gets or sets whether the PDFViewWPFTools will highlight selected text.
        /// This can be useful in order to turn off text selection during text searching or similar
        /// </summary>
        public bool SuppressSelectedTextHighlighting
        {
            set
            {
                if (mSuppressSelectedTextHighlighting != value)
                {
                    mSuppressSelectedTextHighlighting = value;
                    if (mSuppressSelectedTextHighlighting && CurrentTool != null)
                    {
                        CurrentTool.ClearAllTextHighlights();
                    }
                }
            }
            get { return mSuppressSelectedTextHighlighting; }
        }

        private bool mTextSelectCanClickLinks = true;
        /// <summary>
        /// Gets or sets whether the text select tool can click links.
        /// </summary>
        public bool TextSelectCanClickLinks
        {
            get { return mTextSelectCanClickLinks; }
            set { mTextSelectCanClickLinks = value; }
        }

        internal ToolType mToolTypeAfterToolChange = ToolType.e_pan;

        private bool mIsRightToLeftLanguage = false;
        /// <summary>
        /// Sets whether or not the language is right to left.
        /// </summary>
        public bool IsRightToLeftLanguage
        {
            internal get { return mIsRightToLeftLanguage; }
            set { mIsRightToLeftLanguage = value; }
        }

        private List<Annot> mCurrentCopiedAnnotations;
        /// <summary>
        /// Gets a list of currently copied annotations.
        /// </summary>
        public List<Annot> CurrentCopiedAnnotations
        {
            get { return mCurrentCopiedAnnotations; }
        }

        private NoteManager mNoteManager;
        /// <summary>
        /// Gets the class that displays and manages the notes for various annotations.
        /// </summary>
        internal NoteManager NoteManager
        {
            get { return mNoteManager; }
        }

        private PDFViewWPF mPDFView;
        private Tool mCurrentTool;
        /// <summary>
        /// Returns the tool that is currently active.
        /// </summary>
        public Tool CurrentTool
        {
            get { return mCurrentTool; }
        }

        private Canvas mAnnotationCanvas;
        /// <summary>
        /// Gets the canvas on which the tools draw temporary annotation shapes, adds widgets for form filling, and more.
        /// </summary>
        public Canvas AnnotationCanvas
        {
            get { return mAnnotationCanvas; }
        }

        private Canvas mTextSelectionCanvas;
        /// <summary>
        /// Gets the canvas on which tools draw text selection.
        /// </summary>
        public Canvas TextSelectionCanvas
        {
            get { return mTextSelectionCanvas; }
        }

        /// <summary>
        /// Returns the currently selected annotations.
        /// </summary>
        public List<Annot> SelectedAnnotations
        {
            get
            {
                AnnotEdit editTool = CurrentTool as AnnotEdit;
                if (editTool != null)
                {
                    return editTool.SelectedAnnotations;
                }
                return new List<Annot>();
            }
        }
 
        /// <summary>
        /// This event is fired every time the tool mode is changed.
        /// </summary>
        public event ToolModeChangedHandler ToolModeChanged;

        /// <summary>
        /// This event is fired when an annotation has been added to a document
        /// </summary>
        public event AnnotationModificationHandler AnnotationAdded;

        /// <summary>
        /// This event is raised whenever an annotation on the current document has been edited
        /// </summary>
        public event AnnotationModificationHandler AnnotationEdited;

        /// <summary>
        /// This event is raised whenever an annotation has been deleted from the document
        /// </summary>
        public event AnnotationModificationHandler AnnotationRemoved;

        /// <summary>
        /// This event is raised when the selected annotations change
        /// </summary>
        public event AnnotationSelectionChanged SelectedAnnotationsChanged;

        /// <summary>
        /// This event is raised whenever an annotation on the current document has been edited
        /// </summary>
        public event AnnotationModificationHandler AnnotationNoteChanged;

        private bool _IsEnabled = true;
        /// <summary>
        /// Gets or sets whether the tools respond to interaction from the PDFViewWPF
        /// </summary>
        public bool IsEnabled { get { return _IsEnabled; } set { _IsEnabled = value; } }

        //////////////////////////////////////////////////////////////////////////
        // Generating click (tap) events
        protected bool mShallClick = false;
        protected UIPoint mClickPoint;
        protected const double CLICK_THRESHOLD = 4;


        //////////////////////////////////////////////////////////////////////////
        // Touch interaction
        private IList<int> mTouchIDs;

        /// <summary>
        /// A list of touch ID's currently on the screen
        /// </summary>
        public IList<int> TouchIDs
        {
            get 
            {
                if (mTouchIDs == null)
                {
                    mTouchIDs = new List<int>();
                }
                return mTouchIDs; 
            }
        }

        // long press and tap
        public double TOUCH_PRESS_DIST_THRESHOLD = 25;
        public double LONG_PRESS_TIME_THRESHOLD = 0.8;
        public double TAP_TIME_THRESHOLD = 0.3;
        private DateTime _LongPressStartTime;
        private UIPoint _LongPressDownPoint;
        private bool _TouchPressAborted; // too much movement, or a second finger.

        // text selection scroll speeds when dragging to edge.
        // the speed with which we scroll
        public double TEXT_SELECT_SCROLL_SPEED_X = 20;
        public double TEXT_SELECT_SCROLL_SPEED_Y = 50;
        // speed increases linearly if within this margin
        public double TEXT_SELECT_SCROLL_MARGIN_X = 4;
        public double TEXT_SELECT_SCROLL_MARGIN_Y = 50;
        // once margin is passed, increase speed by factor
        public double TEXT_SELECT_SCROLL_SPEED_MULTIPLIER_IF_POST_MARGIN_X = 8;
        public double TEXT_SELECT_SCROLL_SPEED_MULTIPLIER_IF_POST_MARGIN_Y = 4;

        /// <summary>
        /// Creates a ToolManager that will attach to view and provide interactive behavior
        /// </summary>
        /// <param name="view">The PDFViewWPF to attach to the ToolManager</param>
        public ToolManager(PDFViewWPF view)
        {
            mPDFView = view;
            mCurrentCopiedAnnotations = new List<Annot>();
            mAnnotationCanvas = new Canvas();

            SubscribeToolManager();
            Canvas viewerCanvas = mPDFView.GetCanvas();
            viewerCanvas.Children.Add(mAnnotationCanvas);
            System.Windows.Data.Binding binder = new System.Windows.Data.Binding("ActualWidth");
            binder.Source = viewerCanvas;
            mAnnotationCanvas.SetBinding(Canvas.WidthProperty, binder);
            binder = new System.Windows.Data.Binding("ActualHeight");
            binder.Source = viewerCanvas;
            mAnnotationCanvas.SetBinding(Canvas.HeightProperty, binder);

            mTextSelectionCanvas = new Canvas();
            viewerCanvas.Children.Add(mTextSelectionCanvas);

            mNoteManager = new NoteManager(mPDFView, this);

            mDelayRemoveTimers = new List<DelayRemoveTimer>();

            CreateTool(ToolType.e_pan, null);
        }

        /// <summary>
        /// Subscribes the toolmanager to any events it needs.
        /// </summary>
        private void SubscribeToolManager()
        {
            Canvas drawCanvas = mPDFView.GetCanvas();

            mPDFView.MouseLeftButtonDown += PDFView_MouseLeftButtonDown;
            mPDFView.MouseRightButtonDown += PDFView_MouseRightButtonDown;
            mPDFView.MouseLeftButtonUp += PDFView_MouseLeftButtonUp;
            mPDFView.MouseRightButtonUp += PDFView_MouseRightButtonUp;
            mPDFView.MouseMove += PDFView_MouseMove;

            mPDFView.PreviewMouseWheel += PDFView_PreviewMouseWheel;
            mPDFView.MouseWheel += PDFView_MouseWheel;

            mPDFView.MouseEnter += PDFView_MouseEnter;
            mPDFView.MouseLeave += PDFView_MouseLeave;

            mPDFView.PreviewKeyDown += PDFView_KeyDown;
            mPDFView.PreviewKeyUp += PDFView_KeyUp;

            mPDFView.PreviewMouseDoubleClick += PDFView_PreviewMouseDoubleClick;
            mPDFView.MouseDoubleClick += PDFView_MouseDoubleClick;

            mPDFView.CurrentZoomChanged += PDFView_CurrentZoomChanged;
            mPDFView.LayoutChanged += PDFView_LayoutChanged;
            mPDFView.CurrentPageNumberChanged += PDFView_CurrentPageNumberChanged;
            mPDFView.CurrentScrollChanged += PDFView_CurrentScrollChanged;
            //mPDFView.On

            mPDFView.PreviewMouseRightButtonUp += PDFView_PreviewMouseRightButtonUp;

            // touch events
            mPDFView.TouchDown += mPDFView_TouchDown;
            mPDFView.TouchMove += mPDFView_TouchMove;
            mPDFView.TouchUp += mPDFView_TouchUp;
            mPDFView.LostTouchCapture += mPDFView_LostTouchCapture;
            //mPDFView.Touch

            mPDFView.PreviewMouseDown += mPDFView_PreviewMouseDown;
            mPDFView.PreviewMouseMove += mPDFView_PreviewMouseMove;
            mPDFView.PreviewMouseUp += mPDFView_PreviewMouseUp;

            mPDFView.OnRenderFinished += mPDFView_OnRenderFinished;
        }


        void mPDFView_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            Evaluate(e);
        }

        void mPDFView_PreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            Evaluate(e);
        }

        void mPDFView_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            Evaluate(e);
        }

        private void Evaluate(System.Windows.Input.MouseEventArgs e)
        {
            if (e.StylusDevice != null)
            {
                e.Handled = true;
            }
        }

        void mPDFView_ManipulationStarting(object sender, System.Windows.Input.ManipulationStartingEventArgs e)
        {
        }

        void mPDFView_ManipulationStarted(object sender, System.Windows.Input.ManipulationStartedEventArgs e)
        {
        }

        void mPDFView_PreviewTouchDown(object sender, System.Windows.Input.TouchEventArgs e)
        {
        }


        /// <summary>
        /// Unsubscribes the tool manager.
        /// </summary>
        private void UnsubscribeToolManager()
        {
            mPDFView.MouseLeftButtonDown -= PDFView_MouseLeftButtonDown;
            mPDFView.MouseRightButtonDown -= PDFView_MouseRightButtonDown;
            mPDFView.MouseLeftButtonUp -= PDFView_MouseLeftButtonUp;
            mPDFView.MouseRightButtonUp -= PDFView_MouseRightButtonUp;
            mPDFView.MouseMove -= PDFView_MouseMove;

            mPDFView.MouseEnter -= PDFView_MouseEnter;
            mPDFView.MouseLeave -= PDFView_MouseLeave;

            mPDFView.PreviewMouseWheel -= PDFView_PreviewMouseWheel;
            mPDFView.MouseWheel -= PDFView_MouseWheel;

            mPDFView.PreviewKeyDown -= PDFView_KeyDown;
            mPDFView.PreviewKeyUp -= PDFView_KeyUp;

            mPDFView.PreviewMouseDoubleClick -= PDFView_PreviewMouseDoubleClick;
            mPDFView.MouseDoubleClick -= PDFView_MouseDoubleClick;

            mPDFView.CurrentZoomChanged -= PDFView_CurrentZoomChanged;
            mPDFView.LayoutChanged -= PDFView_LayoutChanged;
            mPDFView.CurrentPageNumberChanged -= PDFView_CurrentPageNumberChanged;
            mPDFView.CurrentScrollChanged -= PDFView_CurrentScrollChanged;

            mPDFView.PreviewMouseRightButtonUp -= PDFView_PreviewMouseRightButtonUp;

            // touch events
            mPDFView.TouchDown -= mPDFView_TouchDown;
            mPDFView.TouchMove -= mPDFView_TouchMove;
            mPDFView.TouchUp -= mPDFView_TouchUp;

            mPDFView.PreviewMouseDown -= mPDFView_PreviewMouseDown;
            mPDFView.PreviewMouseMove -= mPDFView_PreviewMouseMove;
            mPDFView.PreviewMouseUp -= mPDFView_PreviewMouseUp;

            mPDFView.OnRenderFinished -= mPDFView_OnRenderFinished;

            CleanUpTimers();
        }


        /// <summary>
        /// Creates a new tool of type mode
        /// </summary>
        /// <param name="mode">The type of tool to create</param>
        /// <returns>The newly created tool</returns>
        public Tool CreateTool(ToolType mode)
        {
            return CreateTool(mode, null);
        }

        /// <summary>
        /// Creates a new tool of type mode
        /// </summary>
        /// <param name="mode">The type of tool to create</param>
        /// <param name="current_tool">The current tool that is used. This will transfer some information to the new tool. </param>
        /// <returns>The newly created tool</returns>
        public Tool CreateTool(ToolType mode, Tool current_tool)
        {
            return CreateTool(mode, current_tool, false);
        }

        /// <summary>
        /// Creates a new tool of type mode
        /// </summary>
        /// <param name="mode">The type of tool to create</param>
        /// <param name="current_tool">The current tool that is used. This will transfer some information to the new tool. </param>
        /// <param name="toolIsPersistent">True if the tool will persists (e.g. you can draw multiple lines without reselecting the tool.
        /// false (default) otherwise.</param>
        /// <returns>The newly created tool</returns>
        public Tool CreateTool(ToolType mode, Tool current_tool, bool toolIsPersistent)
        {
            Tool t = null;

            switch (mode)
            {
                case ToolType.e_pan:
                    t = new Pan(mPDFView, this);
                    break;
                case ToolType.e_annot_edit:
                    t = new AnnotEdit(mPDFView, this);
                    break;
                case ToolType.e_line_create:
                    t = new LineCreate(mPDFView, this);
                    break;
                case ToolType.e_polyline_create:
                    t = new PolylineCreate(mPDFView, this);
                    break;
                case ToolType.e_polygon_create:
                    t = new PolygonCreate(mPDFView, this);
                    break;
                case ToolType.e_arrow_create:
                    t = new ArrowCreate(mPDFView, this);
                    break;
                case ToolType.e_rect_create:
                    t = new RectCreate(mPDFView, this);
                    break;
                case ToolType.e_oval_create:
                    t = new OvalCreate(mPDFView, this);
                    break;
                case ToolManager.ToolType.e_ink_create:
                    t = new FreehandCreate(mPDFView, this);
                    break;
                case ToolManager.ToolType.e_callout_create:
                    t = new CalloutCreate(mPDFView, this);
                    break;
                case ToolManager.ToolType.e_text_annot_create:
                    t = new FreeTextCreate(mPDFView, this);
                    break;
                case ToolManager.ToolType.e_link_action:
                    t = new LinkAction(mPDFView, this);
                    break;
                case ToolType.e_text_select:
                    t = new TextSelectStructural(mPDFView, this);
                    break;
                case ToolType.e_text_select_rectangular:
                    t = new TextSelectRectangular(mPDFView, this);
                    break;
                case ToolType.e_form_fill:
                    t = new FormFill(mPDFView, this);
                    break;
                case ToolType.e_sticky_note_create:
                    t = new StickyNoteCreate(mPDFView, this);
                    break;
                case ToolManager.ToolType.e_text_highlight:
                    t = new TextHightlightCreate(mPDFView, this);
                    break;
                case ToolManager.ToolType.e_text_underline:
                    t = new TextUnderlineCreate(mPDFView, this);
                    break;
                case ToolManager.ToolType.e_text_strikeout:
                    t = new TextStrikeoutCreate(mPDFView, this);
                    break;
                case ToolManager.ToolType.e_text_squiggly:
                    t = new TextSquigglyCreate(mPDFView, this);
                    break;
                case ToolManager.ToolType.e_none:
                    t = null;
                    break;
                default:
                    t = new Pan(mPDFView, this);
                    break;
            }

            if (current_tool != null)
            {
                // Transfer some data between tools
                if (t != null)
                {
                    t.Transfer((Tool)current_tool);
                }
                current_tool.OnClose();		//close the old tool; old tool can use this to clean up things.
            }

            // This can happen when the user explicitly creates new tools
            if (mCurrentTool != null && mCurrentTool != current_tool)
            {
                mCurrentTool.OnClose();
            }

            //call a tool's onCreate() function in which the tool can initialize things that require the transferred properties. 
            if (t != null)
            {
                t.OnCreate();
                t.UseSameToolWhenDone = toolIsPersistent;
            }

            mCurrentTool = t;
            if (ToolModeChanged != null)
            {
                ToolModeChanged(CurrentTool.ToolMode);
            }

            if (mode == mToolTypeAfterToolChange)
            {
                mToolTypeAfterToolChange = ToolType.e_pan;
            }

            return t;
        }

        /// <summary>
        /// Tells the tools to remove any highlighted text from text selection
        /// </summary>
        public void ClearSelectedTextHighlighting()
        {
            if (CurrentTool != null)
            {
                CurrentTool.ClearAllTextHighlights();
            }
        }

        public void Close()
        {
            if (mCurrentTool != null)
            {
                mCurrentTool.OnClose();
                mCurrentTool = null;
            }
        }



        #region Annotation Modification

        /// <summary>
        /// Lets the various tools raise the AnnotationAdded event from a unified location.
        /// </summary>
        /// <param name="annot"></param>
        internal void RaiseAnnotationAddedEvent(Annot annot)
        {
            if (AnnotationAdded != null)
            {
                Annot forwardedAnnot = annot;
                mPDFView.DocLockRead();
                try
                {
                    forwardedAnnot = GetDerivedAnnotClass(forwardedAnnot);
                }
                catch (Exception e) { System.Diagnostics.Debug.WriteLine(e); }
                finally
                {
                    mPDFView.DocUnlockRead();
                }
                AnnotationAdded(forwardedAnnot);
            }
        }

        /// <summary>
        /// Lets the various tools raise the AnnotationEdited event from a unified location.
        /// </summary>
        /// <param name="annot"></param>
        internal void RaiseAnnotationEditedEvent(Annot annot)
        {
            if (AnnotationEdited != null)
            {
                Annot forwardedAnnot = annot;
                mPDFView.DocLockRead();
                try
                {
                    forwardedAnnot = GetDerivedAnnotClass(forwardedAnnot);
                }
                catch (Exception e) { System.Diagnostics.Debug.WriteLine(e); }
                finally
                {
                    mPDFView.DocUnlockRead();
                }
                AnnotationEdited(forwardedAnnot);
            }
        }

        /// <summary>
        /// Lets the various tools raise the AnnotationRemoved event from a unified location.
        /// </summary>
        /// <param name="annot"></param>
        internal void RaiseAnnotationRemovedEvent(Annot annot)
        {
            if (AnnotationRemoved != null)
            {
                Annot forwardedAnnot = annot;
                mPDFView.DocLockRead();
                try
                {
                    forwardedAnnot = GetDerivedAnnotClass(forwardedAnnot);
                }
                catch (Exception e) { System.Diagnostics.Debug.WriteLine(e); }
                finally
                {
                    mPDFView.DocUnlockRead();
                }
                AnnotationRemoved(forwardedAnnot);
            }
        }

        /// <summary>
        /// This lets the user AnnotEdit tool raise an event when the selection is changed.
        /// </summary>
        internal void RaiseSelectedAnnotationsChangedEvent()
        {
            if (SelectedAnnotationsChanged != null)
            {
                SelectedAnnotationsChanged();
            }
        }

        /// <summary>
        /// Lets the various tools raise the AnnotationEdited event from a unified location.
        /// </summary>
        /// <param name="annot"></param>
        internal void RaiseAnnotationNoteChangedEvent(Annot annot)
        {
            if (AnnotationNoteChanged != null)
            {
                AnnotationNoteChanged(annot);
            }
        }

        #endregion Annotation Modification


        #region Event Handlers
        //////////////////////////////////////////////////////////////////////////
        // All events that the ToolManager gets follows the same pattern.
        // First, the event is forwarded to the current tool. 
        // Once the current tool is finished, we look to see if the next tool mode
        // is the same as the current tool mode. This gives the current tool a 
        // chance to process the event, and if it detects that another tool should
        // handle this event, it can set the next tool mode to the appropriate
        // tool.
        // This is done for example when the Pan tool handles a MouseLeftButtonDown
        // event and notices a form field at the current cursor location.
        //////////////////////////////////////////////////////////////////////////


        void PDFView_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            mShallClick = true;
            mClickPoint = e.GetPosition(mPDFView);

            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.MouseLeftButtonDownHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void PDFView_MouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.MouseRightButtonDownHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void PDFView_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (mShallClick)
            {
                UIPoint upPoint = e.GetPosition(mPDFView);
                double dist = (upPoint.X - mClickPoint.X) * (upPoint.X - mClickPoint.X)
                    + (upPoint.Y - mClickPoint.Y) * (upPoint.Y - mClickPoint.Y);
                if (dist > CLICK_THRESHOLD)
                {
                    mShallClick = false;
                }

            }
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.MouseMovedHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void PDFView_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (mShallClick)
            {
                HandleMouseClick(sender, new System.Windows.Input.MouseEventArgs(e.MouseDevice, e.Timestamp));
            }
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.MouseLeftButtonUpHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void PDFView_MouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.MouseRightButtonUpHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void PDFView_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.MouseEnterHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void PDFView_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.MouseLeaveHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }




        private void PDFView_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mNoteManager.HandlePreviewMouseWheel(sender, e);
                    if (!e.Handled)
                    {
                        mCurrentTool.PreviewMouseWheelHandler(sender, e);
                        next_tm = mCurrentTool.NextToolMode;
                        if (prev_tm != next_tm)
                        {
                            mCurrentTool = CreateTool(next_tm, mCurrentTool);
                            prev_tm = next_tm;
                        }
                        else
                        {
                            break;
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void PDFView_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.MouseWheelHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
        
        void PDFView_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.KeyDownHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void PDFView_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.KeyUpHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Note, this event is generated by the tool manager
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="mouseEventArgs"></param>
        private void HandleMouseClick(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.MouseClickHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }


        void PDFView_PreviewMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {

        }


        void PDFView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            mShallClick = false;
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.MouseDoubleClickHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }


        void PDFView_CurrentZoomChanged(PDFViewWPF viewer)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.ZoomChangedHandler(viewer, new RoutedEventArgs());
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }


        void PDFView_LayoutChanged(PDFViewWPF viewer)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.LayoutChangedHandler(viewer, new RoutedEventArgs());
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }


        void PDFView_CurrentPageNumberChanged(PDFViewWPF viewer, int currentPage, int totalPages)
        {
            RemoveTimersOnHiddenPages(currentPage);

            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.CurrentPageNumberChangedHandler(viewer, currentPage, totalPages);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }


        void PDFView_CurrentScrollChanged(PDFViewWPF viewer, ScrollChangedEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.CurrentScrollChangedHandler(viewer, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }



        void PDFView_PreviewMouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.PreviewMouseRightButtonUpHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }


        //////////////////////////////////////////////////////////////////////////
        // Touch events

        void mPDFView_TouchDown(object sender, System.Windows.Input.TouchEventArgs e)
        {
            TouchIDs.Add(e.TouchDevice.Id);
            if (TouchIDs.Count == 1)
            {
                _LongPressDownPoint = e.GetTouchPoint(null).Position;
                _LongPressStartTime = DateTime.Now;
                _TouchPressAborted = false;
            }
            else
            {
                _TouchPressAborted = true;
            }
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.TouchDownHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void mPDFView_TouchMove(object sender, System.Windows.Input.TouchEventArgs e)
        {
            UIPoint movePoint = e.GetTouchPoint(null).Position;
            double xDist = movePoint.X - _LongPressDownPoint.X;
            double yDist = movePoint.Y - _LongPressDownPoint.Y;
            _TouchPressAborted = (xDist * xDist) + (yDist * yDist) > TOUCH_PRESS_DIST_THRESHOLD;
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.TouchMoveHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void mPDFView_TouchUp(object sender, System.Windows.Input.TouchEventArgs e)
        {
            if (!_TouchPressAborted)
            {
                _TouchPressAborted = true;
                TimeSpan delay = DateTime.Now.Subtract(_LongPressStartTime);
                if (delay.TotalSeconds > LONG_PRESS_TIME_THRESHOLD)
                {
                    mPDFView_LongPress(sender, e);
                }
                if (delay.TotalSeconds < TAP_TIME_THRESHOLD)
                {
                    mPDFView_Tap(sender, e);
                }
            }
            if (TouchIDs.Contains(e.TouchDevice.Id))
            {
                TouchIDs.Remove(e.TouchDevice.Id);
            }
            if (mCurrentTool != null && !e.Handled && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.TouchUpHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void mPDFView_LostTouchCapture(object sender, System.Windows.Input.TouchEventArgs e)
        {
            if (TouchIDs.Contains(e.TouchDevice.Id))
            {
                TouchIDs.Remove(e.TouchDevice.Id);
            }
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.TouchCaptureLostHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void mPDFView_LongPress(object sender, System.Windows.Input.TouchEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.LongPressHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        void mPDFView_Tap(object sender, System.Windows.Input.TouchEventArgs e)
        {
            if (mCurrentTool != null && _IsEnabled)
            {
                ToolManager.ToolType prev_tm = mCurrentTool.ToolMode;
                ToolManager.ToolType next_tm;
                while (true)
                {
                    mCurrentTool.TapHandler(sender, e);
                    next_tm = mCurrentTool.NextToolMode;
                    if (prev_tm != next_tm)
                    {
                        mCurrentTool = CreateTool(next_tm, mCurrentTool);
                        prev_tm = next_tm;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        #endregion Event Handlers


        #region Handling Annotation Drawing

        private List<DelayRemoveTimer> mDelayRemoveTimers;
        internal IList<DelayRemoveTimer> DelayRemoveTimers { get { return mDelayRemoveTimers; } }

        void mPDFView_OnRenderFinished(PDFViewWPF viewer)
        {
            CleanUpTimers();
        }

        private void RemoveTimersOnHiddenPages(int currentPage)
        {
            pdftron.PDF.PDFViewWPF.PagePresentationMode presentationMode = mPDFView.GetPagePresentationMode();


            if (presentationMode == pdftron.PDF.PDFViewWPF.PagePresentationMode.e_single_page||
                        presentationMode == pdftron.PDF.PDFViewWPF.PagePresentationMode.e_facing||
                        presentationMode == pdftron.PDF.PDFViewWPF.PagePresentationMode.e_facing_cover)
            {
                List<int> pages = new List<int>();
                pages.Add(currentPage);

                if (presentationMode == PDFViewWPF.PagePresentationMode.e_facing)
                {
                    pages.Add(currentPage % 2 == 1 ? currentPage + 1 : currentPage - 1);
                }
                else if (presentationMode == PDFViewWPF.PagePresentationMode.e_facing_cover)
                {
                    pages.Add(currentPage % 2 == 0 ? currentPage + 1 : currentPage - 1);
                }

                List<DelayRemoveTimer> removedTimers = new List<DelayRemoveTimer>();
                foreach (DelayRemoveTimer timer in mDelayRemoveTimers)
                {
                    if (!pages.Contains(timer.PageNumber))
                    {
                        removedTimers.Add(timer);
                        timer.Destroy();
                    }
                }
                foreach (DelayRemoveTimer timer in removedTimers)
                {
                    mDelayRemoveTimers.Remove(timer);
                }
            }

        }

        public void CleanUpTimers()
        {
            foreach (DelayRemoveTimer timer in mDelayRemoveTimers)
            {
                timer.Destroy();
            }
            mDelayRemoveTimers.Clear();
        }

        #endregion Handling Annotation Drawing


        #region Utilities

        /// <summary>
        /// Returns an Annot that is actually the derived type.
        /// E.g. if the annotation is a line, the value returned from this function can be cast to a Line
        /// 
        /// Note: Acquire a readlock before calling this function.
        /// </summary>
        /// <param name="annot">The annot to get the derived version of.</param>
        /// <returns></returns>
        public static Annot GetDerivedAnnotClass(Annot annot)
        {
            Annot returnAnnot = annot;

            switch(annot.GetType())
            {
                case Annot.Type.e_Caret:
                    if (!(annot is Annots.Caret))
                    {
                        returnAnnot = new Annots.Caret(annot);
                    }
                    break;
                case Annot.Type.e_Circle:
                    if (!(annot is Annots.Circle))
                    {
                        returnAnnot = new Annots.Circle(annot);
                    }
                    break;
                case Annot.Type.e_FileAttachment:
                    if (!(annot is Annots.FileAttachment))
                    {
                        returnAnnot = new Annots.FileAttachment(annot);
                    }
                    break;
                case Annot.Type.e_FreeText:
                    if (!(annot is Annots.FreeText))
                    {
                        returnAnnot = new Annots.FreeText(annot);
                    }
                    break;
                case Annot.Type.e_Highlight:
                    if (!(annot is Annots.Highlight))
                    {
                        returnAnnot = new Annots.Highlight(annot);
                    }
                    break;
                case Annot.Type.e_Ink:
                    if (!(annot is Annots.Ink))
                    {
                        returnAnnot = new Annots.Ink(annot);
                    }
                    break;
                case Annot.Type.e_Line:
                    if (!(annot is Annots.Line))
                    {
                        returnAnnot = new Annots.Line(annot);
                    }
                    break;
                case Annot.Type.e_Link:
                    if (!(annot is Annots.Link))
                    {
                        returnAnnot = new Annots.Link(annot);
                    }
                    break;
                case Annot.Type.e_Movie:
                    if (!(annot is Annots.Movie))
                    {
                        returnAnnot = new Annots.Movie(annot);
                    }
                    break;
                case Annot.Type.e_Polygon:
                    if (!(annot is Annots.Polygon))
                    {
                        returnAnnot = new Annots.Polygon(annot);
                    }
                    break;
                case Annot.Type.e_Polyline:
                    if (!(annot is Annots.PolyLine))
                    {
                        returnAnnot = new Annots.PolyLine(annot);
                    }
                    break;
                case Annot.Type.e_Popup:
                    if (!(annot is Annots.Popup))
                    {
                        returnAnnot = new Annots.Popup(annot);
                    }
                    break;
                case Annot.Type.e_Redact:
                    if (!(annot is Annots.Redaction))
                    {
                        returnAnnot = new Annots.Redaction(annot);
                    }
                    break;
                case Annot.Type.e_Stamp:
                    if (!(annot is Annots.RubberStamp))
                    {
                        returnAnnot = new Annots.RubberStamp(annot);
                    }
                    break;
                case Annot.Type.e_Screen:
                    if (!(annot is Annots.Screen))
                    {
                        returnAnnot = new Annots.Screen(annot);
                    }
                    break;
                case Annot.Type.e_Sound:
                    if (!(annot is Annots.Sound))
                    {
                        returnAnnot = new Annots.Sound(annot);
                    }
                    break;
                case Annot.Type.e_Square:
                    if (!(annot is Annots.Square))
                    {
                        returnAnnot = new Annots.Square(annot);
                    }
                    break;
                case Annot.Type.e_Squiggly:
                    if (!(annot is Annots.Squiggly))
                    {
                        returnAnnot = new Annots.Squiggly(annot);
                    }
                    break;
                case Annot.Type.e_StrikeOut:
                    if (!(annot is Annots.StrikeOut))
                    {
                        returnAnnot = new Annots.StrikeOut(annot);
                    }
                    break;
                case Annot.Type.e_Text:
                    if (!(annot is Annots.Text))
                    {
                        returnAnnot = new Annots.Text(annot);
                    }
                    break;
                case Annot.Type.e_Underline:
                    if (!(annot is Annots.Underline))
                    {
                        returnAnnot = new Annots.Underline(annot);
                    }
                    break;
                case Annot.Type.e_Watermark:
                    if (!(annot is Annots.Watermark))
                    {
                        returnAnnot = new Annots.Watermark(annot);
                    }
                    break;
                case Annot.Type.e_Widget:
                    if (!(annot is Annots.Widget))
                    {
                        returnAnnot = new Annots.Widget(annot);
                    }
                    break;
            }
            return returnAnnot;
        }

        #region IDisposable Support
        private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: dispose managed state (managed objects).
                    UnsubscribeToolManager();
                    mPDFView = null;
                }

                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
                // TODO: set large fields to null.

                disposedValue = true;
            }
        }

        // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
        // ~ToolManager() {
        //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        //   Dispose(false);
        // }

        // This code added to correctly implement the disposable pattern.
        public void Dispose()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(true);
            // TODO: uncomment the following line if the finalizer is overridden above.
            // GC.SuppressFinalize(this);
        }
        #endregion

        #endregion
    }
}
