using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
#if WISEJ
using Wisej.Web;
using MvvmFx.WisejWeb.Properties;
#else
using System.Windows.Forms;
using MvvmFx.Windows.Forms.Properties;
#endif

// code from Sascha Knopf
// http://www.codeproject.com/Articles/15396/Implementing-complex-data-binding-in-custom-contro

// Improvements by Tiago Freitas Leal (MvvmFx project).

namespace MvvmFx.WisejWeb
{
    /// <summary>
    /// Data binding enabled list view control.
    /// </summary>
    [Description("Data binding enabled list view control.")]
    [ComplexBindingProperties("DataSource", "DataMember")]
    [ToolboxItem(true)]
    [ToolboxBitmap(typeof (BoundListView), "BoundListView.bmp")]
    public class BoundListView : ListView
    {
        #region Fields

        private bool _isHandlingPositionChange;

        private readonly Container _components = null;
        private readonly ListChangedEventHandler _listChangedHandler;
        private readonly EventHandler _positionChangedHandler;
        private CurrencyManager _listManager;

        private object _dataSource;
        private string _dataMember;
        #endregion

        #region Properties

#if WINFORMS
        /// <summary>
        /// Gets or sets the data source for this <see cref="MvvmFx.Windows.Forms.BoundListView"/>.
        /// </summary>
        /// <returns>
        /// An object that implements the <see cref="System.Collections.IList"/> or
        /// <see cref="System.ComponentModel.IListSource"/> interfaces,
        /// such as a <see cref="System.Data.DataSet"/> or an <see cref="System.Array"/>. The default is null.
        /// </returns>>
#else
        /// <summary>
        /// Gets or sets the data source for this <see cref="MvvmFx.WisejWeb.BoundListView"/>.
        /// </summary>
        /// <returns>
        /// An object that implements the <see cref="System.Collections.IList"/> or
        /// <see cref="System.ComponentModel.IListSource"/> interfaces,
        /// such as a <see cref="System.Data.DataSet"/> or an <see cref="System.Array"/>. The default is null.
        /// </returns>>
#endif
        /*[Bindable(true, BindingDirection.TwoWay)] do not uncomment*/
        [AttributeProvider(typeof (IListSource))]
        [DefaultValue((string) null)]
        [Editor("System.Windows.Forms.Design.DataSourceListEditor, System.Design", typeof (UITypeEditor))]
        [TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
        [RefreshProperties(RefreshProperties.Repaint)]
        [Category("Data")]
        [Description("Indicates the list that this control will use to get its items.")]
        public object DataSource
        {
            get { return _dataSource; }
            set
            {
                if (_dataSource != value)
                {
                    _dataSource = value;
                    TryDataBinding();
                }
            }
        }

#if WINFORMS
        /// <summary>
        /// Gets or sets the name of the list or table in the data source for which
        /// the <see cref="MvvmFx.Windows.Forms.BoundListView"/> is displaying data.
        /// </summary>
        /// <returns>
        /// The name of the table or list in the <see cref="MvvmFx.Windows.Forms.BoundListView.DataSource"/> for which the
        /// <see cref="MvvmFx.Windows.Forms.BoundListView"/> is displaying data. The default is <see cref="System.String.Empty"/>.
        /// </returns>
        /// <exception cref="System.Exception">
        /// An error occurred in the data source and either there is no handler for the <see cref="System.Windows.Forms.DataGridView.DataError"/>
        /// event or the handler has set the <see cref="System.Windows.Forms.DataGridViewDataErrorEventArgs.ThrowException"/> property to true.
        /// The exception object can typically be cast to type <see cref="System.FormatException"/>.
        /// </exception>
#else
        /// <summary>
        /// Gets or sets the name of the list or table in the data source for which
        /// the <see cref="MvvmFx.WisejWeb.BoundListView"/> is displaying data.
        /// </summary>
        /// <returns>
        /// The name of the table or list in the <see cref="MvvmFx.WisejWeb.BoundListView.DataSource"/> for which the
        /// <see cref="MvvmFx.WisejWeb.BoundListView"/> is displaying data. The default is <see cref="System.String.Empty"/>.
        /// </returns>
        /// <exception cref="System.Exception">
        /// An error occurred in the data source and either there is no handler for the <see cref="Wisej.Web.DataGridView.DataError"/>
        /// event or the handler has set the <see cref="Wisej.Web.DataGridViewDataErrorEventArgs.ThrowException"/> property to true.
        /// The exception object can typically be cast to type <see cref="System.FormatException"/>.
        /// </exception>
#endif
        /*[Bindable(true, BindingDirection.TwoWay)] do not uncomment*/
        [DefaultValue(null)]
        [Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof (UITypeEditor))]
        [RefreshProperties(RefreshProperties.Repaint)]
        [Category("Data")]
        [Description("Indicates a sub-list of the DataSource to show in the BoundListView control.")]
        public string DataMember
        {
            get { return _dataMember; }
            set
            {
                if (_dataMember != value)
                {
                    _dataMember = value;
                    TryDataBinding();
                }
            }
        }

