// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace AjaxControlToolkit
{
    /// <summary>
    /// The Accordion control represents a series of panes that can be viewed
    /// one at a time.  The control is used to create "strongly typed" access
    /// to the AccordionBehavior.  Its major purpose is to structure the content
    /// in a way that the AccordionBehavior can understand it.  We need a series
    /// of HTML container elements, such as divs, that looks like the following:
    ///   <div id="Accordion">
    ///     <span id="AccordionPane1">
    ///       <div id="AccordionPane1Header"></div>
    ///       <div id="AccordionPane1Content"></div>
    ///     </span>
    ///     <span id="AccordionPane2">
    ///       <div id="AccordionPane2Header"></div>
    ///       <div id="AccordionPane2Content"></div>
    ///     </span>
    ///     .
    ///     .
    ///     .
    ///   </div>
    /// </summary>
    [ParseChildren(true)]
    [PersistChildren(true)]
    [Designer("AjaxControlToolkit.AccordionDesigner, AjaxControlToolkit")]
    [ToolboxData("<{0}:Accordion runat=server></{0}:Accordion>")]
    [System.Drawing.ToolboxBitmap(typeof(Accordion), "Accordion.Accordion.ico")]
    public class Accordion : WebControl
    {
        /// <summary>
        /// ViewState key for tracking the number of panes in the Accordion
        /// </summary>
        internal const string ItemCountViewStateKey = "_!ItemCount";

        /// <summary>
        /// Event to raise when an item (i.e. Pane's Header or Content) is
        /// created during data binding
        /// </summary>
        public event EventHandler<AccordionItemEventArgs> ItemCreated;

        /// <summary>
        /// Event to raise when an item (i.e. Pane's Header or Content) is
        /// data bound
        /// </summary>
        public event EventHandler<AccordionItemEventArgs> ItemDataBound;

        /// <summary>
        /// Event to raise when a command is fired
        /// </summary>
        public event CommandEventHandler ItemCommand;

        /// <summary>
        /// AccordionExtender to attach
        /// </summary>
        private AccordionExtender _extender;

        /// <summary>
        /// The Accordion's child panes
        /// </summary>
        private AccordionPaneCollection _panes;

#region DataBinding Fields

        /// <summary>
        /// DataSource to bind the Accordion to
        /// </summary>
        private object _dataSource;

        /// <summary>
        /// DataBinding template for the header
        /// </summary>
        private ITemplate _headerTemplate;

        /// <summary>
        /// DataBinding template for the content
        /// </summary>
        private ITemplate _contentTemplate;

        /// <summary>
        /// Whether or not the control has been initialized
        /// </summary>
        private bool _initialized;

        /// <summary>
        /// Whether the page's PreLoad event has already fired
        /// </summary>
        private bool _pagePreLoadFired;

        /// <summary>
        /// Whether or not the Accordion needs to be databound but hasn't been yet
        /// </summary>
        private bool _requiresDataBinding;

        /// <summary>
        /// Flag to determine if we should throw an exception when a data property
        /// (i.e. DataSource, DataSourceID, DataMember) is changed
        /// </summary>
        private bool _throwOnDataPropertyChange;

        /// <summary>
        /// View of the the data provided by the data property
        /// </summary>
        private DataSourceView _currentView;

        /// <summary>
        /// Whether the current DataSourceView was loaded from a DataSourceID
        /// </summary>
        private bool _currentViewIsFromDataSourceID;

        /// <summary>
        /// Whether the current DataSourceView contains valid data
        /// </summary>
        private bool _currentViewValid;

        /// <summary>
        /// Arguments used to sort, filter, etc., the data when creating
        /// the DataSourceView (although we will use the default whenever possible)
        /// </summary>
        private DataSourceSelectArguments _arguments;

        /// <summary>
        /// Enumerable list of data items obtained from the DataSource
        /// </summary>
        IEnumerable _selectResult;

        /// <summary>
        /// Thread synchronization event used for obtaining data from the DataSource
        /// </summary>
        EventWaitHandle _selectWait;

#endregion

        /// <summary>
        /// Default constructor that tells ASP.NET to render it as a DIV
        /// </summary>
        public Accordion()
            : base(HtmlTextWriterTag.Div)
        {
        }

        /// <summary>
        /// Reference to the AccordionExtender wrapped by the Accordion control
        /// </summary>
        /// <remarks>
        /// This will be referenced in CreateChildControls so that the extender
        /// will always be created by any calls to EnsureChildControls.
        /// </remarks>
        private AccordionExtender AccordionExtender
        {
            get
            {
                if (_extender == null)
                {
                    // Create the extender
                    _extender = new AccordionExtender();
                    _extender.ID = ID + "_AccordionExtender";
                    _extender.TargetControlID = ID;
                    Controls.AddAt(0, _extender);
                }
                return _extender;
            }
        }

        /// <summary>
        /// Length of the transition animation in milliseconds
        /// </summary>
        [Browsable(true)]
        [Category("Behavior")]
        [Description("Length of the transition animation in milliseconds")]
        [DefaultValue(500)]
        public int TransitionDuration
        {
            get { return AccordionExtender.TransitionDuration; }
            set { AccordionExtender.TransitionDuration = value; }
        }

        /// <summary>
        /// The number of frames per second used in the transition animation effects.
        /// This is used to tune performance when using FadeTransition, a large number
        /// of Accordion Panes, etc.
        /// </summary>
        [Browsable(true)]
        [Category("Behavior")]
        [Description("Number of frames per second used in the transition animation")]
        [DefaultValue(15)]
        public int FramesPerSecond
        {
            get { return AccordionExtender.FramesPerSecond; }
            set { AccordionExtender.FramesPerSecond = value; }
        }

        /// <summary>
        /// Whether or not to use a fade effect when transitioning between selected
        /// Accordion Panes
        /// </summary>
        [Browsable(true)]
        [Category("Behavior")]
        [Description("Whether or not to use a fade effect in the transition animations")]
        [DefaultValue(false)]
        public bool FadeTransitions
        {
            get { return AccordionExtender.FadeTransitions; }
            set { AccordionExtender.FadeTransitions = value; }
        }

        /// <summary>
        /// Default Header CSS Class
        /// </summary>
        [Browsable(true)]
        [Category("Appearance")]
        [Description("Default CSS class for Accordion Pane Headers")]
        public string HeaderCssClass
        {
            get { return AccordionExtender.HeaderCssClass; }
            set { AccordionExtender.HeaderCssClass = value; }
        }

        /// <summary>
        /// Default selected Header CSS Class
        /// </summary>
        [Browsable(true)]
        [Category("Appearance")]
        [Description("Default CSS class for the selected Accordion Pane Headers")]
        public string HeaderSelectedCssClass
        {
            get { return AccordionExtender.HeaderSelectedCssClass; }
            set { AccordionExtender.HeaderSelectedCssClass = value; }
        }

        /// <summary>
        /// Default Content CSS Class
        /// </summary>
        [Browsable(true)]
        [Category("Appearance")]
        [Description("Default CSS class for Accordion Pane Content")]
        public string ContentCssClass
        {
            get { return AccordionExtender.ContentCssClass; }
            set { AccordionExtender.ContentCssClass = value; }
        }

        /// <summary>
        /// Determine how growth of the Accordion will be controlled.  If it is set to
        /// None, then the Accordion can grow as large or as small as necessary.  If it is
        /// set to Limit, then the Accordion will always be less than or equal to its
        /// Height.  If it is set to Fill then it will always be equal to its height.
        /// </summary>
        [Browsable(true)]
        [Category("Behavior")]
        [Description("Determine how the growth of the Accordion will be controlled")]
        [DefaultValue(AutoSize.None)]
        public AutoSize AutoSize
        {
            get { return AccordionExtender.AutoSize; }
            set { AccordionExtender.AutoSize = value; }
        }

        /// <summary>
        /// Index of the AccordionPane to be displayed
        /// (this property must be set before OnPreRender)
        /// </summary>
        [Browsable(true)]
        [Category("Behavior")]
        [Description("Index of the AccordionPane to be displayed")]
        [DefaultValue(0)]
        public int SelectedIndex
        {
            get { return AccordionExtender.SelectedIndex; }
            set { AccordionExtender.SelectedIndex = value; }
        }

        /// <summary>
        /// Whether or not clicking the header will close the currently opened pane (leaving
        /// all the Accordion's panes closed)
        /// </summary>
        [Browsable(true)]
        [Category("Behavior")]
        [Description("Whether or not clicking the header will close the currently opened pane (leaving all the Accordion's panes closed)")]
        [DefaultValue(true)]
        public bool RequireOpenedPane
        {
            get { return AccordionExtender.RequireOpenedPane; }
            set { AccordionExtender.RequireOpenedPane = value; }
        }

        /// <summary>
        /// Whether or not we suppress the client-side click handlers of any elements (including server
        /// controls like Button or HTML elements like anchor) in the header sections of the Accordion.
        /// </summary>
        [Browsable(true)]
        [Category("Behavior")]
        [Description("Whether or not we suppress the client-side click handlers of any elements in the header sections")]
        [DefaultValue(false)]
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Postbacks", Justification="ASP.NET term")]
        public bool SuppressHeaderPostbacks
        {
            get { return AccordionExtender.SuppressHeaderPostbacks; }
            set { AccordionExtender.SuppressHeaderPostbacks = value; }
        }

        /// <summary>
        /// Collection of child panes in the Accordion
        /// </summary>
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public AccordionPaneCollection Panes
        {
            get
            {
                if (_panes == null)
                    _panes = new AccordionPaneCollection(this);
                return _panes;
            }
        }

        /// <summary>
        /// Prevent the Controls property from appearing in the editor (so
        /// that people will use the Panes collection instead)
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override ControlCollection Controls
        {
            get { return base.Controls; }
        }

#region DataBinding Properties

        /// <summary>
        /// Template for the Header of databound panes
        /// </summary>
        [Browsable(false)]
        [DefaultValue(null)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof(AccordionContentPanel))]
        public virtual ITemplate HeaderTemplate
        {
            get { return _headerTemplate; }
            set { _headerTemplate = value; }
        }

        /// <summary>
        /// Template for the Content of databound panes
        /// </summary>
        [Browsable(false)]
        [DefaultValue(null)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof(AccordionContentPanel))]
        public virtual ITemplate ContentTemplate
        {
            get { return _contentTemplate; }
            set { _contentTemplate = value; }
        }

        /// <summary>
        /// Gets or sets the data source that provides data for populating
        /// the list of AccordionPanes.
        /// </summary>
        [Bindable(true)]
        [Category("Data")]
        [DefaultValue(null)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", Justification = "Assembly is not localized")]
        public virtual object DataSource
        {
            get { return _dataSource; }
            set
            {
                if ((value == null) || (value is IListSource) || (value is IEnumerable))
                {
                    _dataSource = value;
                    OnDataPropertyChanged();
                }
                else
                {
                    throw new ArgumentException("Can't bind to value that is not an IListSource or an IEnumerable.");
                }
            }
        }

        /// <summary>
        /// The ID of the DataControl that this control should use to retrieve
        /// its data source. When the control is bound to a DataControl, it
        /// can retrieve a data source instance on-demand, and thereby attempt
        /// to work in auto-DataBind mode.
        /// </summary>
        [DefaultValue("")]
        [IDReferenceProperty(typeof(DataSourceControl))]
        [Category("Data")]
        [SuppressMessage("Microsoft.Naming", "CA1706:ShortAcronymsShouldBeUppercase", MessageId = "Member", Justification = "Following ASP.NET Convention")]
        public virtual string DataSourceID
        {
            get { return ViewState["DataSourceID"] as string ?? string.Empty; }
            set
            {
                ViewState["DataSourceID"] = value;
                OnDataPropertyChanged();
            }
        }

        /// <summary>
        /// Member in the DataSource to bind to
        /// </summary>
        [DefaultValue("")]
        [Category("Data")]
        public virtual string DataMember
        {
            get { return ViewState["DataMember"] as string ?? string.Empty; }
            set
            {
                ViewState["DataMember"] = value;
                OnDataPropertyChanged();
            }
        }

        /// <summary>
        /// Whether or not the Accordion was databound using the DataSourceID
        /// property rather than setting the DataSource directly
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1706:ShortAcronymsShouldBeUppercase", MessageId = "Member", Justification = "Following ASP.NET Convention")]
        protected bool IsBoundUsingDataSourceID
        {
            get { return !string.IsNullOrEmpty(DataSourceID); }
        }

        /// <summary>
        /// Whether or not the control has already been databound, or still needs
        /// to be databound
        /// </summary>
        protected bool RequiresDataBinding
        {
            get { return _requiresDataBinding; }
            set { _requiresDataBinding = value; }
        }

        /// <summary>
        /// Arguments used to request data-related operations from
        /// data source controls when data is retrieved
        /// </summary>
        protected DataSourceSelectArguments SelectArguments
        {
            get
            {
                if (_arguments == null)
                    _arguments = CreateDataSourceSelectArguments();
                return _arguments;
            }
        }

#endregion

        /// <summary>
        /// OnInit handler to wireup the Page's PreLoad event
        /// </summary>
        /// <param name="e">EventArgs</param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            if (Page != null)
            {
                Page.PreLoad += new EventHandler(this.OnPagePreLoad);
                if (!IsViewStateEnabled && Page.IsPostBack)
                    RequiresDataBinding = true;
            }
        }

        /// <summary>
        /// OnPreLoad is used to determine whether or not we still need to databind the Accordion
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="e">EventArgs</param>
        private void OnPagePreLoad(object sender, EventArgs e)
        {
            _initialized = true;

            if (Page != null)
            {
                Page.PreLoad -= new EventHandler(this.OnPagePreLoad);

                // Setting RequiresDataBinding to true in OnLoad is too late because the OnLoad page event
                // happens before the control.OnLoad method gets called.  So a page_load handler on the page
                // that calls DataBind won't prevent DataBind from getting called again in PreRender.
                if (!Page.IsPostBack)
                    RequiresDataBinding = true;

                // If this is a postback and viewstate is enabled, but we have never bound the control
                // before, it is probably because its visibility was changed in the postback.  In this
                // case, we need to bind the control or it will never appear.  This is a common scenario
                // for Wizard and MultiView.
                if (Page.IsPostBack && IsViewStateEnabled && ViewState[ItemCountViewStateKey] == null)
                    RequiresDataBinding = true;

                _pagePreLoadFired = true;
            }

            EnsureChildControls();
        }

        /// <summary>
        /// Connect to the DataSourceView and determine if we still need to
        /// do databinding
        /// </summary>
        /// <param name="e">EventArgs</param>
        protected override void OnLoad(EventArgs e)
        {
            _initialized = true; // just in case we were added to the page after PreLoad
            ConnectToDataSourceView();
            if (Page != null && !_pagePreLoadFired && ViewState[ItemCountViewStateKey] == null)
            {
                // If the control was added after PagePreLoad, we still need to databind it because it missed its
                // first change in PagePreLoad.  If this control was created by a call to a parent control's DataBind
                // in Page_Load (with is relatively common), this control will already have been databound even
                // though pagePreLoad never fired and the page isn't a postback.
                if (!Page.IsPostBack)
                {
                    RequiresDataBinding = true;
                }
                // If the control was added to the page after page.PreLoad, we'll never get the event and we'll
                // never databind the control.  So if we're catching up and Load happens but PreLoad never happened,
                // call DataBind.  This may make the control get databound twice if the user called DataBind on the control
                // directly in Page.OnLoad, but better to bind twice than never to bind at all.
                else if (IsViewStateEnabled)
                {
                    RequiresDataBinding = true;
                }
            }

            base.OnLoad(e);
        }

        /// <summary>
        /// Create the AccordionExtender and attach it to the div
        /// that will be generated for this control
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "controls")]
        protected override void CreateChildControls()
        {
            base.CreateChildControls();
                        
            // If we already have items in the ViewState, create the control
            // hierarchy using the view state (and not the datasource)
            if (AccordionExtender != null && ViewState[ItemCountViewStateKey] != null)
                CreateControlHierarchy(false);
            
            ClearChildViewState();

            // Ensure creation of child controls
            foreach (AccordionPane pane in Panes)
            {
                ControlCollection controls = pane.Controls;
            }
        }

        /// <summary>
        /// Mark the selected AccordionPane so it does not appear collapsed
        /// </summary>
        /// <param name="e">EventArgs</param>
        protected override void OnPreRender(EventArgs e)
        {
            EnsureDataBound();
            base.OnPreRender(e);

            // Set the overflow to hidden to prevent any growth from
            // showing initially before it is hidden by the script if
            // we are controlling the height
            if (AutoSize != AutoSize.None)
            {
                Style[HtmlTextWriterStyle.Overflow] = "hidden";
                Style[HtmlTextWriterStyle.OverflowX] = "auto";
            }

            // Apply the standard header/content styles, but allow the
            // pane's styles to take precedent            
            foreach (AccordionPane pane in Panes)
            {
                if (!string.IsNullOrEmpty(HeaderCssClass) && string.IsNullOrEmpty(pane.HeaderCssClass))
                    pane.HeaderCssClass = HeaderCssClass;
                if (!string.IsNullOrEmpty(ContentCssClass) && string.IsNullOrEmpty(pane.ContentCssClass))
                    pane.ContentCssClass = ContentCssClass;
            }

            // Get the index of the selected pane, or use the first pane if we don't
            // have a valid index and require one.  (Note: We don't reset the SelectedIndex
            // property because it may refer to a pane that will be added dynamically on the
            // client.  If we need to start with a pane visible, then we'll open the first
            // pane because that's the default value used on the client as the SelectedIndex
            // in this scenario.)
            int index = AccordionExtender.SelectedIndex;
            index = ((index < 0 || index >= Panes.Count) && AccordionExtender.RequireOpenedPane) ? 0 : index;

            // Make sure the selected pane is displayed
            if (index >= 0 && index < Panes.Count)
            {
                AccordionContentPanel content = Panes[index].ContentContainer;
                if (content != null)
                    content.Collapsed = false;

                // Set the CSS class for the open panes header
                if (!string.IsNullOrEmpty(HeaderSelectedCssClass))
                    Panes[index].HeaderCssClass = HeaderSelectedCssClass;
            }
        }

        /// <summary>
        /// Override FindControl to look first at this control, then check each
        /// of its child AccordionPanes for the control
        /// </summary>
        /// <param name="id">ID of the control</param>
        /// <returns>Control</returns>
        public override Control FindControl(string id)
        {
            Control ctrl = base.FindControl(id);
            if (ctrl == null)
                foreach (AccordionPane pane in Panes)
                {
                    ctrl = pane.FindControl(id);
                    if (ctrl != null)
                        break;
                }
            return ctrl;
        }

        /// <summary>
        /// Empty out the child Pane's collection
        /// </summary>
        internal void ClearPanes()
        {
            for (int i = Controls.Count - 1; i >= 0; i--)
                if (Controls[i] is AccordionPane)
                    Controls.RemoveAt(i);
        }

