using System;
using System.Collections;
using System.Web.UI.WebControls;
using Spring.DataBinding;
using Spring.Globalization;

namespace Spring.DataBinding
{
    ///<summary>
    /// Handles binding of a multiple selection ListControl to a target's IList property.
    ///</summary>
    public class MultipleSelectionListControlBinding : SimpleExpressionBinding
    {
        ///<summary>
        /// Creates a new instance of this binding.
        ///</summary>
        ///<param name="sourceExpression">The expression that will evaluate to the <see cref="ListControl"/> acting as the source</param>
        ///<param name="targetExpression">The expression that will evaluate to an <see cref="IList"/> property acting as the target</param>
        /// <param name="direction">Binding's direction</param>
        ///<param name="itemFormatter">An optional formatter converting target items into <see cref="ListItem.Value"/> and back</param>
        /// <remarks>
        /// If no formatter is specified, a <see cref="DataSourceItemFormatter"/> will be used.
        /// </remarks>
        public MultipleSelectionListControlBinding(string sourceExpression, string targetExpression, BindingDirection direction,
                                                   IFormatter itemFormatter)
            :base(sourceExpression, targetExpression, itemFormatter)
        {
            this.Direction = direction;
            if (itemFormatter == null)
            {
                this.Formatter = new DataSourceItemFormatter("DataSource", "DataValueField");
            }
        }

        /// <summary>
        /// Actually performs unbinding the ListControl's selected values into the target's <see cref="IList"/>
        /// </summary>
        protected override void DoBindSourceToTarget(object source, object target, IDictionary variables)
        {
            // retrieve targetlist
            IList targetList = this.GetTargetValue(target,variables) as IList;
            if(targetList == null)
            {
                throw new ArgumentException(string.Format("Target property must evaluate to an instance of {0}.", typeof(IList).FullName));
            }

            // retrieve ListControl to be unbound
            source = this.GetSourceValue(source, variables);
            ListControl listControl = source as ListControl;
            if(listControl == null)
            {
                throw new ArgumentException(string.Format("Source property must evaluate to an instance of {0}.", typeof(ListControl).FullName));
            }

            // reset target list
            targetList.Clear();

            // set binding context if applicable
            IBindingAwareFormatter bindingAwareFormatter = this.Formatter as IBindingAwareFormatter;
            if (bindingAwareFormatter != null)
            {
                bindingAwareFormatter.SetBindingContext(source, target, variables, BindingDirection.SourceToTarget);
            }

            // (re-)add selected items to the target list
            try
            {
                IFormatter formatter = this.Formatter;
                ListItemCollection listItems = listControl.Items;
                foreach(ListItem listItem in listControl.Items)
                {
                    if (listItem.Selected)
                    {
                        // formatter must return an object applicable to the targetlist.
                        object item = formatter.Parse(listItem.Value);
                        targetList.Add(item);
                    }
                }
            }
            finally
            {
                if(bindingAwareFormatter != null)
                {
                    bindingAwareFormatter.ClearBindingContext();
                }
            }
        }

        /// <summary>
        /// Actually performs binding the targetlist to the ListControl.
        /// </summary>
        protected override void DoBindTargetToSource(object source, object target, IDictionary variables)
        {
            // retrieve targetlist
            IList targetList = this.GetTargetValue(target, variables) as IList;
            if (targetList == null)
            {
                throw new ArgumentException("Target property has to be initialized to an instance of IList interface.");
            }

            // retrieve ListControl to be bound
            source = this.GetSourceValue(source, variables);
            ListControl listControl = source as ListControl;
            if(listControl == null)
            {
                throw new ArgumentException(string.Format("Source property has to be initialized to an instance of {0}.", typeof(ListControl).FullName));
            }

            // clear control's selection
            listControl.ClearSelection();
            ListItemCollection listItems = listControl.Items;

            // set binding context if applicable
            IBindingAwareFormatter bindingAwareFormatter = this.Formatter as IBindingAwareFormatter;
            if(bindingAwareFormatter != null)
            {
                bindingAwareFormatter.SetBindingContext(source, target, variables, BindingDirection.TargetToSource);
            }

            // select all items in ListControl matching keys in targetlist
            try
            {
                IFormatter formatter = this.Formatter;
                foreach(object objectItem in targetList)
                {
                    string key = formatter.Format(objectItem);
                    ListItem item = listItems.FindByValue(key);
                    if (item != null)
                    {
                        item.Selected = true;
                    }
                    else
                    {
                        throw new ArgumentOutOfRangeException("value", key, string.Format("Value '{0}' does not exist", key ));
                    }
                }
            }
            finally
            {
                if(bindingAwareFormatter != null)
                {
                    bindingAwareFormatter.ClearBindingContext();
                }
            }
        }

        /// <summary>
        /// Setting source value is not allowed in this bindingType.
        /// </summary>
        protected override void SetSourceValue(object source, object value, IDictionary variables)
        {
            throw new InvalidOperationException(string.Format("Setting source value in '{0}' is not allowed.",this.GetType().FullName));
        }

        /// <summary>
        /// Setting target value is not allowed in this bindingType.
        /// </summary>
        protected override void SetTargetValue(object target, object value, IDictionary variables)
        {
            throw new InvalidOperationException(string.Format("Setting target value in '{0}' is not allowed.", this.GetType().FullName));
        }
    }
}