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

   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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Threading;
using Xceed.Wpf.Toolkit.Primitives;
using Xceed.Wpf.Toolkit.Media.Animation;
using Xceed.Wpf.Toolkit.Core.Utilities;

namespace Xceed.Wpf.Toolkit.Panels
{
  public class SwitchPanel : PanelBase, IScrollInfo
  {
    #region Static Fields

    private static readonly Vector ZeroVector = new Vector();

    #endregion

    #region Constructors

    public SwitchPanel()
    {
      this.SetLayouts( new ObservableCollection<AnimationPanel>() );
      this.Loaded += new RoutedEventHandler( this.OnLoaded );
    }

    #endregion

    #region AreLayoutSwitchesAnimated Property

    public static readonly DependencyProperty AreLayoutSwitchesAnimatedProperty =
      DependencyProperty.Register( "AreLayoutSwitchesAnimated", typeof( bool ), typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( true ) );

    public bool AreLayoutSwitchesAnimated
    {
      get
      {
        return ( bool )this.GetValue( SwitchPanel.AreLayoutSwitchesAnimatedProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.AreLayoutSwitchesAnimatedProperty, value );
      }
    }

    #endregion

    #region ActiveLayout Property

    private static readonly DependencyPropertyKey ActiveLayoutPropertyKey =
      DependencyProperty.RegisterReadOnly( "ActiveLayout", typeof( AnimationPanel ), typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsMeasure,
          new PropertyChangedCallback( SwitchPanel.OnActiveLayoutChanged ) ) );

    public static readonly DependencyProperty ActiveLayoutProperty = SwitchPanel.ActiveLayoutPropertyKey.DependencyProperty;

    public AnimationPanel ActiveLayout
    {
      get
      {
        return ( AnimationPanel )this.GetValue( SwitchPanel.ActiveLayoutProperty );
      }
    }

    protected void SetActiveLayout( AnimationPanel value )
    {
      this.SetValue( SwitchPanel.ActiveLayoutPropertyKey, value );
    }

