// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.


using System.ComponentModel;
using System.Text;
using System.Diagnostics;

namespace System.Windows.Input.Manipulations
{
    /// <summary>
    /// Implements the extrapolation of a manipulation's position, orientation, and average radius.
    /// </summary>
    /// <remarks>
    /// <para>
    /// An <strong>InertiaProcessor2D</strong> object enables your application to extrapolate an 
    /// element's location, orientation, and other properties by simulating real-world behavior. 
    /// </para>
    /// <para>
    /// For instance, when a user moves an element and then releases it, 
    /// the element can continue moving, decelerate, and then slowly stop.
    /// An inertia processor implements this behavior by causing the affine 2-D values 
    /// (origin, scale, translation, and rotation) to change over a specified time at a 
    /// specified deceleration rate.
    /// </para>
    /// <para>
    /// An inertia processor by itself does not cause an element to move and decelerate. Your 
    /// application receives information from an inertia processor and applies the values
    /// as needed to an application-specific element. Typically, an application uses the 
    /// information received from an inertia processor to change the location, size or
    /// orientation of an element.
    /// </para>
    /// <para>
    /// Inertia processing is typically used in conjunction with manipulation processing.
    /// For more information, see the
    /// <strong><see cref="System.Windows.Input.Manipulations.ManipulationProcessor2D"/></strong>
    /// class.
    /// </para>
    /// </remarks>
    public class InertiaProcessor2D
    {
        private const double timestampTicksPerMillisecond = ManipulationProcessor2D.TimestampTicksPerMillisecond;
        private const double millisecondsPerTimestampTick = 1.0 / timestampTicksPerMillisecond;
        private const double millisecondsPerTimestampTickSquared = millisecondsPerTimestampTick * millisecondsPerTimestampTick;

        private const string initialOriginXName = "InitialOriginX";
        private const string initialOriginYName = "InitialOriginY";

        #region Private Fields

#if DEBUG
        // for debugging only to log all manipulation activity
        private StringBuilder log = new StringBuilder();
#endif

        // inital timestamp
        private Int64 initialTimestamp;

        // previous timestamp
        private Int64 previousTimestamp = -1L; // should be any value different than initialTimestamp

        // behaviors
        private InertiaTranslationBehavior2D translationBehavior;
        private InertiaRotationBehavior2D rotationBehavior;
        private InertiaExpansionBehavior2D expansionBehavior;

        // initial scale
        private double initialScale = 1.0;
        private double desiredDisplacement = double.NaN;
        private double desiredDeceleration = double.NaN;

        // initial states
        private InitialState initialTranslationX = new InitialState();
        private InitialState initialTranslationY = new InitialState();
        private InitialState initialOrientation = new InitialState();
        private InitialState initialExpansion = new InitialState();

        // exrapolation states
        private ExtrapolationState translationX;
        private ExtrapolationState translationY;
        private ExtrapolationState orientation;
        private ExtrapolationState expansion;

        // current state of the processors
        private ProcessorState processorState = ProcessorState.NotInitialized;

        #endregion Private Fields


        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="InertiaProcessor2D"/> class.
        /// </summary>
        public InertiaProcessor2D()
        {
            // do not allow expansion be less than 1
            this.initialExpansion.MinBound = 1;
            this.initialExpansion.Value = 1;

            // use property accessors rather than direct member access to
            // ensure that all the proper plumbing is hooked up
            TranslationBehavior = new InertiaTranslationBehavior2D();
            RotationBehavior = new InertiaRotationBehavior2D();
            ExpansionBehavior = new InertiaExpansionBehavior2D();
        }

        #endregion Constructors


        #region Public Properties
        /// <summary>
        /// Gets or sets the x-coordinate for the initial origin, in coordinate units.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The origin point represented by the <strong>InitialOriginX</strong> and
        /// <strong><see cref="InitialOriginY"/></strong>
        /// properties is the average position of all manipulators associated with an element. 
        /// </para>
        /// <para>
        ///  A valid value for <strong>InitialOriginX</strong> is any finite number. 
        ///  The default value is 0.0.
        /// </para>
        /// </remarks>
        public float InitialOriginX
        {
            get
            {
                return (float)this.initialTranslationX.Value;
            }
            set
            {
                CheckNotRunning(initialOriginXName);
                CheckOriginalValue(value, initialOriginXName);
                Reset();
                this.initialTranslationX.Value = value;
            }
        }

        /// <summary>
        /// Gets or sets the y-coordinate for the initial origin, in coordinate units.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The origin point represented by the <strong><see cref="InitialOriginX"/></strong> and
        /// <strong>InitialOriginY</strong>
        /// properties is the average position of all manipulators associated with an element. 
        /// </para>
        /// <para>
        ///  A valid value for <strong>InitialOriginY</strong> is any finite number. 
        ///  The default value is 0.0.
        /// </para>
        /// </remarks>
        public float InitialOriginY
        {
            get
            {
                return (float)this.initialTranslationY.Value;
            }
            set
            {
                CheckNotRunning(initialOriginYName);
                CheckOriginalValue(value, initialOriginYName);
                Reset();
                this.initialTranslationY.Value = value;
            }
        }

        /// <summary>
        /// Gets whether inertia is currently in progress.
        /// </summary>
        /// <example>
        /// <para>
        /// In the following example, an event handler for the 
        /// <strong><see cref="System.Windows.Input.Manipulations.ManipulationProcessor2D.Started">ManipulationProcessor2D.Started</see></strong>
        /// event checks to see if inertia processing is running and, if so, stops it by calling the
        /// <strong><see cref="Complete">InertiaProcessor2D.Complete</see></strong> method.
        /// </para>
        /// <code lang="cs">
        ///  <code source="MPIP\ManipulationAPI\ManipulationItem.xaml.cs" region="OnManipulationStarted"/>
        ///  <code source="MPIP\ManipulationAPI\ManipulationItem.xaml.cs" region="Timestamp"/>
        /// </code>
        /// </example>
        public bool IsRunning
        {
            get { return this.processorState == ProcessorState.Running; }
        }