#region DataBinding

        /// <summary>
        /// Connects this data bound control to the appropriate DataSourceView
        /// and hooks up the appropriate event listener for the
        /// DataSourceViewChanged event. The return value is the new view (if
        /// any) that was connected to. An exception is thrown if there is
        /// a problem finding the requested view or data source.
        /// </summary>
        /// <returns>New View</returns>
        [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", Justification = "Assembly is not localized")]
        private DataSourceView ConnectToDataSourceView()
        {
            // If the current view is correct, there is no need to reconnect
            if (_currentViewValid && !DesignMode)
                return _currentView;

            // Disconnect from old view, if necessary
            if ((_currentView != null) && (_currentViewIsFromDataSourceID))
            {
                // We only care about this event if we are bound through the DataSourceID property
                _currentView.DataSourceViewChanged -= new EventHandler(OnDataSourceViewChanged);
            }

            // Connect to new view
            IDataSource ds = null;
            string dataSourceID = DataSourceID;

            if (!string.IsNullOrEmpty(dataSourceID))
            {
                // Try to find a DataSource control with the ID specified in DataSourceID
                Control control = NamingContainer.FindControl(dataSourceID);
                if (control == null)
                    throw new HttpException(String.Format(CultureInfo.CurrentCulture, "DataSource '{1}' for control '{0}' doesn't exist", ID, dataSourceID));
                ds = control as IDataSource;
                if (ds == null)
                    throw new HttpException(String.Format(CultureInfo.CurrentCulture, "'{1}' is not a data source for control '{0}'.", ID, dataSourceID));
            }

            if (ds == null)
            {
                // DataSource control was not found, construct a temporary data source to wrap the data
                return null;
            }
            else
            {
                // Ensure that both DataSourceID as well as DataSource are not set at the same time
                if (DataSource != null)
                    throw new InvalidOperationException("DataSourceID and DataSource can't be set at the same time.");
            }

            // IDataSource was found, extract the appropriate view and return it
            DataSourceView newView = ds.GetView(DataMember);
            if (newView == null)
                throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, "DataSourceView not found for control '{0}'", ID));

            _currentViewIsFromDataSourceID = IsBoundUsingDataSourceID;
            _currentView = newView;
            // If we're bound through the DataSourceID proeprty, then we care about this event
            if ((_currentView != null) && (_currentViewIsFromDataSourceID))
                _currentView.DataSourceViewChanged += new EventHandler(OnDataSourceViewChanged);
            _currentViewValid = true;

            return _currentView;
        }

        /// <summary>
        /// Bind the Accordion to its DataSource
        /// </summary>
        public override void DataBind()
        {
            // Don't databind to a data source control when the control is in the designer but not top-level
            if (IsBoundUsingDataSourceID && DesignMode && (Site == null))
                return;

            // do our own databinding
            RequiresDataBinding = false;
            OnDataBinding(EventArgs.Empty);
        }

        /// <summary>
        /// DataBind the Accordion to its panes
        /// </summary>
        /// <param name="e">EventArgs</param>
        protected override void OnDataBinding(EventArgs e)
        {
            base.OnDataBinding(e);

            //Only bind if the control has the DataSource or DataSourceID set
            if (this.DataSource!=null || IsBoundUsingDataSourceID)
            {
                // reset the control state
                ClearPanes();
                ClearChildViewState();

                // and then create the control hierarchy using the datasource
                CreateControlHierarchy(true);
                ChildControlsCreated = true;
            }
        }

        /// <summary>
        /// Create the new control hierarchy of AccordionPanes
        /// (using the DataSource if specificed)
        /// </summary>
        /// <param name="useDataSource">Whether or not we should use the DataSource</param>
        protected virtual void CreateControlHierarchy(bool useDataSource)
        {
            int count = -1;
            IEnumerable dataSource = null;
            List<AccordionPane> itemsArray = new List<AccordionPane>();

            if (!useDataSource)
            {
                object viewCount = ViewState[ItemCountViewStateKey];

                // ViewState must have a non-null value for ItemCount because we check for
                // this in CreateChildControls
                if (viewCount != null)
                {
                    count = (int)viewCount;
                    if (count != -1)
                    {
                        List<object> dummyList = new List<object>(count);
                        for (int i = 0; i < count; i++)
                            dummyList.Add(null);
                        dataSource = dummyList;
                        itemsArray.Capacity = count;
                    }
                }
            }
            else
            {
                dataSource = GetData();
                count = 0;
                ICollection collection = dataSource as ICollection;
                if (collection != null)
                    itemsArray.Capacity = collection.Count;
            }

            if (dataSource != null)
            {
                int index = 0;
                foreach (object dataItem in dataSource)
                {
                    AccordionPane ap = new AccordionPane();
                    Controls.Add(ap);

                    CreateItem(dataItem, index, AccordionItemType.Header, ap.HeaderContainer, HeaderTemplate, useDataSource);
                    CreateItem(dataItem, index, AccordionItemType.Content, ap.ContentContainer, ContentTemplate, useDataSource);

                    itemsArray.Add(ap);

                    count++;
                    index++;
                }
            }

            // If we're binding, save the number of items contained in the repeater for use in round-trips
            if (useDataSource)
                ViewState[ItemCountViewStateKey] = ((dataSource != null) ? count : -1);
        }

        /// <summary>
        /// Create an AccordionPane's item (either Header or Content) and raise the ItemCreated event
        /// </summary>
        /// <param name="dataItem">Item's data</param>
        /// <param name="index">Index</param>
        /// <param name="itemType">Type of the item (Header or Content)</param>
        /// <param name="container">Control to fill</param>
        /// <param name="template">Template for the binding</param>
        /// <param name="dataBind">Whether or not to bind</param>
        private void CreateItem(object dataItem, int index, AccordionItemType itemType, AccordionContentPanel container, ITemplate template, bool dataBind)
        {

            if (template == null)
                return;

            AccordionItemEventArgs itemArgs = new AccordionItemEventArgs(container, itemType);
            OnItemCreated(itemArgs);

            container.SetDataItemProperties(dataItem, index, itemType);
            template.InstantiateIn(container);

            if (dataBind)
            {
                container.DataBind();
                OnItemDataBound(itemArgs);
            }
        }

        /// <summary>
        /// Ensure that the Accordion has been databound if it needed to be
        /// </summary>
        protected void EnsureDataBound()
        {
            try
            {
                _throwOnDataPropertyChange = true;
                if (RequiresDataBinding && !string.IsNullOrEmpty(DataSourceID))
                    DataBind();
            }
            finally
            {
                _throwOnDataPropertyChange = false;
            }
        }

        /// <summary>
        /// Returns an IEnumerable that is the DataSource, which either came
        /// from the DataSource property or from the control bound via the
        /// DataSourceID property.
        /// </summary>
        /// <returns>DataSource</returns>
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Performs complex action")]
        protected virtual IEnumerable GetData()
        {
            _selectResult = null;
            DataSourceView view = ConnectToDataSourceView();
            if (view != null)
            {
                Debug.Assert(_currentViewValid);

                // create a handle here to make sure this is a synchronous operation.
                _selectWait = new EventWaitHandle(false, EventResetMode.AutoReset);
                view.Select(SelectArguments, new DataSourceViewSelectCallback(DoSelect));
                _selectWait.WaitOne();

            }
            else if (DataSource != null)
            {
                _selectResult = DataSource as IEnumerable;
            }
            return _selectResult;
        }

        /// <summary>
        /// Create the DataSourceSelectArguments (which just defaults to the Empty value
        /// because we don't want to sort, filter, etc.)
        /// </summary>
        /// <returns>DataSourceSelectArguments</returns>
        protected virtual DataSourceSelectArguments CreateDataSourceSelectArguments()
        {
            return DataSourceSelectArguments.Empty;
        }

        /// <summary>
        /// Select the data
        /// </summary>
        /// <param name="data">Data</param>
        private void DoSelect(IEnumerable data)
        {
            _selectResult = data;
            _selectWait.Set();
        }

        /// <summary>
        /// Wrap the CommandArgs of an ItemCommand event with AccordionCommandEventArgs
        /// </summary>
        /// <param name="source">Source</param>
        /// <param name="args">EventArgs</param>
        /// <returns>Whether the event was handled</returns>
        protected override bool OnBubbleEvent(object source, EventArgs args)
        {
            bool handled = false;
            AccordionCommandEventArgs accordionArgs = args as AccordionCommandEventArgs;
            if (accordionArgs != null)
            {
                OnItemCommand(accordionArgs);
                handled = true;
            }
            return handled;
        }

        /// <summary>
        /// This method is called when DataMember, DataSource, or DataSourceID is changed.
        /// </summary>
        [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", Justification = "Assembly is not localized")]
        protected virtual void OnDataPropertyChanged()
        {
            if (_throwOnDataPropertyChange)
                throw new HttpException("Invalid data property change");
            if (_initialized)
                RequiresDataBinding = true;
            _currentViewValid = false;
        }

        /// <summary>
        /// Indicate that we need to be databound whenever the DataSourceView changes
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="args">EventArgs</param>
        protected virtual void OnDataSourceViewChanged(object sender, EventArgs args)
        {
            RequiresDataBinding = true;
        }

        /// <summary>
        /// Raise the ItemCommand event
        /// </summary>
        /// <param name="args">EventArgs</param>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Using appropriate EventArgs for the Event")]
        protected virtual void OnItemCommand(AccordionCommandEventArgs args)
        {
            if (ItemCommand != null)
                ItemCommand(this, args);
        }

        /// <summary>
        /// Raise the ItemCreatedEvent
        /// </summary>
        /// <param name="args">EventArgs</param>
        protected virtual void OnItemCreated(AccordionItemEventArgs args)
        {
            if (ItemCreated != null)
                ItemCreated(this, args);
        }

        /// <summary>
        /// Raise the ItemDataBound event
        /// </summary>
        /// <param name="args">EventArgs</param>
        protected virtual void OnItemDataBound(AccordionItemEventArgs args)
        {
            if (ItemDataBound != null)
                ItemDataBound(this, args);
        }

#endregion
    }
}