    private static void OnActiveLayoutChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
      ( ( SwitchPanel )d ).OnActiveLayoutChanged( e );
    }

    protected virtual void OnActiveLayoutChanged( DependencyPropertyChangedEventArgs e )
    {
      if( _currentLayoutPanel != null )
      {
        _currentLayoutPanel.DeactivateLayout();
      }
      _currentLayoutPanel = e.NewValue as AnimationPanel;
      if( _currentLayoutPanel != null )
      {
        IScrollInfo info = _currentLayoutPanel as IScrollInfo;
        if( info != null && info.ScrollOwner != null )
        {
          info.ScrollOwner.InvalidateScrollInfo();
        }

        _currentLayoutPanel.ActivateLayout();
      }

      this.RaiseActiveLayoutChangedEvent();
      this.Dispatcher.BeginInvoke(
        DispatcherPriority.Normal,
        ( ThreadStart )delegate()
        {
          this.UpdateSwitchTemplate();
        } );
    }

    #endregion

    #region ActiveLayoutIndex Property

    public static readonly DependencyProperty ActiveLayoutIndexProperty =
      DependencyProperty.Register( "ActiveLayoutIndex", typeof( int ), typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( -1,
          new PropertyChangedCallback( SwitchPanel.OnActiveLayoutIndexChanged ), new CoerceValueCallback( SwitchPanel.CoerceActiveLayoutIndexValue ) ) );

    public int ActiveLayoutIndex
    {
      get
      {
        return ( int )this.GetValue( SwitchPanel.ActiveLayoutIndexProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.ActiveLayoutIndexProperty, value );
      }
    }

    private static void OnActiveLayoutIndexChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
      ( ( SwitchPanel )d ).OnActiveLayoutIndexChanged( e );
    }

    protected virtual void OnActiveLayoutIndexChanged( DependencyPropertyChangedEventArgs e )
    {
      this.SetActiveLayout( ( this.Layouts.Count == 0 ) ? null : this.Layouts[ ActiveLayoutIndex ] );
    }

    private static object CoerceActiveLayoutIndexValue( DependencyObject d, object value )
    {
      int panelCount = ( d as SwitchPanel ).Layouts.Count;
      int result = ( int )value;

      if( result < 0 && panelCount > 0 )
      {
        result = 0;
      }
      else if( result >= panelCount )
      {
        result = panelCount - 1;
      }

      return result;
    }

    #endregion

    #region ActiveSwitchTemplate Property

    private static readonly DependencyPropertyKey ActiveSwitchTemplatePropertyKey =
      DependencyProperty.RegisterReadOnly( "ActiveSwitchTemplate", typeof( DataTemplate ), typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( null, new PropertyChangedCallback( SwitchPanel.OnActiveSwitchTemplateChanged ) ) );

    public static readonly DependencyProperty ActiveSwitchTemplateProperty = SwitchPanel.ActiveSwitchTemplatePropertyKey.DependencyProperty;

    public DataTemplate ActiveSwitchTemplate
    {
      get
      {
        return ( DataTemplate )this.GetValue( SwitchPanel.ActiveSwitchTemplateProperty );
      }
    }

    protected void SetActiveSwitchTemplate( DataTemplate value )
    {
      this.SetValue( SwitchPanel.ActiveSwitchTemplatePropertyKey, value );
    }

    private static void OnActiveSwitchTemplateChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
      ( ( SwitchPanel )d ).OnActiveSwitchTemplateChanged( e );
    }

    protected virtual void OnActiveSwitchTemplateChanged( DependencyPropertyChangedEventArgs e )
    {
      if( _presenters.Count > 0 )
      {
        DataTemplate template = e.NewValue as DataTemplate;
        List<UIElement> currentChildren = new List<UIElement>( this.InternalChildren.Count );
        foreach( UIElement child in this.InternalChildren )
        {
          if( child == null )
            continue;

          currentChildren.Add( child );
        }

        foreach( SwitchPresenter presenter in _presenters )
        {
          if( presenter._switchRoot != null && currentChildren.Contains( presenter._switchRoot ) )
          {
            presenter.SwapTheTemplate( template, this.AreLayoutSwitchesAnimated );
          }
        }
      }
    }

    #endregion

    #region DefaultAnimationRate Property

    public static readonly DependencyProperty DefaultAnimationRateProperty =
      AnimationPanel.DefaultAnimationRateProperty.AddOwner( typeof( SwitchPanel ) );

    public AnimationRate DefaultAnimationRate
    {
      get
      {
        return ( AnimationRate )this.GetValue( SwitchPanel.DefaultAnimationRateProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.DefaultAnimationRateProperty, value );
      }
    }

    #endregion

    #region DefaultAnimator Property

    public static readonly DependencyProperty DefaultAnimatorProperty =
      AnimationPanel.DefaultAnimatorProperty.AddOwner( typeof( SwitchPanel ) );

    public IterativeAnimator DefaultAnimator
    {
      get
      {
        return ( IterativeAnimator )this.GetValue( SwitchPanel.DefaultAnimatorProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.DefaultAnimatorProperty, value );
      }
    }

    #endregion

    #region EnterAnimationRate Property

    public static readonly DependencyProperty EnterAnimationRateProperty =
      AnimationPanel.EnterAnimationRateProperty.AddOwner( typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( AnimationRate.Default ) );

    public AnimationRate EnterAnimationRate
    {
      get
      {
        return ( AnimationRate )this.GetValue( SwitchPanel.EnterAnimationRateProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.EnterAnimationRateProperty, value );
      }
    }

    #endregion

    #region EnterAnimator Property

    public static readonly DependencyProperty EnterAnimatorProperty =
      AnimationPanel.EnterAnimatorProperty.AddOwner( typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( IterativeAnimator.Default ) );

    public IterativeAnimator EnterAnimator
    {
      get
      {
        return ( IterativeAnimator )this.GetValue( SwitchPanel.EnterAnimatorProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.EnterAnimatorProperty, value );
      }
    }

    #endregion

    #region ExitAnimationRate Property

    public static readonly DependencyProperty ExitAnimationRateProperty =
      AnimationPanel.ExitAnimationRateProperty.AddOwner( typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( AnimationRate.Default ) );

    public AnimationRate ExitAnimationRate
    {
      get
      {
        return ( AnimationRate )this.GetValue( SwitchPanel.ExitAnimationRateProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.ExitAnimationRateProperty, value );
      }
    }

    #endregion

    #region ExitAnimator Property

    public static readonly DependencyProperty ExitAnimatorProperty =
      AnimationPanel.ExitAnimatorProperty.AddOwner( typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( IterativeAnimator.Default ) );

    public IterativeAnimator ExitAnimator
    {
      get
      {
        return ( IterativeAnimator )this.GetValue( SwitchPanel.ExitAnimatorProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.ExitAnimatorProperty, value );
      }
    }

    #endregion

    #region LayoutAnimationRate Property

    public static readonly DependencyProperty LayoutAnimationRateProperty =
      AnimationPanel.LayoutAnimationRateProperty.AddOwner( typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( AnimationRate.Default ) );

    public AnimationRate LayoutAnimationRate
    {
      get
      {
        return ( AnimationRate )this.GetValue( SwitchPanel.LayoutAnimationRateProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.LayoutAnimationRateProperty, value );
      }
    }

    #endregion

    #region LayoutAnimator Property

    public static readonly DependencyProperty LayoutAnimatorProperty =
      AnimationPanel.LayoutAnimatorProperty.AddOwner( typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( IterativeAnimator.Default ) );

    public IterativeAnimator LayoutAnimator
    {
      get
      {
        return ( IterativeAnimator )this.GetValue( SwitchPanel.LayoutAnimatorProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.LayoutAnimatorProperty, value );
      }
    }

    #endregion

    #region Layouts Property

    private static readonly DependencyPropertyKey LayoutsPropertyKey =
      DependencyProperty.RegisterReadOnly( "Layouts", typeof( ObservableCollection<AnimationPanel> ), typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsArrange,
          new PropertyChangedCallback( SwitchPanel.OnLayoutsChanged ) ) );

    public static readonly DependencyProperty LayoutsProperty = SwitchPanel.LayoutsPropertyKey.DependencyProperty;

    public ObservableCollection<AnimationPanel> Layouts
    {
      get
      {
        return ( ObservableCollection<AnimationPanel> )this.GetValue( SwitchPanel.LayoutsProperty );
      }
    }

    protected void SetLayouts( ObservableCollection<AnimationPanel> value )
    {
      this.SetValue( SwitchPanel.LayoutsPropertyKey, value );
    }

    private static void OnLayoutsChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
      ( ( SwitchPanel )d ).OnLayoutsChanged( e );
    }

    protected virtual void OnLayoutsChanged( DependencyPropertyChangedEventArgs e )
    {
      if( e.NewValue != null )
      {
        ( e.NewValue as ObservableCollection<AnimationPanel> ).CollectionChanged
          += new NotifyCollectionChangedEventHandler( LayoutsCollectionChanged );
      }
      if( e.OldValue != null )
      {
        ( e.OldValue as ObservableCollection<AnimationPanel> ).CollectionChanged
          -= new NotifyCollectionChangedEventHandler( LayoutsCollectionChanged );
      }
    }

    #endregion

    #region SwitchAnimationRate Property

    public static readonly DependencyProperty SwitchAnimationRateProperty =
      AnimationPanel.SwitchAnimationRateProperty.AddOwner( typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( AnimationRate.Default ) );

    public AnimationRate SwitchAnimationRate
    {
      get
      {
        return ( AnimationRate )this.GetValue( SwitchPanel.SwitchAnimationRateProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.SwitchAnimationRateProperty, value );
      }
    }

    #endregion

    #region SwitchAnimator Property

    public static readonly DependencyProperty SwitchAnimatorProperty =
      AnimationPanel.SwitchAnimatorProperty.AddOwner( typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( IterativeAnimator.Default ) );

    public IterativeAnimator SwitchAnimator
    {
      get
      {
        return ( IterativeAnimator )this.GetValue( SwitchPanel.SwitchAnimatorProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.SwitchAnimatorProperty, value );
      }
    }

    #endregion

    #region SwitchTemplate Property

    public static readonly DependencyProperty SwitchTemplateProperty =
      DependencyProperty.Register( "SwitchTemplate", typeof( DataTemplate ), typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( null,
          new PropertyChangedCallback( SwitchPanel.OnSwitchTemplateChanged ) ) );

    public DataTemplate SwitchTemplate
    {
      get
      {
        return ( DataTemplate )this.GetValue( SwitchPanel.SwitchTemplateProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.SwitchTemplateProperty, value );
      }
    }

    private static void OnSwitchTemplateChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
      ( ( SwitchPanel )d ).OnSwitchTemplateChanged( e );
    }

    protected virtual void OnSwitchTemplateChanged( DependencyPropertyChangedEventArgs e )
    {
      this.UpdateSwitchTemplate();
    }

    #endregion

    #region TemplateAnimationRate Property

    public static readonly DependencyProperty TemplateAnimationRateProperty =
      AnimationPanel.TemplateAnimationRateProperty.AddOwner( typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( AnimationRate.Default ) );

    public AnimationRate TemplateAnimationRate
    {
      get
      {
        return ( AnimationRate )this.GetValue( SwitchPanel.TemplateAnimationRateProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.TemplateAnimationRateProperty, value );
      }
    }

    #endregion

    #region TemplateAnimator Property

    public static readonly DependencyProperty TemplateAnimatorProperty =
      AnimationPanel.TemplateAnimatorProperty.AddOwner( typeof( SwitchPanel ),
        new FrameworkPropertyMetadata( IterativeAnimator.Default ) );

    public IterativeAnimator TemplateAnimator
    {
      get
      {
        return ( IterativeAnimator )this.GetValue( SwitchPanel.TemplateAnimatorProperty );
      }
      set
      {
        this.SetValue( SwitchPanel.TemplateAnimatorProperty, value );
      }
    }

    #endregion

    #region VisualChildrenCount Protected Property

    protected override int VisualChildrenCount
    {
      get
      {
        int result = 0;

        if( this.HasLoaded && _currentLayoutPanel != null )
        {
          result = _currentLayoutPanel.VisualChildrenCountInternal;
        }
        else
        {
          result = base.VisualChildrenCount;
        }

        return result;
      }
    }

    #endregion

    #region ExitingChildren Internal Property

    internal List<UIElement> ExitingChildren
    {
      get
      {
        return _exitingChildren;
      }
    }

    private readonly List<UIElement> _exitingChildren = new List<UIElement>();

    #endregion

    #region ChildrenInternal Internal Property

    internal UIElementCollection ChildrenInternal
    {
      get
      {
        return this.InternalChildren;
      }
    }

    #endregion

    #region HasLoaded Internal Property

    internal bool HasLoaded
    {
      get
      {
        return _cacheBits[ ( int )CacheBits.HasLoaded ];
      }
      set
      {
        _cacheBits[ ( int )CacheBits.HasLoaded ] = value;
      }
    }

    #endregion

    #region IsScrollingPhysically Private Property

    private bool IsScrollingPhysically
    {
      get
      {
        bool isScrollingPhys = false;

        if( _scrollOwner != null )
        {
          isScrollingPhys = true;

          if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
          {
            isScrollingPhys = ( ( IScrollInfo )this.ActiveLayout ).ScrollOwner == null;
          }
        }

        return isScrollingPhys;
      }
    }

    #endregion

    #region ActiveLayoutChanged Event

    public static readonly RoutedEvent ActiveLayoutChangedEvent =
      EventManager.RegisterRoutedEvent( "ActiveLayoutChanged", RoutingStrategy.Bubble, typeof( RoutedEventHandler ), typeof( SwitchPanel ) );

    public event RoutedEventHandler ActiveLayoutChanged
    {
      add
      {
        this.AddHandler( SwitchPanel.ActiveLayoutChangedEvent, value );
      }
      remove
      {
        this.RemoveHandler( SwitchPanel.ActiveLayoutChangedEvent, value );
      }
    }

    protected RoutedEventArgs RaiseActiveLayoutChangedEvent()
    {
      return SwitchPanel.RaiseActiveLayoutChangedEvent( this );
    }

    internal static RoutedEventArgs RaiseActiveLayoutChangedEvent( UIElement target )
    {
      if( target == null )
        return null;

      RoutedEventArgs args = new RoutedEventArgs();
      args.RoutedEvent = SwitchPanel.ActiveLayoutChangedEvent;
      RoutedEventHelper.RaiseEvent( target, args );
      return args;
    }

    #endregion

    #region SwitchAnimationBegun Event

    public static readonly RoutedEvent SwitchAnimationBegunEvent =
      EventManager.RegisterRoutedEvent( "SwitchAnimationBegun", RoutingStrategy.Bubble, typeof( RoutedEventHandler ), typeof( SwitchPanel ) );

    public event RoutedEventHandler SwitchAnimationBegun
    {
      add
      {
        this.AddHandler( SwitchPanel.SwitchAnimationBegunEvent, value );
      }
      remove
      {
        this.RemoveHandler( SwitchPanel.SwitchAnimationBegunEvent, value );
      }
    }

    protected RoutedEventArgs RaiseSwitchAnimationBegunEvent()
    {
      return SwitchPanel.RaiseSwitchAnimationBegunEvent( this );
    }

    private static RoutedEventArgs RaiseSwitchAnimationBegunEvent( UIElement target )
    {
      if( target == null )
        return null;

      RoutedEventArgs args = new RoutedEventArgs();
      args.RoutedEvent = SwitchPanel.SwitchAnimationBegunEvent;
      RoutedEventHelper.RaiseEvent( target, args );
      return args;
    }

    #endregion

    #region SwitchAnimationCompleted Event

    public static readonly RoutedEvent SwitchAnimationCompletedEvent =
      EventManager.RegisterRoutedEvent( "SwitchAnimationCompleted", RoutingStrategy.Bubble, typeof( RoutedEventHandler ), typeof( SwitchPanel ) );

    public event RoutedEventHandler SwitchAnimationCompleted
    {
      add
      {
        this.AddHandler( SwitchPanel.SwitchAnimationCompletedEvent, value );
      }
      remove
      {
        this.RemoveHandler( SwitchPanel.SwitchAnimationCompletedEvent, value );
      }
    }

    protected RoutedEventArgs RaiseSwitchAnimationCompletedEvent()
    {
      return SwitchPanel.RaiseSwitchAnimationCompletedEvent( this );
    }

    private static RoutedEventArgs RaiseSwitchAnimationCompletedEvent( UIElement target )
    {
      if( target == null )
        return null;

      RoutedEventArgs args = new RoutedEventArgs();
      args.RoutedEvent = SwitchPanel.SwitchAnimationCompletedEvent;
      RoutedEventHelper.RaiseEvent( target, args );
      return args;
    }

    #endregion

    protected override Size MeasureOverride( Size availableSize )
    {
      AnimationPanel layout = ( Layouts.Count == 0 ) ? _defaultLayoutCanvas : this.ActiveLayout;
      Size measureSize = layout.MeasureChildrenCore( this.InternalChildren, availableSize );

      if( this.IsScrollingPhysically )
      {
        Size viewport = availableSize;
        Size extent = measureSize;

        //
        // Make sure our offset works with the new size of the panel.  We don't want to show
        // any whitespace if the user scrolled all the way down and then increased the size of the panel.
        //
        Vector newOffset = new Vector(
          Math.Max( 0, Math.Min( _offset.X, extent.Width - viewport.Width ) ),
          Math.Max( 0, Math.Min( _offset.Y, extent.Height - viewport.Height ) ) );

        this.SetScrollingData( viewport, extent, newOffset );
      }

      return measureSize;
    }

    protected override Size ArrangeOverride( Size finalSize )
    {
      AnimationPanel layout = ( Layouts.Count == 0 ) ? _defaultLayoutCanvas : this.ActiveLayout;

      if( this.IsScrollingPhysically )
      {
        layout.PhysicalScrollOffset = _offset;
      }
      else
      {
        layout.PhysicalScrollOffset = ZeroVector;
      }

      return layout.ArrangeChildrenCore( InternalChildren, finalSize );
    }

    protected override Visual GetVisualChild( int index )
    {
      if( this.HasLoaded && _currentLayoutPanel != null )
        return _currentLayoutPanel.GetVisualChildInternal( index );

      return base.GetVisualChild( index );
    }

    protected override void OnVisualChildrenChanged( DependencyObject visualAdded, DependencyObject visualRemoved )
    {
      // The OnNotifyVisualChildAdded/Removed methods get called for all animation panels within a 
      // SwitchPanel.Layouts collection, regardless of whether they are the active layout for the SwitchPanel.
      if( visualAdded is UIElement )
      {
        // do not issue notification for a child that is exiting
        if( _currentLayoutPanel == null || !_currentLayoutPanel.IsRemovingInternalChild )
        {
          foreach( AnimationPanel panel in Layouts )
          {
            panel.OnNotifyVisualChildAddedInternal( visualAdded as UIElement );
          }
        }
      }
      else if( visualRemoved is UIElement )
      {
        foreach( AnimationPanel panel in Layouts )
        {
          panel.OnNotifyVisualChildRemovedInternal( visualRemoved as UIElement );
        }
      }

      if( _currentLayoutPanel != null )
      {
        _currentLayoutPanel.OnSwitchParentVisualChildrenChanged( visualAdded, visualRemoved );
      }
      else
      {
        base.OnVisualChildrenChanged( visualAdded, visualRemoved );
      }
    }

    internal void AddVisualChildInternal( Visual child )
    {
      this.AddVisualChild( child );
    }

    internal void BeginLayoutSwitch()
    {
      this.RaiseSwitchAnimationBegunEvent();
    }

    internal void EndLayoutSwitch()
    {
      this.RaiseSwitchAnimationCompletedEvent();
    }

    internal Visual GetVisualChildInternal( int index )
    {
      // called from AnimationPanel to access base class method
      return base.GetVisualChild( index );
    }

    internal void OnVisualChildrenChangedInternal( DependencyObject visualAdded, DependencyObject visualRemoved )
    {
      base.OnVisualChildrenChanged( visualAdded, visualRemoved );
    }

    internal UIElement RegisterPresenter( SwitchPresenter presenter )
    {
      UIElement result = null;
      AnimationPanel ignore;
      result = AnimationPanel.FindAncestorChildOfAnimationPanel( presenter, out ignore );
      if( result != null )
      {
        _presenters.Add( presenter );
        presenter.SwapTheTemplate( ActiveSwitchTemplate, false );
      }
      return result;
    }

    internal void RemoveVisualChildInternal( Visual child )
    {
      this.RemoveVisualChild( child );
    }

    internal void UnregisterPresenter( SwitchPresenter presenter, DependencyObject container )
    {
      if( container != null )
      {
        _presenters.Remove( presenter );
        presenter.SwapTheTemplate( null, false );
      }
    }

    internal void UpdateSwitchTemplate()
    {
      this.SetActiveSwitchTemplate( ( this.ActiveLayout == null ) || ( this.ActiveLayout.SwitchTemplate == null )
          ? this.SwitchTemplate : this.ActiveLayout.SwitchTemplate );
    }

    private void OnLoaded( object sender, RoutedEventArgs e )
    {
      this.HasLoaded = true;

      // invalidate arrange to give enter animations a chance to run
      this.InvalidateArrange();
    }

    private void LayoutsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      if( e.Action != NotifyCollectionChangedAction.Move )
      {
        if( e.NewItems != null )
        {
          foreach( AnimationPanel panel in e.NewItems )
          {
            this.AddLogicalChild( panel );
            panel.SetSwitchParent( this );

            if( panel is IScrollInfo )
            {
              ( ( IScrollInfo )panel ).ScrollOwner = this.ScrollOwner;
            }

            if( this.IsLoaded )
            {
              foreach( UIElement child in this.InternalChildren )
              {
                if( child == null )
                  continue;

                panel.OnNotifyVisualChildAddedInternal( child );
              }
            }
          }
        }

        if( e.OldItems != null )
        {
          foreach( AnimationPanel panel in e.OldItems )
          {
            if( IsLoaded )
            {
              foreach( UIElement child in this.InternalChildren )
              {
                if( child == null )
                  continue;

                panel.OnNotifyVisualChildRemovedInternal( child );
              }
            }

            this.RemoveLogicalChild( panel );
            panel.SetSwitchParent( null );

            if( panel is IScrollInfo )
            {
              ( ( IScrollInfo )panel ).ScrollOwner = null;
            }
          }
        }
      }

      // ensure valid ActiveLayoutIndex value
      this.CoerceValue( SwitchPanel.ActiveLayoutIndexProperty );
      this.SetActiveLayout( ( this.Layouts.Count == 0 ) ? null : this.Layouts[ ActiveLayoutIndex ] );
    }

    private void ResetScrollInfo()
    {
      _offset = new Vector();
      _viewport = _extent = new Size( 0, 0 );
    }

    private void OnScrollChange()
    {
      if( this.ScrollOwner != null )
      {
        this.ScrollOwner.InvalidateScrollInfo();
      }
    }

    private void SetScrollingData( Size viewport, Size extent, Vector offset )
    {
      _offset = offset;

      if( DoubleHelper.AreVirtuallyEqual( viewport, _viewport ) == false || DoubleHelper.AreVirtuallyEqual( extent, _extent ) == false ||
          DoubleHelper.AreVirtuallyEqual( offset, _computedOffset ) == false )
      {
        _viewport = viewport;
        _extent = extent;
        _computedOffset = offset;
        this.OnScrollChange();
      }
    }

    private double ValidateInputOffset( double offset, string parameterName )
    {
      if( double.IsNaN( offset ) )
        throw new ArgumentOutOfRangeException( parameterName );

      return Math.Max( 0d, offset );
    }

    private int FindChildFromVisual( Visual vis )
    {
      int index = -1;

      DependencyObject parent = vis;
      DependencyObject child = null;

      do
      {
        child = parent;
        parent = VisualTreeHelper.GetParent( child );
      }
      while( parent != null && parent != this );

      if( parent == this )
      {
        index = this.Children.IndexOf( ( UIElement )child );
      }

      return index;
    }

    #region IScrollInfo Members

    public bool CanHorizontallyScroll
    {
      get
      {
        if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
          return ( ( IScrollInfo )this.ActiveLayout ).CanHorizontallyScroll;

        return _allowHorizontal;
      }
      set
      {
        if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
        {
          ( ( IScrollInfo )this.ActiveLayout ).CanHorizontallyScroll = value;
        }
        else
        {
          _allowHorizontal = value;
        }
      }
    }

    public bool CanVerticallyScroll
    {
      get
      {
        if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
          return ( ( IScrollInfo )this.ActiveLayout ).CanVerticallyScroll;

        return _allowVertical;
      }
      set
      {
        if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
        {
          ( ( IScrollInfo )this.ActiveLayout ).CanVerticallyScroll = value;
        }
        else
        {
          _allowVertical = value;
        }
      }
    }

    public double ExtentHeight
    {
      get
      {
        if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
          return ( ( IScrollInfo )this.ActiveLayout ).ExtentHeight;

        return _extent.Height;
      }
    }

    public double ExtentWidth
    {
      get
      {
        if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
          return ( ( IScrollInfo )this.ActiveLayout ).ExtentWidth;

        return _extent.Width;
      }
    }

    public double HorizontalOffset
    {
      get
      {
        if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
          return ( ( IScrollInfo )this.ActiveLayout ).HorizontalOffset;

        return _offset.X;
      }
    }

    public void LineDown()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )this.ActiveLayout ).LineDown();
      }
      else
      {
        this.SetVerticalOffset( this.VerticalOffset + 1d );
      }
    }

    public void LineLeft()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )this.ActiveLayout ).LineLeft();
      }
      else
      {
        this.SetHorizontalOffset( this.VerticalOffset - 1d );
      }
    }

    public void LineRight()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )this.ActiveLayout ).LineRight();
      }
      else
      {
        this.SetHorizontalOffset( this.VerticalOffset + 1d );
      }
    }

    public void LineUp()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )this.ActiveLayout ).LineUp();
      }
      else
      {
        this.SetVerticalOffset( this.VerticalOffset + 1d );
      }
    }

    public Rect MakeVisible( Visual visual, Rect rectangle )
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
        return ( ( IScrollInfo )this.ActiveLayout ).MakeVisible( visual, rectangle );

      if( ( rectangle.IsEmpty || ( visual == null ) ) || ( ( visual == this ) || !this.IsAncestorOf( visual ) ) )
        return Rect.Empty;

      rectangle = visual.TransformToAncestor( ( Visual )this ).TransformBounds( rectangle );

      if( this.IsScrollingPhysically == false )
        return rectangle;

      //
      // Make sure we can find the child...
      //
      int index = this.FindChildFromVisual( visual );

      if( index == -1 )
        throw new ArgumentException( "visual" );

      //
      // Since our _Offset pushes the items down we need to correct it here to 
      // give a true rectangle of the child.
      //
      Rect itemRect = rectangle;
      itemRect.Offset( _offset );

      Rect viewRect = new Rect( new Point( _offset.X, _offset.Y ), _viewport );

      Vector newPhysOffset;
      if( ScrollHelper.ScrollLeastAmount( viewRect, itemRect, out newPhysOffset ) )
      {
        this.SetHorizontalOffset( newPhysOffset.X );
        this.SetVerticalOffset( newPhysOffset.Y );
      }

      return rectangle;
    }

    public void MouseWheelDown()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )ActiveLayout ).MouseWheelDown();
      }
      else
      {
        this.SetVerticalOffset( this.VerticalOffset + SystemParameters.WheelScrollLines );
      }
    }

    public void MouseWheelLeft()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )this.ActiveLayout ).MouseWheelLeft();
      }
      else
      {
        this.SetVerticalOffset( this.VerticalOffset - 3d );
      }
    }

    public void MouseWheelRight()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )this.ActiveLayout ).MouseWheelRight();
      }
      else
      {
        this.SetVerticalOffset( this.VerticalOffset + 3d );
      }
    }

    public void MouseWheelUp()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )this.ActiveLayout ).MouseWheelUp();
      }
      else
      {
        this.SetVerticalOffset( this.VerticalOffset - SystemParameters.WheelScrollLines );
      }
    }

    public void PageDown()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )this.ActiveLayout ).PageDown();
      }
      else
      {
        this.SetVerticalOffset( this.VerticalOffset + this.ViewportHeight );
      }
    }

    public void PageLeft()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )this.ActiveLayout ).PageLeft();
      }
      else
      {
        this.SetHorizontalOffset( this.HorizontalOffset - this.ViewportWidth );
      }
    }

    public void PageRight()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )this.ActiveLayout ).PageRight();
      }
      else
      {
        this.SetHorizontalOffset( this.HorizontalOffset + this.ViewportWidth );
      }
    }

    public void PageUp()
    {
      if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
      {
        ( ( IScrollInfo )this.ActiveLayout ).PageUp();
      }
      else
      {
        this.SetVerticalOffset( this.VerticalOffset - this.ViewportHeight );
      }
    }

    public ScrollViewer ScrollOwner
    {
      get
      {
        return _scrollOwner;
      }
      set
      {
        foreach( AnimationPanel layout in this.Layouts )
        {
          if( layout != null && layout is IScrollInfo )
          {
            ( ( IScrollInfo )layout ).ScrollOwner = value;
          }
        }

        if( _scrollOwner != value )
        {
          _scrollOwner = value;

          this.ResetScrollInfo();
        }
      }
    }

    public void SetHorizontalOffset( double offset )
    {
      offset = this.ValidateInputOffset( offset, "HorizontalOffset" );

      offset = Math.Min( offset, this.ExtentWidth - this.ViewportWidth );

      if( !DoubleHelper.AreVirtuallyEqual( offset, _offset.X ) )
      {
        _offset.X = offset;
        base.InvalidateMeasure();
      }
    }

    public void SetVerticalOffset( double offset )
    {
      offset = this.ValidateInputOffset( offset, "VerticalOffset" );

      offset = Math.Min( offset, this.ExtentHeight - this.ViewportHeight );

      if( !DoubleHelper.AreVirtuallyEqual( offset, _offset.Y ) )
      {
        _offset.Y = offset;
        base.InvalidateMeasure();
      }
    }

    public double VerticalOffset
    {
      get
      {
        if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
        {
          return ( ( IScrollInfo )this.ActiveLayout ).VerticalOffset;
        }

        return _offset.Y;
      }
    }

    public double ViewportHeight
    {
      get
      {
        if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
        {
          return ( ( IScrollInfo )this.ActiveLayout ).ViewportHeight;
        }

        return _viewport.Height;
      }
    }

    public double ViewportWidth
    {
      get
      {
        if( this.ActiveLayout != null && this.ActiveLayout is IScrollInfo )
        {
          return ( ( IScrollInfo )this.ActiveLayout ).ViewportWidth;
        }

        return _viewport.Width;
      }
    }

    #endregion

    #region Private Fields

    internal AnimationPanel _currentLayoutPanel = null;

    private readonly AnimationPanel _defaultLayoutCanvas = new Xceed.Wpf.Toolkit.Panels.WrapPanel();
    private readonly Collection<SwitchPresenter> _presenters = new Collection<SwitchPresenter>();

    private BitVector32 _cacheBits = new BitVector32( 0 );

    private bool _allowHorizontal = false;
    private bool _allowVertical = false;
    private Vector _computedOffset = new Vector( 0.0, 0.0 );
    private Size _extent = new Size( 0, 0 );
    private Vector _offset = new Vector( 0, 0 );
    private ScrollViewer _scrollOwner = null;
    private Size _viewport;

    #endregion

    #region CacheBits Nested Type

    private enum CacheBits
    {
      HasLoaded = 0x00000001,
    }

    #endregion
  }
}