        /// <summary>
        /// Gets or sets the translation behavior of the inertia processor.
        /// </summary>
        public InertiaTranslationBehavior2D TranslationBehavior
        {
            get { return this.translationBehavior; }
            set
            {
                SetBehavior<InertiaTranslationBehavior2D>(
                    ref this.translationBehavior,
                    value,
                    OnTranslationBehaviorChanged,
                    "TranslationBehavior");
            }
        }

        /// <summary>
        /// Gets or sets the rotation behavior of the inertia processor.
        /// </summary>
        /// <example>
        /// In the following example, the 
        /// <strong><see cref="P:System.Windows.Input.Manipulations.InertiaRotationBehavior2D.DesiredRotation"/></strong>
        /// property is set to enable inertia processing to rotate an object 
        /// three-and-one-half times from its starting orientation.
        /// <code source="MPIP\ManipulationAPI\ManipulationItem.xaml.cs" region="SetDesiredRotation"/>
        /// </example>
        public InertiaRotationBehavior2D RotationBehavior
        {
            get { return this.rotationBehavior; }
            set
            {
                SetBehavior<InertiaRotationBehavior2D>(
                    ref this.rotationBehavior,
                    value,
                    OnRotationBehaviorChanged,
                    "RotationBehavior");
            }
        }

        /// <summary>
        /// Gets or sets the expansion behavior of the inertia processor.
        /// </summary>
        public InertiaExpansionBehavior2D ExpansionBehavior
        {
            get { return this.expansionBehavior; }
            set
            {
                SetBehavior<InertiaExpansionBehavior2D>(
                    ref this.expansionBehavior,
                    value,
                    OnExpansionBehaviorChanged,
                    "ExpansionBehavior");
            }
        }

        #endregion Public Properties


        #region Public Events

        /// <summary>
        /// Occurs when the extrapolation origin has changed or when translation, scaling, or rotation have occurred.
        /// </summary>
        /// <remarks>
        /// The <strong>InertiaProcessor2D.Delta</strong> event and the
        /// <strong><see cref="E:System.Windows.Input.Manipulations.ManipulationProcessor2D.Delta">ManipulationProcessor2D.Delta</see></strong>
        /// event are the same type. Typically, you can use the same event handler
        /// for both events.
        /// </remarks>
        /// <example>
        /// <code source="MPIP\ManipulationAPI\ManipulationItem.xaml.cs" region="OnManipulationOrInertiaDelta" lang="cs"/>
        /// </example>
        public event EventHandler<Manipulation2DDeltaEventArgs> Delta;

        /// <summary>
        /// Occurs when extrapolation has completed.
        /// </summary>
        public event EventHandler<Manipulation2DCompletedEventArgs> Completed;

        #endregion Public Events


        #region Public Methods

        /// <summary>
        /// Extrapolates the manipulation's position, orientation, and average radius at the specified
        /// time.
        /// </summary>
        /// <param name="timestamp">The timestamp to perform extrapolation, in 100-nanosecond ticks.</param>
        /// <returns><strong>true</strong> if extrapolation is in progress; otherwise, <strong>false</strong>
        /// if extrapolation has completed.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">The timestamp parameter
        /// is less than the initial or previous timestamp.
        /// </exception>
        /// <remarks>
        /// Timestamps are in 100-nanosecond units.
        /// </remarks>
        public bool Process(Int64 timestamp)
        {
            return Process(timestamp, false/*forceCompleted*/);
        }

        /// <summary>
        /// Completes final extrapolation by using the specified timestamp and raises the
        /// <strong><see cref="Completed"/></strong> event.
        /// </summary>
        /// <param name="timestamp">The timestamp to complete extrapolation, in 100-nanosecond ticks.</param>
        /// <exception cref="System.ArgumentOutOfRangeException">The timestamp parameter
        /// is less than the initial or previous timestamp.
        /// </exception>
        /// <remarks>
        /// Timestamps are in 100-nanosecond units.
        /// </remarks>
        /// <example>
        /// <para>
        /// In the following example, an event handler for the 
        /// <strong><see cref="System.Windows.Input.Manipulations.ManipulationProcessor2D.Started">ManipulationProcessor2D.Started</see></strong>
        /// event checks to see if inertia processing is running and if so, stops it by calling the
        /// <strong>Complete</strong> method.
        /// </para>
        /// <code lang="cs">
        ///  <code source="MPIP\ManipulationAPI\ManipulationItem.xaml.cs" region="OnManipulationStarted"/>
        ///  <code source="MPIP\ManipulationAPI\ManipulationItem.xaml.cs" region="Timestamp"/>
        /// </code>
        /// </example>
        public void Complete(Int64 timestamp)
        {
            bool result = Process(timestamp, true/*forceCompleted*/);
            Debug.Assert(!result, "Complete method is supposed to raise Completed event.");
        }

        /// <summary>
        /// Set parameters on the inertia processor.
        /// </summary>
        /// <param name="parameters">Parameters to set.</param>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public void SetParameters(InertiaParameters2D parameters)
        {
            ArgumentNullException.ThrowIfNull(parameters);

            InertiaTranslationBehavior2D translationParameters = parameters as InertiaTranslationBehavior2D;
            if (translationParameters != null)
            {
                TranslationBehavior = translationParameters;
                return;
            }

            InertiaRotationBehavior2D rotationParameters = parameters as InertiaRotationBehavior2D;
            if (rotationParameters != null)
            {
                RotationBehavior = rotationParameters;
                return;
            }

            InertiaExpansionBehavior2D expansionParameters = parameters as InertiaExpansionBehavior2D;
            if (expansionParameters != null)
            {
                ExpansionBehavior = expansionParameters;
                return;
            }

            Debug.Fail("Unsupported parameters");
        }
        #endregion Public Methods


        #region Private Methods & Properties

