/*************************************************************************************

   Extended WPF Toolkit

   Copyright (C) 2007-2013 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license 

   For more features, controls, and fast professional support,
   pick up the Plus Edition at http://xceed.com/wpf_toolkit

   Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids

  ***********************************************************************************/

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Xceed.Wpf.Toolkit.Core.Utilities;
using Xceed.Wpf.Toolkit.Core;

namespace Xceed.Wpf.Toolkit.Media.Animation
{
  [TypeConverter( typeof( AnimationRateConverter ) )]
  [StructLayout( LayoutKind.Explicit )]
  public struct AnimationRate
  {
    #region Static Fields

    private static AnimationRate _default = new AnimationRate( true );

    #endregion

    #region Constructors

    public AnimationRate( TimeSpan duration )
    {
      if( duration < TimeSpan.Zero )
      {
        throw new ArgumentException( ErrorMessages.GetMessage( ErrorMessages.NegativeTimeSpanNotSupported ) );
      }
      _speed = 0d;
      _duration = duration.Ticks;
      _rateType = RateType.TimeSpan;
    }

    public AnimationRate( double speed )
    {
      if( DoubleHelper.IsNaN( speed ) || speed < 0d )
      {
        throw new ArgumentException( ErrorMessages.GetMessage( ErrorMessages.NegativeSpeedNotSupported ) );
      }
      _duration = 0;
      _speed = speed;
      _rateType = RateType.Speed;
    }

    private AnimationRate( bool ignore )
    {
      _duration = 0;
      _speed = double.NaN;
      _rateType = RateType.Speed;
    }

    #endregion

    #region Default Property

    public static AnimationRate Default
    {
      get
      {
        return _default;
      }
    }

    #endregion

    #region HasDuration Property

    public bool HasDuration
    {
      get
      {
        return ( _rateType == RateType.TimeSpan );
      }
    }

    #endregion

    #region Duration Property

    public TimeSpan Duration
    {
      get
      {
        if( this.HasDuration )
          return TimeSpan.FromTicks( _duration );

        throw new InvalidOperationException( 
          string.Format(
            ErrorMessages.GetMessage( ErrorMessages.InvalidRatePropertyAccessed ),
            "Duration", 
            this, 
            "Speed" ) );
      }
    }

    #endregion

    #region HasSpeed Property

    public bool HasSpeed
    {
      get
      {
        return ( _rateType == RateType.Speed );
      }
    }

    #endregion

    #region Speed Property

    public double Speed
    {
      get
      {
        if( this.HasSpeed )
          return _speed;

        throw new InvalidOperationException( 
          string.Format(
            ErrorMessages.GetMessage( ErrorMessages.InvalidRatePropertyAccessed ),
            "Speed", 
            this, 
            "Duration" ) );
      }
    }

    #endregion

    public AnimationRate Add( AnimationRate animationRate )
    {
      return this + animationRate;
    }

    public override bool Equals( Object value )
    {
      if( value == null )
        return false;

      if( value is AnimationRate )
        return this.Equals( ( AnimationRate )value );

      return false;
    }

    public bool Equals( AnimationRate animationRate )
    {
      if( this.HasDuration )
      {
        if( animationRate.HasDuration )
          return _duration == animationRate._duration;

        return false;
      }
      else // HasSpeed
      {
        if( animationRate.HasSpeed )
        {
          if( DoubleHelper.IsNaN( _speed ) )
            return DoubleHelper.IsNaN( animationRate._speed );

          return _speed == animationRate._speed;
        }

        return false;
      }
    }

    public static bool Equals( AnimationRate t1, AnimationRate t2 )
    {
      return t1.Equals( t2 );
    }

    public override int GetHashCode()
    {
      if( this.HasDuration )
        return _duration.GetHashCode();

      return _speed.GetHashCode();
    }

    public AnimationRate Subtract( AnimationRate animationRate )
    {
      return this - animationRate;
    }