        /// <summary>
        /// Gets the selected item.
        /// </summary>
        /// <value>
        /// The selected item.
        /// </value>
        [Browsable(false)]
        [Bindable(false)]
        [DefaultValue(null)]
        public object SelectedItem
        {
            get { return _listManager.Current; }
        }

        #endregion

        #region Constructor & Dispose

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundListView"/> class.
        /// </summary>
        public BoundListView()
        {
            _listChangedHandler = ListManager_ListChanged;
            _positionChangedHandler = ListManager_PositionChanged;

            View = View.Details;
#if WINFORMS
            FullRowSelect = true;
            HideSelection = false;
#endif
            MultiSelect = false;
            LabelEdit = true;
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_components != null)
                    _components.Dispose();
            }
            base.Dispose(disposing);
        }

        #endregion

        #region TryDataBinding

        /// <summary>
        /// Tries to get a new CurrencyManager for new DataBinding
        /// </summary>
        private void TryDataBinding()
        {
            if (DataSource == null ||
                BindingContext == null)
                return;

            CurrencyManager currencyManager;
            try
            {
                currencyManager = (CurrencyManager) BindingContext[_dataSource, _dataMember];
            }
            catch (ArgumentException)
            {
                // If no CurrencyManager was found
                return;
            }

            BeginUpdate();

            // Unwire the old CurrencyManager
            if (_listManager != null)
            {
                _listManager.ListChanged -= _listChangedHandler;
                _listManager.PositionChanged -= _positionChangedHandler;
            }
            _listManager = currencyManager;

            // Update metadata and data
            CalculateColumns();
            UpdateAllData();

            // Wire the new CurrencyManager
            if (_listManager != null)
            {
                _listManager.ListChanged += _listChangedHandler;
                _listManager.PositionChanged += _positionChangedHandler;
            }

            EndUpdate();
        }

        #endregion

        #region Item Methods

        /// <summary>
        /// Updates all Items.
        /// </summary>
        private void UpdateAllData()
        {
            Items.Clear();
            for (var index = 0; index < _listManager.Count; index++)
            {
                AddItem(index);
            }

            if (_listManager.Position > -1)
            {
                for (var index = 0; index < Items.Count; index++)
                {
                    if (Items[index].Tag == _listManager.List[_listManager.Position])
                    {
                        Items[index].Selected = true;
                        EnsureVisible(index);
                    }
                    else
                        Items[index].Selected = false;
                }
            }
        }

        /// <summary>
        /// Adds a new item.
        /// </summary>
        /// <param name="index">The index of the item.</param>
        private void AddItem(int index)
        {
            var item = GetListViewItem(index);
            Items.Insert(index, item);
        }

        /// <summary>
        /// Updates the data of the item with the DataSource.
        /// </summary>
        /// <param name="index">The index of the item.</param>
        private void UpdateItem(int index)
        {
            if (index >= 0 &&
                index < Items.Count)
            {
                var item = GetListViewItem(index);
                Items[index] = item;
            }
        }

        /// <summary>
        /// Returns a <see cref="ListViewItem"/> wich contains the row-data at given index.
        /// </summary>
        /// <param name="index">The index of the row.</param>
        /// <returns>A item wich contains the data.</returns>
        private ListViewItem GetListViewItem(int index)
        {
            var row = _listManager.List[index];
            var propColl = _listManager.GetItemProperties();
            var items = new ArrayList();

            // Fill value for each column
            foreach (ColumnHeader column in Columns)
            {
                var prop = propColl.Find(column.Name, false);
                if (prop != null)
                {
                    var value = prop.GetValue(row);
                    if (value != null)
                        items.Add(value.ToString());
                    else
                        items.Add(string.Empty);
                }
            }
            var item = new ListViewItem((string[]) items.ToArray(typeof (string)));
            item.Tag = row;
            return item;
        }

        /// <summary>
        /// Delete the item at the given index.
        /// </summary>
        /// <param name="index">The index of the item.</param>
        private void DeleteItem(int index)
        {
            if (index >= 0 &&
                index < Items.Count)
                Items.RemoveAt(index);
        }

        /// <summary>
        /// Calculates the Colums of the <see cref="BoundListView"/>.
        /// </summary>
        private void CalculateColumns()
        {
            if (_listManager == null)
                return;

            if (Columns.Count == 0)
            {
                foreach (PropertyDescriptor prop in _listManager.GetItemProperties())
                {
                    var column = new ColumnHeader {Text = prop.Name, Name = prop.Name};
                    Columns.Add(column);
                }
            }
        }

        #endregion

        #region BindingContext Events

#if WINFORMS
        /// <summary>
        /// Raises the <see cref="System.Windows.Forms.Control.BindingContextChanged"/> event.
        /// </summary>
        /// <param name="e">An <see cref="System.EventArgs"/> that contains the event data. 
#else
        /// <summary>
        /// Raises the <see cref="Wisej.Base.ControlBase.BindingContextChanged"/> event.
        /// </summary>
        /// <param name="e">An <see cref="System.EventArgs"/> that contains the event data. 