        /// <summary>
        /// Sets the initial timestamp.
        /// </summary>
        private void SetInitialTimestamp(Int64 timestamp)
        {
            Debug.Assert(!IsRunning);
            if (timestamp != this.initialTimestamp)
            {
                Reset();
                this.initialTimestamp = timestamp;
                this.previousTimestamp = timestamp;
            }
        }

        /// <summary>
        /// Resets the processor to the initial state.
        /// </summary>
        private void Reset()
        {
            if (this.processorState != ProcessorState.NotInitialized)
            {
                this.processorState = ProcessorState.NotInitialized;

                // set previousTimestamp to a value different than initialTimestamp,
                // this indicates that initialTimestamp is not initialized, basically
                // IsInitialTimestampInitialized is defined as "previousTimestamp==initialTimestamp"
                this.previousTimestamp = unchecked(this.initialTimestamp - 1);
            }
        }

        /// <summary>
        /// Called when a parameter is changed that is not allowed to change
        /// while inertia is running.
        /// </summary>
        /// <param name="paramName"></param>
        private void CheckNotRunning(string paramName)
        {
            if (IsRunning)
            {
                throw Exceptions.CannotChangeParameterDuringInertia(paramName);
            }
        }

        /// <summary>
        /// Sets acceleration behavior.
        /// </summary>
        /// <typeparam name="TBehavior"></typeparam>
        /// <param name="currentBehavior"></param>
        /// <param name="newBehavior"></param>
        /// <param name="handler"></param>
        /// <param name="propertyName"></param>
        private void SetBehavior<TBehavior>(
            ref TBehavior currentBehavior,
            TBehavior newBehavior,
            Action<InertiaParameters2D, string> handler,
            string propertyName)
            where TBehavior : InertiaParameters2D
        {
            Debug.Assert(handler != null);

            if (!object.ReferenceEquals(newBehavior, currentBehavior))
            {
                if (currentBehavior != null)
                {
                    currentBehavior.Changed -= handler;
                    currentBehavior = null;
                }
                Reset();
                if (newBehavior != null)
                {
                    currentBehavior = newBehavior;
                    currentBehavior.Changed += handler;
                }
                handler(currentBehavior, propertyName);
            }
        }

        /// <summary>
        /// Here when the translation behavior changes.
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="paramName"></param>
        private void OnTranslationBehaviorChanged(
            InertiaParameters2D parameters,
            string paramName)
        {
            CheckNotRunning(paramName);
            Reset();

            InertiaTranslationBehavior2D behavior = GetBehavior<InertiaTranslationBehavior2D>(parameters);

            this.desiredDeceleration = behavior.DesiredDeceleration;
            this.desiredDisplacement = behavior.DesiredDisplacement;
            this.initialTranslationX.Velocity = behavior.InitialVelocityX;
            this.initialTranslationY.Velocity = behavior.InitialVelocityY;
        }

        /// <summary>
        /// Here when the rotation behavior changes.
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="paramName"></param>
        private void OnRotationBehaviorChanged(
            InertiaParameters2D parameters,
            string paramName)
        {
            CheckNotRunning(paramName);
            Reset();

            InertiaRotationBehavior2D behavior = GetBehavior<InertiaRotationBehavior2D>(parameters);

            this.initialOrientation.AbsoluteDeceleration = behavior.DesiredDeceleration;
            this.initialOrientation.AbsoluteOffset = behavior.DesiredRotation;
            this.initialOrientation.Velocity = behavior.InitialVelocity;
        }

        /// <summary>
        /// Here when the expansion behavior changes.
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="paramName"></param>
        private void OnExpansionBehaviorChanged(
            InertiaParameters2D parameters,
            string paramName)
        {
            CheckNotRunning(paramName);
            Reset();

            InertiaExpansionBehavior2D behavior = GetBehavior<InertiaExpansionBehavior2D>(parameters);

            this.initialExpansion.Value = behavior.InitialRadius;
            this.initialExpansion.AbsoluteDeceleration = behavior.DesiredDeceleration;

            // Just use the X values and ignore the Y. Y will either be the same
            // (in which case ignoring it doesn't matter), or else an exception
            // will get thrown when they try to start the processor (in which case
            // it doesn't matter, either).
            this.initialExpansion.AbsoluteOffset = (behavior == null) ? float.NaN : behavior.DesiredExpansionX;
            this.initialExpansion.Velocity = (behavior == null) ? float.NaN : behavior.InitialVelocityX;
        }

        /// <summary>
        /// Given some inertia parameters, get an appropriate behavior object.
        /// If the parameters are null, gets a default.
        /// </summary>
        /// <typeparam name="TBehavior"></typeparam>
        /// <param name="parameters"></param>
        /// <returns></returns>
        private static TBehavior GetBehavior<TBehavior>(
            InertiaParameters2D parameters) where TBehavior : InertiaParameters2D, new()
        {
            if (parameters == null)
            {
                return new TBehavior();
            }
            else
            {
                return (TBehavior)parameters;
            }
        }

        /// <summary>
        /// Gets the velocities for the processor.
        /// </summary>
        /// <returns></returns>
        private ManipulationVelocities2D GetVelocities()
        {
            switch (this.processorState)
            {
                case ProcessorState.Running:
                case ProcessorState.Completing:
                    break;

                default:
                    // If we're not running or completing, velocity is zero.
                    return ManipulationVelocities2D.Zero;
            }

            long timeDelta = unchecked(this.previousTimestamp - this.initialTimestamp);
            if (timeDelta < 0)
            {
                // timestamps haven't been initialized yet
                return ManipulationVelocities2D.Zero;
            }

            return new ManipulationVelocities2D(
                this.translationX.GetVelocity(timeDelta),
                this.translationY.GetVelocity(timeDelta),
                this.orientation.GetVelocity(timeDelta),
                this.expansion.GetVelocity(timeDelta));
        }

