using System;
using System.Collections;
using System.Web.UI;

namespace Spring.DataBinding
{
    /// <summary>
    /// This formatter acts as an adapter to a datasource. It parses a given datasource into 
    /// a table to allow converting back and forth between objects and their keys during formatting.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The DataSourceItemFormatter expects the "source" to have 2 properties named "DataSourceField" and "DataValueField".
    /// </para>
    /// </remarks>
    public class DataSourceItemFormatter : IBindingAwareFormatter
    {
        private string _dataSourceFieldName;
        private string _dataValueFieldName;

        [ThreadStatic]
        private Hashtable _dataItemsByKey;
        [ThreadStatic] 
        private string _dataItemKeyField;

        /// <summary>
        /// Initialize this instance.
        /// </summary>
        /// <param name="dataSourceField">The name of the "source"s property containing the dataSource</param>
        /// <param name="dataValueField">The name of the "source"s property containing the name of the key property</param>
        public DataSourceItemFormatter(string dataSourceField, string dataValueField)
        {
            this._dataSourceFieldName = dataSourceField;
            if(dataValueField == null)
            {
                throw new ArgumentNullException("DataValueField must not be null.");
            }
            this._dataValueFieldName = dataValueField;
        }

        /// <summary>
        /// Sets the bindingContext for the current thread.
        /// </summary>
        /// <param name="source">The source object</param>
        /// <param name="target">The target object</param>
        /// <param name="variables">Variables to be used during binding</param>
        /// <param name="direction">The current binding's direction</param>
        public void SetBindingContext(object source, object target, IDictionary variables, BindingDirection direction)
        {
            // Extract dataSource from source object
            IEnumerable dataSource = DataBinder.GetPropertyValue(source, this._dataSourceFieldName) as IEnumerable;
            // Extract dataItemKeyField from source object
            string dataItemKeyField = DataBinder.GetPropertyValue(source, this._dataValueFieldName) as String;

            Initialize(dataSource, dataItemKeyField, direction);
        }

        /// <summary>
        /// Reset the current thread's binding context.
        /// </summary>
        public void ClearBindingContext()
        {
            _dataItemsByKey = null;
            _dataItemKeyField = null;
        }

        /// <summary>
        /// Initialize a new instance.
        /// </summary>
        /// <param name="dataSource">The datasource containing list items</param>
        /// <param name="dataItemKeyField">The name of the listitem's property that evaluates to the item's key</param>
        /// <param name="direction">The direction of the current binding</param>
        private void Initialize(IEnumerable dataSource, string dataItemKeyField, BindingDirection direction)
        {
            _dataItemKeyField = dataItemKeyField;

            // if we are binding from target to source only, we can save some performance 
            // here because only Format() will be called.
            if (direction != (direction&BindingDirection.TargetToSource))
            {
                if(dataSource == null)
                {
                    throw new ArgumentNullException(
                        string.Format("DataSource must not be null."));
                }

                _dataItemsByKey = new Hashtable();
                foreach(object dataItem in dataSource)
                {
                    string key = Format(dataItem);
                    this._dataItemsByKey.Add(key, dataItem);
                }
            }
        }

        /// <summary>
        /// Returns the key of the specified value object.
        /// </summary>
        /// <param name="value">The value to extract the key from.</param>
        /// <returns>Key extracted from <paramref name="value"/>.</returns>
        public string Format(object value)
        {
            return DataBinder.GetPropertyValue(value, this._dataItemKeyField, null);
        }

        /// <summary>
        /// Return the object with the specified key from the datasource.
        /// </summary>
        /// <param name="key">The key of the object.</param>
        /// <returns>Parsed <paramref name="value"/>.</returns>
        public object Parse(string key)
        {
            object item = this._dataItemsByKey[key];
            if (item==null)
            {
                throw new ArgumentOutOfRangeException("key", key, string.Format("Item with key {0} does not exist", key));
            }
            return item;
        }
    }
}