#endif
        protected override void OnBindingContextChanged(EventArgs e)
        {
            TryDataBinding();
            base.OnBindingContextChanged(e);
        }

        #endregion

        #region Position changed from DataSource

        private void ListManager_PositionChanged(object sender, EventArgs e)
        {
            if (_isHandlingPositionChange)
                return;

            _isHandlingPositionChange = true;

            if (Items.Count > _listManager.Position)
            {
                for (var index = 0; index < Items.Count; index++)
                {
                    if (Items[index].Tag == _listManager.List[_listManager.Position])
                    {
                        Items[index].Selected = true;
                        EnsureVisible(index);
                    }
                    else
                        Items[index].Selected = false;
                }
            }

            _isHandlingPositionChange = false;
        }

        #endregion

        #region Item(s) changed from DataSource

        private void ListManager_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (e.ListChangedType == ListChangedType.Reset ||
                e.ListChangedType == ListChangedType.ItemMoved)
            {
                // Update all data
                UpdateAllData();
            }
            else if (e.ListChangedType == ListChangedType.ItemAdded)
            {
                // Add new Item
                AddItem(e.NewIndex);
            }
            else if (e.ListChangedType == ListChangedType.ItemChanged)
            {
                // Change Item
                UpdateItem(e.NewIndex);
            }
            else if (e.ListChangedType == ListChangedType.ItemDeleted)
            {
                // Delete Item
                DeleteItem(e.NewIndex);
            }
            else
            {
                // Update metadata and all data
                CalculateColumns();
            }
        }

        #endregion

        #region Position changed from ListView

#if !WEBGUI

        /// <summary>
        /// Raises the <see cref="System.Windows.Forms.ListView.SelectedIndexChanged" /> event.
        /// </summary>
        /// <param name="e">An <see cref="System.EventArgs" /> that contains the event data.
        protected override void OnSelectedIndexChanged(EventArgs e)
        {
            if (_isHandlingPositionChange)
                return;

            _isHandlingPositionChange = true;

            try
            {
                if (SelectedIndices.Count > 0)
                {
                    var selectedIndex = SelectedIndices[0];

                    for (var index = 0; index < Items.Count; index++)
                    {
                        if (Items[selectedIndex].Tag == _listManager.List[index])
                        {
                            _listManager.Position = index;
                            break;
                        }
                    }
                }
            }
            catch
            {
                // Could happen, if you change the position while someone edits a row with invalid data.
            }

            _isHandlingPositionChange = false;
            base.OnSelectedIndexChanged(e);
        }

#else

        /// <summary>
        /// Raises the <see cref="SelectedIndexChanged" /> event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.
        protected override void OnSelectedIndexChanged(EventArgs e)
        {
            try
            {
                if (SelectedIndices.Count > 0 && _listManager.Position != SelectedIndex)
                    _listManager.Position = SelectedIndex;
            }
            catch
            {
                // Could appear, if you change the position while someone edits a row with invalid data.
            }
            base.OnSelectedIndexChanged(e);
        }

        /// <summary>
        /// Raises the <see cref="Click" /> event.
        /// </summary>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.
        protected override void OnClick(EventArgs e)
        {
            try
            {
                _listManager.Position = SelectedIndex;
            }
            catch
            {
                // Could appear, if you change the position while someone edits a row with invalid data.
            }
            base.OnClick(e);
        }

#endif

        #endregion

        #region Item changed from ListView

#if WINFORMS
        /// <summary>
        /// Raises the <see cref="System.Windows.Forms.ListView.AfterLabelEdit" /> event.
        /// </summary>
        /// <param name="e">A <see cref="System.Windows.Forms.LabelEditEventArgs" /> that contains the event data.
#else
        /// <summary>
        /// Raises the <see cref="Wisej.Web.ListView.AfterLabelEdit"></see> event.
        /// </summary>
        /// <param name="e">A <see cref="Wisej.Web.LabelEditEventArgs"> that contains the event data.
#endif
        protected override void OnAfterLabelEdit(LabelEditEventArgs e)
        {
            if (e.Label == null)
            {
                // If you press ESC while editing.
                e.CancelEdit = true;
                return;
            }

#if WINFORMS
            var index = e.Item;
#else
            var index = e.Item.Index;
#endif

            if (_listManager.List.Count > index)
            {
                var row = _listManager.List[index];
                // In a ListView you are only able to edit the first Column.
                var col = _listManager.GetItemProperties().Find(Columns[0].Text, false);
                try
                {
                    if (row != null && col != null)
                        col.SetValue(row, e.Label);
                    _listManager.EndCurrentEdit();
                    base.OnAfterLabelEdit(e);
                }
                catch (Exception ex)
                {
                    // If you try to enter strings in number-columns, too long strings or something
                    // else wich is not allowed by the DataSource.
                    MessageBox.Show(Resources.EditFailed + ": " + ex.Message, Resources.EditFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    _listManager.CancelCurrentEdit();
                    e.CancelEdit = true;
                }
            }
        }

        #endregion
    }
}