        /// <summary>
        /// Gets incremental delta since last event.
        /// </summary>
        /// <param name="translationX"></param>
        /// <param name="translationY"></param>
        /// <param name="orientation"></param>
        /// <param name="expansion"></param>
        /// <param name="scaleDelta"></param>
        /// <returns></returns>
        private static ManipulationDelta2D GetIncrementalDelta(
            ExtrapolatedValue translationX,
            ExtrapolatedValue translationY,
            ExtrapolatedValue orientation,
            ExtrapolatedValue expansion,
            double scaleDelta)
        {
            return new ManipulationDelta2D(
                (float)translationX.Delta,
                (float)translationY.Delta,
                (float)orientation.Delta,
                (float)scaleDelta,
                (float)scaleDelta,
                (float)expansion.Delta,
                (float)expansion.Delta);
        }

        /// <summary>
        /// Gets cumulative delta since inertia began.
        /// </summary>
        /// <param name="translationX"></param>
        /// <param name="translationY"></param>
        /// <param name="orientation"></param>
        /// <param name="expansion"></param>
        /// <param name="totalScale"></param>
        /// <returns></returns>
        private static ManipulationDelta2D GetCumulativeDelta(
            ExtrapolatedValue translationX,
            ExtrapolatedValue translationY,
            ExtrapolatedValue orientation,
            ExtrapolatedValue expansion,
            double totalScale)
        {
            return new ManipulationDelta2D(
                (float)translationX.Total,
                (float)translationY.Total,
                (float)orientation.Total,
                (float)totalScale,
                (float)totalScale,
                (float)expansion.Total,
                (float)expansion.Total);
        }

        /// <summary>
        /// Prepares extrapolation.
        /// </summary>
        private void Prepare()
        {
#if DEBUG
            // clear the log
            log.Length = 0;
#endif
            // calculate initial DesiredTranslationXY and initial DesiredDecelerationXY
            if (!double.IsNaN(this.desiredDisplacement))
            {
                Debug.Assert(double.IsNaN(this.desiredDeceleration), "desiredDisplacement and desiredDeceleration are mutually exclusive.");
                VectorD desiredTranslationXY = GetAbsoluteVector(this.desiredDisplacement,
                    new VectorD(this.initialTranslationX.Velocity, this.initialTranslationY.Velocity));
                initialTranslationX.AbsoluteOffset = Math.Abs(desiredTranslationXY.X);
                initialTranslationY.AbsoluteOffset = Math.Abs(desiredTranslationXY.Y);
                initialTranslationX.AbsoluteDeceleration = double.NaN;
                initialTranslationY.AbsoluteDeceleration = double.NaN;
            }

            else if (!double.IsNaN(this.desiredDeceleration))
            {
                Debug.Assert(double.IsNaN(this.desiredDisplacement), "desiredDisplacement and desiredDeceleration are mutually exclusive.");
                VectorD desiredDecelerationXY = GetAbsoluteVector(this.desiredDeceleration,
                    new VectorD(this.initialTranslationX.Velocity, this.initialTranslationY.Velocity));
                initialTranslationX.AbsoluteDeceleration = Math.Abs(desiredDecelerationXY.X);
                initialTranslationY.AbsoluteDeceleration = Math.Abs(desiredDecelerationXY.Y);
                initialTranslationX.AbsoluteOffset = double.NaN;
                
                initialTranslationY.AbsoluteOffset = double.NaN;
            }

            this.translationX = Prepare(this.initialTranslationX, "translationX");
            this.translationY = Prepare(this.initialTranslationY, "translationY");
            this.orientation = Prepare(this.initialOrientation, "orientation");
            this.expansion = Prepare(this.initialExpansion, "expansion");

            // check that at least one value needs to be extrapolated
            if (this.translationX.ExtrapolationResult == ExtrapolationResult.Skip &&
                this.translationY.ExtrapolationResult == ExtrapolationResult.Skip &&
                this.orientation.ExtrapolationResult == ExtrapolationResult.Skip &&
                this.expansion.ExtrapolationResult == ExtrapolationResult.Skip)
            {
                throw Exceptions.NoInertiaVelocitiesSpecified(
                    "TranslationBehavior.InitialVelocityX",
                    "TranslationBehavior.InitialVelocityY",
                    "RotationBehavior.InitialVelocity",
                    "ExpansionBehavior.InitialVelocityX",
                    "ExpansionBehavior.InitialVelocityY");
            }
        }


        /// <summary>
        /// Prepares extrapolation state for the specified dimension.
        /// </summary>
        private ExtrapolationState Prepare(
            InitialState initialState,
            string dimension)
        {
#if DEBUG
            LogLine("PREPARE: " + dimension);
#endif
            ExtrapolationState state = new ExtrapolationState(initialState);

            // check if initial velocity is set, do nothing if not
            if (state.ExtrapolationResult != ExtrapolationResult.Skip)
            {
                Debug.Assert(
                    !double.IsNaN(state.Offset) || !double.IsNaN(state.AbsoluteDeceleration),
                    "Either offset or deceleration should have been set by now");

                // if velocity is 0, no need to extrapolate, the object gets stopped already
                if (DoubleUtil.IsZero(state.InitialVelocity))
                {
                    state.InitialVelocity = 0; // set to explicit 0 to clear out the fractional part
                    state.Duration = 0;
                    state.Offset = 0;
                    state.AbsoluteDeceleration = 0;
                }

                else
                {
                    Debug.Assert(!DoubleUtil.IsZero(state.InitialVelocity));

                    // calculate duration based on offset
                    if (!double.IsNaN(state.Offset))
                    {
                        if (DoubleUtil.IsZero(state.Offset))
                        {
                            state.Offset = 0; // set to explicit 0 to clear out the fractional part
                        }

                        state.Duration = 2 * Math.Abs(state.Offset / state.InitialVelocity);

                        if (DoubleUtil.IsZero(state.Duration))
                        {
                            state.Duration = 0; // set to explicit 0 to clear out the fractional part
                            state.AbsoluteDeceleration = double.PositiveInfinity;
                        }
                        else
                        {
                            state.AbsoluteDeceleration = Math.Abs(state.InitialVelocity) / state.Duration;
                        }
                    }

                    // calculate duration based on desiredDeceleration
                    // Note: the block above may get almost 0 AbsoluteDeceleration, check it here
                    if (DoubleUtil.IsZero(state.AbsoluteDeceleration))
                    {
                        // 0 desiredDeceleration => infinite duration and offset
                        state.AbsoluteDeceleration = 0; // set to explicit 0 to clear out the fractional part
                        state.Duration = double.PositiveInfinity;
                        state.Offset =
                            (state.InitialVelocity > 0 ? double.PositiveInfinity : double.NegativeInfinity);
                    }
                    else if (double.IsNaN(state.Offset))
                    {
                        // not 0-desiredDeceleration, calculate duration and offset
                        state.Duration = Math.Abs(state.InitialVelocity) / state.AbsoluteDeceleration;
                        state.Offset = state.InitialVelocity * state.Duration * 0.5;
                    }

                    Debug.Assert(state.Duration >= 0);
                    Debug.Assert(!double.IsNaN(state.Deceleration));
                    Debug.Assert(!double.IsNaN(state.Offset));
                }
            }

#if DEBUG
            LogLine(state.ToString());
            LogLine("");
            state.AssertValid();
#endif
            return state;
        }