    public override string ToString()
    {
      if( this.HasDuration )
        return TypeDescriptor.GetConverter( _duration ).ConvertToString( _duration );

      return TypeDescriptor.GetConverter( _speed ).ConvertToString( _speed );
    }

    #region Operators Methods

    public static implicit operator AnimationRate( TimeSpan duration )
    {
      if( duration < TimeSpan.Zero )
        throw new ArgumentException( ErrorMessages.GetMessage( ErrorMessages.NegativeTimeSpanNotSupported ) );

      return new AnimationRate( duration );
    }

    public static implicit operator AnimationRate( double speed )
    {
      if( DoubleHelper.IsNaN( speed ) || speed < 0 )
        throw new ArgumentException( ErrorMessages.GetMessage( ErrorMessages.NegativeSpeedNotSupported ) );

      return new AnimationRate( speed );
    }

    public static implicit operator AnimationRate( int speed )
    {
      if( DoubleHelper.IsNaN( speed ) || speed < 0 )
        throw new ArgumentException( ErrorMessages.GetMessage( ErrorMessages.NegativeSpeedNotSupported ) );

      return new AnimationRate( ( double )speed );
    }

    public static AnimationRate operator +( AnimationRate t1, AnimationRate t2 )
    {
      if( t1.HasDuration && t2.HasDuration )
        return new AnimationRate( t1._duration + t2._duration );

      if( t1.HasSpeed && t2.HasSpeed )
        return new AnimationRate( t1._speed + t2._speed );

      return ( AnimationRate )0d;
    }

    public static AnimationRate operator -( AnimationRate t1, AnimationRate t2 )
    {
      if( t1.HasDuration && t2.HasDuration )
        return new AnimationRate( t1._duration - t2._duration );

      if( t1.HasSpeed && t2.HasSpeed )
        return new AnimationRate( t1._speed - t2._speed );

      return ( AnimationRate )0d;
    }

    public static bool operator ==( AnimationRate t1, AnimationRate t2 )
    {
      return t1.Equals( t2 );
    }

    public static bool operator !=( AnimationRate t1, AnimationRate t2 )
    {
      return !( t1.Equals( t2 ) );
    }

    public static bool operator >( AnimationRate t1, AnimationRate t2 )
    {
      if( t1.HasDuration && t2.HasDuration )
        return t1._duration > t2._duration;

      if( t1.HasSpeed && t2.HasSpeed )
        return ( t1._speed > t2._speed ) && !DoubleHelper.AreVirtuallyEqual( t1._speed, t2._speed );

      // arbitrary: assume a Speed is greater than a Duration
      return t1.HasSpeed;
    }

    public static bool operator >=( AnimationRate t1, AnimationRate t2 )
    {
      return !( t1 < t2 );
    }

    public static bool operator <( AnimationRate t1, AnimationRate t2 )
    {
      if( t1.HasDuration && t2.HasDuration )
        return t1._duration < t2._duration;

      if( t1.HasSpeed && t2.HasSpeed )
        return ( t1._speed < t2._speed ) && !DoubleHelper.AreVirtuallyEqual( t1._speed, t2._speed );

      // arbitrary: assume a Speed is greater than a Duration
      return t1.HasDuration;
    }

    public static bool operator <=( AnimationRate t1, AnimationRate t2 )
    {
      return !( t1 > t2 );
    }

    public static int Compare( AnimationRate t1, AnimationRate t2 )
    {
      if( t1 < t2 )
        return -1;

      if( t1 > t2 )
        return 1;

      // Neither is greater than the other
      return 0;
    }

    public static AnimationRate Plus( AnimationRate animationRate )
    {
      return animationRate;
    }

    public static AnimationRate operator +( AnimationRate animationRate )
    {
      return animationRate;
    }

    #endregion

    #region Private Fields

    [FieldOffset( 0 )]
    long _duration;
    [FieldOffset( 0 )]
    double _speed;
    [FieldOffset( 8 )]
    RateType _rateType;

    #endregion

    #region RateType Nested Type

    private enum RateType
    {
      TimeSpan,
      Speed,
    }

    #endregion
  }
}