        /// <summary>
        /// Extrapolates and raises an event.
        /// </summary>
        /// <returns>returns false if extrapolation is completed.</returns>
        private bool ExtrapolateAndRaiseEvents(Int64 timestamp, bool forceCompleted)
        {
            Debug.Assert(this.processorState == ProcessorState.Running);

            // use 'unchecked' to avoid overflow exceptions
            Int64 timeDelta = unchecked(timestamp - this.initialTimestamp);
            if (timeDelta < 0)
            {
                // timeDelta can be less than 0 if extrapolation runs too long,
                // just stop it here,
                // Note: we are using 'double' to calculate extrapolation,
                // so we could continue execution but unfortunately
                // inertia processor takes Int64 for the timestamp.
                // For simplicity stop here otherwise we would need to do some tricks
                // to bypass Int64.MaxValue barrier.
                timeDelta = Int64.MaxValue;
                forceCompleted = true;
                Debug.WriteLine("Too long extrapolation, stop it.");
            }

            // extrapolate values
            ExtrapolatedValue extrapolatedTranslationX = GetExtrapolatedValueAndUpdateState(this.translationX, timeDelta);
            ExtrapolatedValue extrapolatedTranslationY = GetExtrapolatedValueAndUpdateState(this.translationY, timeDelta);
            ExtrapolatedValue extrapolatedOrientation = GetExtrapolatedValueAndUpdateState(this.orientation, timeDelta);
            ExtrapolatedValue extrapolatedExpansion = GetExtrapolatedValueAndUpdateState(this.expansion, timeDelta);

            // check if all extrapolations are completed or not
            bool completed = forceCompleted ||
                (extrapolatedTranslationX.ExtrapolationResult != ExtrapolationResult.Continue &&
                extrapolatedTranslationY.ExtrapolationResult != ExtrapolationResult.Continue &&
                extrapolatedOrientation.ExtrapolationResult != ExtrapolationResult.Continue &&
                extrapolatedExpansion.ExtrapolationResult != ExtrapolationResult.Continue);

            if (completed)
            {
                // Need to set state to "completing" before raising the event (otherwise,
                // if a completed handler checked the "is running" property, it would
                // return true, which would be odd), 
                // we set to "completing" and not "completed" to let GetVelocity return
                // the last known velocity value and not 0.
                this.processorState = ProcessorState.Completing;

                // raise Completed event
                EventHandler<Manipulation2DCompletedEventArgs> eventHandler = Completed;
                if (eventHandler != null)
                {
                    // calculate scale
                    double totalScale = this.initialScale;
                    if (this.expansion.ExtrapolationResult != ExtrapolationResult.Skip)
                    {
                        Debug.Assert(this.expansion.InitialValue > 0 &&
                            !double.IsInfinity(this.expansion.InitialValue) &&
                            !double.IsNaN(this.expansion.InitialValue), "Invalid initial expansion value.");
                        totalScale *= extrapolatedExpansion.Value / this.expansion.InitialValue;
                    }

                    ManipulationDelta2D total = GetCumulativeDelta(
                        extrapolatedTranslationX,
                        extrapolatedTranslationY,
                        extrapolatedOrientation,
                        extrapolatedExpansion,
                        totalScale);

                    Manipulation2DCompletedEventArgs args = new Manipulation2DCompletedEventArgs(
                        (float)extrapolatedTranslationX.Value,
                        (float)extrapolatedTranslationY.Value,
                        GetVelocities(),
                        total);
                    eventHandler(this, args);

#if DEBUG
                    LogLine(
                        "Completed event:" +
                        " timeDelta=" + timeDelta +
                        " OriginX=" + args.OriginX +
                        " OriginY=" + args.OriginY +
                        " TotalTranslationX=" + args.Total.TranslationX +
                        " TotalTranslationY=" + args.Total.TranslationY +
                        " TotalRotation=" + args.Total.Rotation +
                        " TotalScale=" + args.Total.ScaleX);
#endif
                }
            }

            else
            {
                // raise Delta event
                EventHandler<Manipulation2DDeltaEventArgs> eventHandler = Delta;
                if (eventHandler != null)
                {
                    // calculate scale
                    double totalScale = this.initialScale;
                    double scaleDelta = 1;
                    if (this.expansion.ExtrapolationResult != ExtrapolationResult.Skip)
                    {
                        Debug.Assert(this.expansion.InitialValue > 0 &&
                            !double.IsInfinity(this.expansion.InitialValue) &&
                            !double.IsNaN(this.expansion.InitialValue), "Invalid initial expansion value.");
                        totalScale *= extrapolatedExpansion.Value / this.expansion.InitialValue;

                        if (!DoubleUtil.IsZero(extrapolatedExpansion.Delta))
                        {
                            double previousExpansionValue = extrapolatedExpansion.Value - extrapolatedExpansion.Delta;
                            Debug.Assert(!DoubleUtil.IsZero(previousExpansionValue));
                            scaleDelta = extrapolatedExpansion.Value / previousExpansionValue;
                        }
                    }

                    ManipulationDelta2D delta = GetIncrementalDelta(
                        extrapolatedTranslationX,
                        extrapolatedTranslationY,
                        extrapolatedOrientation,
                        extrapolatedExpansion,
                        scaleDelta);
                    ManipulationDelta2D total = GetCumulativeDelta(
                        extrapolatedTranslationX,
                        extrapolatedTranslationY,
                        extrapolatedOrientation,
                        extrapolatedExpansion,
                        totalScale);

                    Manipulation2DDeltaEventArgs args = new Manipulation2DDeltaEventArgs(
                        (float)extrapolatedTranslationX.Value,
                        (float)extrapolatedTranslationY.Value,
                        GetVelocities(),
                        delta,
                        total);

                    eventHandler(this, args);

#if DEBUG
                    LogLine(
                        "Delta event:" +
                        " timeDelta=" + timeDelta +
                        " OriginX=" + args.OriginX +
                        " OriginY=" + args.OriginY +
                        " DeltaX=" + args.Delta.TranslationX +
                        " DeltaY=" + args.Delta.TranslationY +
                        " RotationDelta=" + args.Delta.Rotation +
                        " ScaleDelta=" + args.Delta.ScaleX +
                        " CumulativeTranslationX=" + args.Cumulative.TranslationX +
                        " CumulativeTranslationY=" + args.Cumulative.TranslationY +
                        " CumulativeRotation=" + args.Cumulative.Rotation +
                        " CumulativeScale=" + args.Cumulative.ScaleX);
#endif

                }
            }

            return !completed;
        }

        /// <summary>
        /// Gets extrapolated value.
        /// </summary>
        /// <param name="initialValue"></param>
        /// <param name="initialVelocity"></param>
        /// <param name="deceleration"></param>
        /// <param name="timeDelta"></param>
        /// <returns></returns>
        private static double GetExtrapolatedValue(double initialValue, double initialVelocity, double deceleration, double timeDelta)
        {
            Debug.Assert(!double.IsNaN(initialVelocity) && !double.IsInfinity(initialValue));
            Debug.Assert(!double.IsNaN(initialVelocity) && !double.IsInfinity(initialVelocity));
            Debug.Assert(!double.IsNaN(deceleration) && !double.IsInfinity(deceleration));
            Debug.Assert(!double.IsNaN(timeDelta) && !double.IsInfinity(timeDelta) && timeDelta >= 0);

            double result = initialValue + (initialVelocity - deceleration * timeDelta * 0.5) * timeDelta;
            return result;
        }

        /// <summary>
        /// Gets extrapolated value.
        /// </summary>
        /// <param name="state"></param>
        /// <param name="timeDelta">in timestamp units (100-nanoseconds)</param>
        /// <returns></returns>
        private static ExtrapolatedValue GetExtrapolatedValueAndUpdateState(ExtrapolationState state, double timeDelta)
        {
            Debug.Assert(!double.IsNaN(timeDelta) && !double.IsInfinity(timeDelta) && timeDelta >= 0);

            if (state.ExtrapolationResult == ExtrapolationResult.Skip)
            {
                return new ExtrapolatedValue(state.InitialValue, 0, 0, ExtrapolationResult.Skip);
            }

            if (state.ExtrapolationResult == ExtrapolationResult.Stop)
            {
                return new ExtrapolatedValue(state.PreviousValue, 0, state.PreviousValue - state.InitialValue, ExtrapolationResult.Stop);
            }

            ExtrapolationResult resultAction = ExtrapolationResult.Continue;
            double resultValue = double.NaN;
            if (timeDelta >= state.Duration)
            {
                resultValue = state.FinalValue;
                timeDelta = state.Duration;
                resultAction = ExtrapolationResult.Stop;
            }

            if (double.IsNaN(resultValue))
            {
                // extrapolate position:
                resultValue = GetExtrapolatedValue(state.InitialValue, state.InitialVelocity, state.Deceleration, timeDelta);

                // make sure that the value is within allowed limits
                double limitedResultValue = state.LimitValue(resultValue);
                if (limitedResultValue != resultValue)
                {
                    resultValue = limitedResultValue;
                    resultAction = ExtrapolationResult.Stop;
                }
            }
            Debug.Assert(!double.IsNaN(resultValue) && !double.IsInfinity(resultValue), "Calculation error, value should be a finite number.");

            ExtrapolatedValue value = new ExtrapolatedValue(resultValue,
                resultValue - state.PreviousValue,
                resultValue - state.InitialValue,
                resultAction);

            // update the state
            state.PreviousValue = resultValue;
            state.ExtrapolationResult = value.ExtrapolationResult;

#if DEBUG
            state.AssertValid();
#endif
            return value;
        }

        /// <summary>
        /// Performs extrapolation.
        /// </summary>
        /// <param name="timestamp"></param>
        /// <param name="forceCompleted"></param>
        /// <returns></returns>
        private bool Process(Int64 timestamp, bool forceCompleted)
        {
            switch (this.processorState)
            {
                case ProcessorState.NotInitialized:
                    // Check our various inertia behaviors to make sure they're in valid states
                    if (this.translationBehavior != null)
                    {
                        this.translationBehavior.CheckValid();
                    }
                    if (this.expansionBehavior != null)
                    {
                        this.expansionBehavior.CheckValid();
                    }
                    if (this.rotationBehavior != null)
                    {
                        this.rotationBehavior.CheckValid();
                    }

                    // verify if initialTimestamp is initialized and set it to the current timestamp if not
                    if (this.previousTimestamp != this.initialTimestamp)
                    {
                        SetInitialTimestamp(timestamp);
                    }

                    // verify parameters and perform initial calculations
                    Prepare();
                    this.processorState = ProcessorState.Running;
                    break;

                case ProcessorState.Running:
                    break;

                case ProcessorState.Completing:
                case ProcessorState.Completed:
                    // do nothing, extrapolation finished
                    return false;

                default:
                    Debug.Assert(false);
                    break;
            }

            // To handle potential wrapping, make sure that 'unchecked' delta between the new value and the previous one
            // is greater than or equal to 0, otherwise there is a chance that user specified a "smaller"
            // timestamp which is wrong.
            if (unchecked(timestamp - this.previousTimestamp) < 0)
            {
                // throw an exception,
                // make sure that the outer method has parameter named "timestamp"
                throw Exceptions.InvalidTimestamp("timestamp", timestamp);
            }

            bool result = ExtrapolateAndRaiseEvents(timestamp, forceCompleted);
            this.previousTimestamp = timestamp;

            if (!result)
            {
                // exrapolation finished, update the state
                this.processorState = ProcessorState.Completed;
            }

            return result;
        }

        /// <summary>
        /// Returns NaN if 'value' is NaN, or 0 - if 'value' is 0 or 'scale' - otherwise.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="scale"></param>
        /// <returns></returns>
        private static double ScaleValue(double value, double scale)
        {
            Debug.Assert(!double.IsInfinity(value));
            if (double.IsNaN(value))
            {
                return double.NaN;
            }
            if (DoubleUtil.IsZero(value))
            {
                return 0;
            }
            return scale;
        }

        /// <summary>
        /// Returns an absolute vector with a given length along the base vector.
        /// The result of this method is used by InitialState.AbsolureOffset and InitialState.AbsolureDeceleration.
        /// </summary>
        private static VectorD GetAbsoluteVector(double length, VectorD baseVector)
        {
            Debug.Assert(!double.IsNaN(length) && length >= 0 && !double.IsInfinity(length));
            Debug.Assert(!double.IsInfinity(baseVector.X));
            Debug.Assert(!double.IsInfinity(baseVector.Y));

            VectorD result;
            if (!double.IsNaN(baseVector.X) && !double.IsNaN(baseVector.Y) &&
                !DoubleUtil.IsZero(baseVector.X) && !DoubleUtil.IsZero(baseVector.Y))
            {
                double scale = length / baseVector.Length;
                result = new VectorD(Math.Abs(baseVector.X * scale),
                    Math.Abs(baseVector.Y * scale));
            }
            else
            {
                result = new VectorD(ScaleValue(baseVector.X, length), ScaleValue(baseVector.Y, length));
            }

            return result;
        }

        /// <summary>
        /// Checks if the given value is valid.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="paramName"></param>
        private static void CheckOriginalValue(float value, string paramName)
        {
            Validations.CheckFinite(value, paramName);
        }

#if DEBUG
        /// <summary>
        /// Logs diagnostic trace.
        /// </summary>
        /// <param name="msg"></param>
        private void LogLine(string msg)
        {
            if (log.Length < 1000000)
            {
                log.AppendLine(msg);
            }
        }
#endif

        #endregion Private Methods


        #region Private Classes

        /// <summary>
        /// Initial state for a single dimension.
        /// </summary>
        private class InitialState
        {
            // initial velocity, in coordinate units per millisecond
            public double Velocity = double.NaN;

            // initial value
            public double Value;

            // absolute offset
            public double AbsoluteOffset = double.NaN;

            // boundary value (for collision detection)
            public double MinBound = double.NegativeInfinity;
            public double MaxBound = double.PositiveInfinity;

            // absolute desiredDeceleration, in coordinate units per squared millisecond
            public double AbsoluteDeceleration = double.NaN;

            // clones the object
            public InitialState Clone()
            {
                return (InitialState)this.MemberwiseClone();
            }

#if DEBUG
            // dumps the object properties
            public override string ToString()
            {
                string result =
                    "Velocity=" + this.Velocity +
                    ",\nValue=" + this.Value +
                    ",\nAbsoluteOffset=" + this.AbsoluteOffset +
                    ",\nAbsoluteDeceleration=" + this.AbsoluteDeceleration +
                    ",\nMinBound=" + this.MinBound +
                    ",\nMaxBound=" + this.MaxBound;
                return result;
            }
#endif

        }

        /// <summary>
        /// Extrapolation state for a single dimension.
        ///
        /// Input:
        /// - InitialVelocity
        /// - InitialValue
        /// - Offset or Deceleration
        /// Optional:
        /// - Bounds
        ///
        /// The inertia processor will use liner extrapolation and calculate duration of the whole extrapolation,
        /// detect collision with the boundary and calculate CollisionTime and Velocity/Deceleration after collision.
        ///
        /// </summary>
        private class ExtrapolationState
        {
            // initial state of the extrapolation
            private readonly InitialState initialState = new InitialState();

            // initial velocity, in coordinate units per timestamp units (100-nanoseconds)
            public double InitialVelocity = double.NaN;

            // initial value
            public readonly double InitialValue;

            // offset from the initial value
            public double Offset;

            // final value
            public double FinalValue
            {
                get
                {
                    // make sure that the final value is within the bounds
                    return LimitValue(InitialValue + Offset);
                }
            }


            /// <summary>
            /// Limits the given value to allowed bounds.
            /// </summary>
            /// <param name="value"></param>
            /// <returns></returns>
            public double LimitValue(double value)
            {
                return DoubleUtil.Limit(
                    value,
                    this.initialState.MinBound,
                    this.initialState.MaxBound);
            }

            // absolute desiredDeceleration in coordinate units per timestamp squared (100-nanoseconds squared)
            public double AbsoluteDeceleration = double.NaN;

            // desiredDeceleration in coordinate units per timestamp squared (100-nanoseconds squared)
            public double Deceleration
            {
                get
                {
                    return InitialVelocity < 0 ? -AbsoluteDeceleration : AbsoluteDeceleration;
                }
            }

            // total duration in timestamp units (100-nanoseconds)
            public double Duration;

            // indicates whether the extrapolation is completed or not
            public ExtrapolationResult ExtrapolationResult = ExtrapolationResult.Skip;

            // previous value
            public double PreviousValue;

            // constructs extrapolation state
            public ExtrapolationState(InitialState initialState)
            {
                Debug.Assert(initialState != null);
                Debug.Assert(initialState.AbsoluteDeceleration >= 0 || double.IsNaN(initialState.AbsoluteDeceleration));
                Debug.Assert(initialState.AbsoluteOffset >= 0 || double.IsNaN(initialState.AbsoluteOffset));

                // clone the initial state to make sure that if a user modifies it
                // (in InertiaProcessor's event handler), it doesn't affect the extrapolation.
                this.initialState = initialState.Clone();
                this.InitialVelocity = initialState.Velocity * InertiaProcessor2D.millisecondsPerTimestampTick; // convert to timestamp units
                this.InitialValue = initialState.Value;
                this.Offset = initialState.Velocity < 0 ? -initialState.AbsoluteOffset : initialState.AbsoluteOffset;
                this.AbsoluteDeceleration = initialState.AbsoluteDeceleration * InertiaProcessor2D.millisecondsPerTimestampTickSquared; // convert to timestamp units
                this.PreviousValue = this.InitialValue;
                this.ExtrapolationResult = double.IsNaN(this.InitialVelocity) ? ExtrapolationResult.Skip : ExtrapolationResult.Continue;
            }

            /// <summary>
            /// Gets the velocity at the specified point in time.
            /// </summary>
            /// <param name="elapsedTimeSinceInitialTimestamp">Timestamp ticks that have elapsed since the initial timestamp.</param>
            /// <returns></returns>
            public float GetVelocity(long elapsedTimeSinceInitialTimestamp)
            {
                Debug.Assert(elapsedTimeSinceInitialTimestamp >= 0);

                if (ExtrapolationResult != ExtrapolationResult.Continue)
                {
                    // extrapolation for this dimension isn't set
                    return 0;
                }

                double result = InitialVelocity - Deceleration * elapsedTimeSinceInitialTimestamp;

                // convert to milliseconds
                result = result * timestampTicksPerMillisecond;
                Debug.Assert(Validations.IsFinite((float)result));
                return (float)result;
            }

#if DEBUG
            // dumps the object properties
            public override string ToString()
            {
                string result =
                    "initialState:\n{" + this.initialState.ToString() + "}\n" +
                    "\nExtrapolationResult=" + this.ExtrapolationResult +
                    ",\nInitialVelocity=" + this.InitialVelocity +
                    ",\nInitialValue=" + this.InitialValue +
                    ",\nFinalValue=" + this.FinalValue +
                    ",\nDeceleration=" + this.Deceleration +
                    ",\nOffset=" + this.Offset +
                    ",\nDuration=" + this.Duration;
                return result;
            }

            // integrity check
            public void AssertValid()
            {
                if (double.IsNaN(this.InitialVelocity))
                {
                    // ignore this dimension
                    Debug.Assert(this.ExtrapolationResult == ExtrapolationResult.Skip);
                    return;
                }

                Debug.Assert(!double.IsNaN(this.InitialValue) && !double.IsInfinity(this.InitialValue));
                Debug.Assert(!double.IsNaN(this.InitialVelocity) && !double.IsInfinity(this.InitialVelocity));
                Debug.Assert(!double.IsNaN(this.Offset)); // can be infinity
                Debug.Assert(!double.IsNaN(this.AbsoluteDeceleration) && this.AbsoluteDeceleration >= 0 && !double.IsInfinity(this.InitialVelocity));
                Debug.Assert(!double.IsNaN(this.Duration) && this.Duration >= 0); // can be infinity
                Debug.Assert(!double.IsNaN(this.FinalValue)); // can be infinity
            }
#endif
        }

        /// <summary>
        /// Extrapolation result for a single dimension.
        /// </summary>
        private enum ExtrapolationResult
        {
            // ignore value
            Skip,

            // continue extrapolation
            Continue,

            // final value
            Stop
        }

        /// <summary>
        /// Extrapolated value for a single dimension.
        /// </summary>
        private struct ExtrapolatedValue
        {
            // the current value
            public readonly double Value;

            // delta from the previous one
            public readonly double Delta;

            // total delta from the moment when extrapolation started
            public readonly double Total;

            // a flag indicating how to handle this value
            public readonly ExtrapolationResult ExtrapolationResult;

            public ExtrapolatedValue(double value, double delta, double total, ExtrapolationResult result)
            {
                Debug.Assert(!double.IsNaN(value) && !double.IsInfinity(value));
                Debug.Assert(!double.IsNaN(delta) && !double.IsInfinity(delta));
                Debug.Assert(!double.IsNaN(total) && !double.IsInfinity(total));
                this.Value = value;
                this.Delta = delta;
                this.Total = total;
                this.ExtrapolationResult = result;
            }
        }

        /// <summary>
        /// Processor state.
        /// </summary>
        private enum ProcessorState
        {
            /// <summary>
            /// instance created by Initialize() method is not called yet,
            /// attempt to call Process() or Complete() will result in an Exception.
            /// </summary>
            NotInitialized,

            /// <summary>
            /// Initialize() method has been called, user can call Process() or Complete().
            /// </summary>
            Running,

            /// <summary>
            /// Extrapolation is about to complete, this is intermediate state, it's set before raising Completed event,
            /// and will be transitioning to Completed by the end of the Process/Complete method call.
            /// This state is introduced in order to make IsRunning property return 'false' but 
            /// GetVelocity method return non 0 value when Completed event handler gets raised.
            /// </summary>
            Completing,

            /// <summary>
            /// Extrapolation is completed, subsequent calls Process() or Complete() will result in Completed event.
            /// To reset the processor, the user needs to call Initialize().
            /// </summary>
            Completed,
        }

        #endregion Private Classes
    }
}
