// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;

namespace System.Windows.Controls
{
    internal class DataGridColumnCollection : ObservableCollection<DataGridColumn>
    {
        #region Data

        private int _lastAccessedSortedIndex = -1;
        private DataGrid _owningGrid;
        private List<DataGridColumn> _sortedColumns;
        private DataGridColumnDisplayIndexComparer _columnComparer;

        #endregion Data

        public DataGridColumnCollection(DataGrid owningGrid)
        {
            this._owningGrid = owningGrid;
            this.ItemsInternal = new List<DataGridColumn>();
            this.FillerColumn = new DataGridFillerColumn(owningGrid);
        }

        #region Properties

        internal int AutogeneratedColumnCount
        {
            get;
            set;
        }

        private DataGridColumnDisplayIndexComparer ColumnComparer
        {
            get
            {
                if (_columnComparer == null)
                {
                    _columnComparer = new DataGridColumnDisplayIndexComparer();
                }
                return _columnComparer;
            }
        }

        internal DataGridFillerColumn FillerColumn
        {
            get;
            private set;
        }

        internal DataGridColumn FirstColumn
        {
            get
            {
                return GetFirstColumn(null /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
            }
        }

        internal DataGridColumn FirstVisibleColumn
        {
            get
            {
                return GetFirstColumn(true /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
            }
        }

        internal DataGridColumn FirstVisibleFrozenColumn
        {
            get
            {
                return GetFirstColumn(true /*isVisible*/, true /*isFrozen*/, null /*isReadOnly*/);
            }
        }

        internal DataGridColumn FirstVisibleWritableColumn
        {
            get
            {
                return GetFirstColumn(true /*isVisible*/, null /*isFrozen*/, false /*isReadOnly*/);
            }
        }

        internal DataGridColumn FirstVisibleScrollingColumn
        {
            get
            {
                return GetFirstColumn(true /*isVisible*/, false /*isFrozen*/, null /*isReadOnly*/);
            }
        }

        internal List<DataGridColumn> ItemsInternal
        {
            get;
            private set;
        }

        internal DataGridColumn LastVisibleColumn
        {
            get
            {
                return GetLastColumn(true /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
            }
        }

        internal DataGridColumn LastVisibleFrozenColumn
        {
            get
            {
                return GetLastColumn(true /*isVisible*/, true /*isFrozen*/, null /*isReadOnly*/);
            }
        }

        internal DataGridColumn LastVisibleScrollingColumn
        {
            get
            {
                return GetLastColumn(true /*isVisible*/, false /*isFrozen*/, null /*isReadOnly*/);
            }
        }

        internal DataGridColumn LastVisibleWritableColumn
        {
            get
            {
                return GetLastColumn(true /*isVisible*/, null /*isFrozen*/, false /*isReadOnly*/);
            }
        }

        internal int VisibleColumnCount
        {
            get
            {
                // 

                int visibleColumnCount = 0;
                for (int columnIndex = 0; columnIndex < this.ItemsInternal.Count; columnIndex++)
                {
                    if (this.ItemsInternal[columnIndex].Visibility == Visibility.Visible)
                    {
                        visibleColumnCount++;
                    }
                }
                return visibleColumnCount;
            }
        }

        internal double VisibleEdgedColumnsWidth
        {
            get;
            private set;
        }

        // 







        #endregion Properties

        #region Protected Methods

        protected override void ClearItems()
        {
            Debug.Assert(this._owningGrid != null);
            if (this.ItemsInternal.Count > 0)
            {
                // 



                if (this._owningGrid.InDisplayIndexAdjustments)
                {
                    // We are within columns display indexes adjustments. We do not allow changing the column collection while adjusting display indexes.
                    throw DataGridError.DataGrid.CannotChangeColumnCollectionWhileAdjustingDisplayIndexes();
                }

                this._owningGrid.OnClearingColumns();
                InvalidateCachedColumnsOrder();
                for (int columnIndex = 0; columnIndex < this.ItemsInternal.Count; columnIndex++)
                {
                    // Detach the column...
                    this.ItemsInternal[columnIndex].OwningGrid = null;
                }
                this.ItemsInternal.Clear();
                this.AutogeneratedColumnCount = 0;
                // 

                this._owningGrid.OnColumnCollectionChanged_PreNotification(false /*columnsGrew*/);
                base.ClearItems();
                this.VisibleEdgedColumnsWidth = 0;
                this._owningGrid.OnColumnCollectionChanged_PostNotification(false /*columnsGrew*/);
            }
        }

        protected override void InsertItem(int columnIndex, DataGridColumn dataGridColumn)
        {
            Debug.Assert(this._owningGrid != null);
            // 



            if (this._owningGrid.InDisplayIndexAdjustments)
            {
                // We are within columns display indexes adjustments. We do not allow changing the column collection while adjusting display indexes.
                throw DataGridError.DataGrid.CannotChangeColumnCollectionWhileAdjustingDisplayIndexes();
            }
            if (dataGridColumn == null)
            {
                throw new ArgumentNullException("dataGridColumn");
            }
            int originalDisplayIndex = dataGridColumn.DisplayIndex;
            if (originalDisplayIndex == -1)
            {
                dataGridColumn.DisplayIndex = columnIndex;
            }
            DataGridCellCoordinates newCurrentCellCoordinates;
            try
            {
                newCurrentCellCoordinates = this._owningGrid.OnInsertingColumn(columnIndex, dataGridColumn);   // will throw an exception if the insertion is illegal
            }
            finally
            {
                dataGridColumn.DisplayIndexInternal = originalDisplayIndex;
            }
            InvalidateCachedColumnsOrder();
            this.ItemsInternal.Insert(columnIndex, dataGridColumn);
            dataGridColumn.Index = columnIndex;
            dataGridColumn.OwningGrid = this._owningGrid;
            if (dataGridColumn.Visibility == Visibility.Visible)
            {
                this.VisibleEdgedColumnsWidth += dataGridColumn.ActualWidth;
            }
            this._owningGrid.OnInsertedColumn_PreNotification(dataGridColumn);
            this._owningGrid.OnColumnCollectionChanged_PreNotification(true /*columnsGrew*/);
            base.InsertItem(columnIndex, dataGridColumn);
            this._owningGrid.OnInsertedColumn_PostNotification(newCurrentCellCoordinates);
            this._owningGrid.OnColumnCollectionChanged_PostNotification(true /*columnsGrew*/);
        }

        protected override void RemoveItem(int columnIndex)
        {
            // 




            if (this._owningGrid.InDisplayIndexAdjustments)
            {
                // We are within columns display indexes adjustments. We do not allow changing the column collection while adjusting display indexes.
                throw DataGridError.DataGrid.CannotChangeColumnCollectionWhileAdjustingDisplayIndexes();
            }

            Debug.Assert(columnIndex >= 0 && columnIndex < this.ItemsInternal.Count);
            Debug.Assert(this._owningGrid != null);

            DataGridColumn dataGridColumn = this.ItemsInternal[columnIndex];
            //



            DataGridCellCoordinates newCurrentCellCoordinates = this._owningGrid.OnRemovingColumn(dataGridColumn);
            InvalidateCachedColumnsOrder();
            this.ItemsInternal.RemoveAt(columnIndex);
            if (dataGridColumn.Visibility == Visibility.Visible)
            {
                this.VisibleEdgedColumnsWidth -= dataGridColumn.ActualWidth;
            }
            dataGridColumn.OwningGrid = null;
            this._owningGrid.OnRemovedColumn_PreNotification(dataGridColumn);
            this._owningGrid.OnColumnCollectionChanged_PreNotification(false /*columnsGrew*/);
            base.RemoveItem(columnIndex);
            this._owningGrid.OnRemovedColumn_PostNotification(newCurrentCellCoordinates);
            this._owningGrid.OnColumnCollectionChanged_PostNotification(false /*columnsGrew*/);
        }

        protected override void SetItem(int columnIndex, DataGridColumn dataGridColumn)
        {
            throw new NotSupportedException();
        }

        #endregion Protected Methods

        #region Internal Methods

        internal bool DisplayInOrder(int columnIndex1, int columnIndex2)
        {
            int displayIndex1 = ((DataGridColumn)this.ItemsInternal[columnIndex1]).DisplayIndex;
            int displayIndex2 = ((DataGridColumn)this.ItemsInternal[columnIndex2]).DisplayIndex;
            return displayIndex1 < displayIndex2;
        }

        internal DataGridColumn GetColumnAtDisplayIndex(int displayIndex)
        {
            if (displayIndex < 0 || displayIndex >= this.ItemsInternal.Count)
            {
                return null;
            }
            if (this.ItemsInternal[displayIndex].DisplayIndex == displayIndex)
            {
                // Performance gain if display indexes coincide with indexes.
                return this.ItemsInternal[displayIndex];
            }
            for (int columnIndex = 0; columnIndex < this.ItemsInternal.Count; columnIndex++)
            {
                if (this.ItemsInternal[columnIndex].DisplayIndex == displayIndex)
                {
                    return this.ItemsInternal[columnIndex];
                }
            }
            Debug.Assert(false, "no column found in GetColumnAtDisplayIndex");
            return null;
        }

        internal int GetColumnCount(bool isVisible, bool isFrozen, int fromColumnIndex, int toColumnIndex)
        {
            Debug.Assert(DisplayInOrder(fromColumnIndex, toColumnIndex));
            Debug.Assert((this.ItemsInternal[toColumnIndex].Visibility == Visibility.Visible) == isVisible);
            Debug.Assert(this.ItemsInternal[toColumnIndex].IsFrozen == isFrozen);

            int columnCount = 0;
            DataGridColumn dataGridColumn = this.ItemsInternal[fromColumnIndex];

            while (dataGridColumn != this.ItemsInternal[toColumnIndex])
            {
                dataGridColumn = GetNextColumn(dataGridColumn, isVisible, isFrozen, null /*isReadOnly*/);
                Debug.Assert(dataGridColumn != null);
                columnCount++;
            }
            return columnCount;
        }

        internal DataGridColumn GetFirstColumn(bool? isVisible, bool? isFrozen, bool? isReadOnly)
        {
            if (this._sortedColumns == null)
            {
                UpdateColumnOrderCache();
            }
#if DEBUG
            Debug.Assert(Debug_VerifyColumnOrderCache());
#endif
            int index = 0;
            while (index < this._sortedColumns.Count)
            {
                DataGridColumn dataGridColumn = this._sortedColumns[index];
                if ((isVisible == null || (dataGridColumn.Visibility == Visibility.Visible) == isVisible) &&
                    (isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
                    (isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
                {
                    this._lastAccessedSortedIndex = index;
                    return dataGridColumn;
                }
                index++;
            }
            return null;
        }

        internal DataGridColumn GetLastColumn(bool? isVisible, bool? isFrozen, bool? isReadOnly)
        {
            if (this._sortedColumns == null)
            {
                UpdateColumnOrderCache();
            }
#if DEBUG
            Debug.Assert(Debug_VerifyColumnOrderCache());
#endif
            int index = this._sortedColumns.Count - 1;
            while (index >= 0)
            {
                DataGridColumn dataGridColumn = this._sortedColumns[index];
                if ((isVisible == null || (dataGridColumn.Visibility == Visibility.Visible) == isVisible) &&
                    (isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
                    (isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
                {
                    this._lastAccessedSortedIndex = index;
                    return dataGridColumn;
                }
                index--;
            }
            return null;
        }

        internal DataGridColumn GetNextColumn(DataGridColumn dataGridColumnStart)
        {
            return GetNextColumn(dataGridColumnStart, null /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        internal DataGridColumn GetNextColumn(DataGridColumn dataGridColumnStart,
                                                  bool? isVisible, bool? isFrozen, bool? isReadOnly)
        {
            Debug.Assert(dataGridColumnStart != null);
            Debug.Assert(this.ItemsInternal.Contains(dataGridColumnStart));

            if (this._sortedColumns == null)
            {
                UpdateColumnOrderCache();
            }
#if DEBUG
            Debug.Assert(Debug_VerifyColumnOrderCache());
#endif
            int index = GetColumnSortedIndex(dataGridColumnStart);
            if (index == -1)
            {
                bool columnFound = false;
                int indexMin = Int32.MaxValue, displayIndexMin = Int32.MaxValue;
                for (index = 0; index < this.ItemsInternal.Count; index++)
                {
                    DataGridColumn dataGridColumn = this.ItemsInternal[index];
                    if ((isVisible == null || (dataGridColumn.Visibility == Visibility.Visible) == isVisible) &&
                        (isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
                        (isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly) &&
                        (dataGridColumn.DisplayIndex > dataGridColumnStart.DisplayIndex ||
                         (dataGridColumn.DisplayIndex == dataGridColumnStart.DisplayIndex &&
                          dataGridColumn.Index > dataGridColumnStart.Index)))
                    {
                        if (dataGridColumn.DisplayIndex < displayIndexMin ||
                            (dataGridColumn.DisplayIndex == displayIndexMin &&
                             dataGridColumn.Index < indexMin))
                        {
                            indexMin = index;
                            displayIndexMin = dataGridColumn.DisplayIndex;
                            columnFound = true;
                        }
                    }
                }
                return columnFound ? this.ItemsInternal[indexMin] : null;
            }
            else
            {
                index++;
                while (index < this._sortedColumns.Count)
                {
                    DataGridColumn dataGridColumn = this._sortedColumns[index];

                    if ((isVisible == null || (dataGridColumn.Visibility == Visibility.Visible) == isVisible) &&
                        (isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
                        (isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
                    {
                        this._lastAccessedSortedIndex = index;
                        return dataGridColumn;
                    }
                    index++;
                }
            }
            return null;
        }

        internal DataGridColumn GetNextVisibleColumn(DataGridColumn dataGridColumnStart)
        {
            return GetNextColumn(dataGridColumnStart, true /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
        }

        internal DataGridColumn GetNextVisibleFrozenColumn(DataGridColumn dataGridColumnStart)
        {
            return GetNextColumn(dataGridColumnStart, true /*isVisible*/, true /*isFrozen*/, null /*isReadOnly*/);
        }

        internal DataGridColumn GetNextVisibleScrollingColumn(DataGridColumn dataGridColumnStart)
        {
            return GetNextColumn(dataGridColumnStart, true /*isVisible*/, false /*isFrozen*/, null /*isReadOnly*/);
        }

        internal DataGridColumn GetNextVisibleWritableColumn(DataGridColumn dataGridColumnStart)
        {
            return GetNextColumn(dataGridColumnStart, true /*isVisible*/, null /*isFrozen*/, false /*isReadOnly*/);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        internal DataGridColumn GetPreviousColumn(DataGridColumn dataGridColumnStart,
                                                      bool? isVisible, bool? isFrozen, bool? isReadOnly)
        {
            Debug.Assert(dataGridColumnStart != null);
            Debug.Assert(this.ItemsInternal.Contains(dataGridColumnStart));

            if (this._sortedColumns == null)
            {
                UpdateColumnOrderCache();
            }
#if DEBUG
            Debug.Assert(Debug_VerifyColumnOrderCache());
#endif
            int index = GetColumnSortedIndex(dataGridColumnStart);
            if (index == -1)
            {
                bool columnFound = false;
                int indexMax = -1, displayIndexMax = -1;
                for (index = 0; index < this.ItemsInternal.Count; index++)
                {
                    DataGridColumn dataGridColumn = this.ItemsInternal[index];
                    if ((isVisible == null || (dataGridColumn.Visibility == Visibility.Visible) == isVisible) &&
                        (isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
                        (isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly) &&
                        (dataGridColumn.DisplayIndex < dataGridColumnStart.DisplayIndex ||
                         (dataGridColumn.DisplayIndex == dataGridColumnStart.DisplayIndex &&
                          dataGridColumn.Index < dataGridColumnStart.Index)))
                    {
                        if (dataGridColumn.DisplayIndex > displayIndexMax ||
                            (dataGridColumn.DisplayIndex == displayIndexMax &&
                             dataGridColumn.Index > indexMax))
                        {
                            indexMax = index;
                            displayIndexMax = dataGridColumn.DisplayIndex;
                            columnFound = true;
                        }
                    }
                }
                return columnFound ? this.ItemsInternal[indexMax] : null;
            }
            else
            {
                index--;
                while (index >= 0)
                {
                    DataGridColumn dataGridColumn = this._sortedColumns[index];
                    if ((isVisible == null || (dataGridColumn.Visibility == Visibility.Visible) == isVisible) &&
                        (isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
                        (isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
                    {
                        this._lastAccessedSortedIndex = index;
                        return dataGridColumn;
                    }
                    index--;
                }
            }
            return null;
        }

        internal DataGridColumn GetPreviousVisibleColumn(DataGridColumn dataGridColumnStart)
        {
            return GetPreviousColumn(dataGridColumnStart, true /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
        }

        internal DataGridColumn GetPreviousVisibleFrozenColumn(DataGridColumn dataGridColumnStart)
        {
            return GetPreviousColumn(dataGridColumnStart, true /*isVisible*/, true /*isFrozen*/, null /*isReadOnly*/);
        }

        internal DataGridColumn GetPreviousVisibleScrollingColumn(DataGridColumn dataGridColumnStart)
        {
            return GetPreviousColumn(dataGridColumnStart, true /*isVisible*/, false /*isFrozen*/, null /*isReadOnly*/);
        }

        internal DataGridColumn GetPreviousVisibleWritableColumn(DataGridColumn dataGridColumnStart)
        {
            return GetPreviousColumn(dataGridColumnStart, true /*isVisible*/, null /*isFrozen*/, false /*isReadOnly*/);
        }

        internal int GetVisibleColumnCount(int fromColumnIndex, int toColumnIndex)
        {
            Debug.Assert(DisplayInOrder(fromColumnIndex, toColumnIndex));
            Debug.Assert(this.ItemsInternal[toColumnIndex].Visibility == Visibility.Visible);

            int columnCount = 0;
            DataGridColumn dataGridColumn = this.ItemsInternal[fromColumnIndex];

            while (dataGridColumn != this.ItemsInternal[toColumnIndex])
            {
                dataGridColumn = GetNextVisibleColumn(dataGridColumn);
                Debug.Assert(dataGridColumn != null);
                columnCount++;
            }
            return columnCount;
        }

        internal void EnsureVisibleEdgedColumnsWidth()
        {
            // 

            this.VisibleEdgedColumnsWidth = 0;
            for (int columnIndex = 0; columnIndex < this.ItemsInternal.Count; columnIndex++)
            {
                if (this.ItemsInternal[columnIndex].Visibility == Visibility.Visible)
                {
                    this.VisibleEdgedColumnsWidth += this.ItemsInternal[columnIndex].ActualWidth;
                }
            }
        }

        internal double GetVisibleFrozenEdgedColumnsWidth()
        {
            // 

            double visibleFrozenColumnsWidth = 0;
            for (int columnIndex = 0; columnIndex < this.ItemsInternal.Count; columnIndex++)
            {
                if (this.ItemsInternal[columnIndex].Visibility == Visibility.Visible && this.ItemsInternal[columnIndex].IsFrozen)
                {
                    visibleFrozenColumnsWidth += this.ItemsInternal[columnIndex].ActualWidth;
                }
            }
            return visibleFrozenColumnsWidth;
        }

        internal void InvalidateCachedColumnsOrder()
        {
            this._sortedColumns = null;
        }

        #endregion Internal Methods

        #region Private Methods

        private int GetColumnSortedIndex(DataGridColumn dataGridColumn)
        {
            Debug.Assert(dataGridColumn != null);
            Debug.Assert(this._sortedColumns != null);
            Debug.Assert(this._lastAccessedSortedIndex == -1 || this._lastAccessedSortedIndex < this.ItemsInternal.Count);

            if (this._lastAccessedSortedIndex != -1 &&
                this._sortedColumns[this._lastAccessedSortedIndex] == dataGridColumn)
            {
                return this._lastAccessedSortedIndex;
            }

            int index = 0;
            while (index < this._sortedColumns.Count)
            {
                if (dataGridColumn.Index == this._sortedColumns[index].Index)
                {
                    this._lastAccessedSortedIndex = index;
                    return index;
                }
                index++;
            }
            return -1;
        }

        private void UpdateColumnOrderCache()
        {
            this._sortedColumns = new List<DataGridColumn>(this.ItemsInternal);
            this._sortedColumns.Sort(this.ColumnComparer);
            this._lastAccessedSortedIndex = -1;
        }

        #endregion Private Methods

        #region DataGridColumnDisplayIndexComparer

        private class DataGridColumnDisplayIndexComparer : IComparer<DataGridColumn>
        {
            #region IComparer<DataGridColumn> Members

            public int Compare(DataGridColumn x, DataGridColumn y)
            {
                return x.DisplayIndex - y.DisplayIndex;
            }

            #endregion
        }

        #endregion DataGridColumnDisplayIndexComparer

        #region Debugging Methods

#if DEBUG
        internal bool Debug_VerifyColumnDisplayIndexes()
        {
            for (int columnDisplayIndex = 0; columnDisplayIndex < this.ItemsInternal.Count; columnDisplayIndex++)
            {
                if (GetColumnAtDisplayIndex(columnDisplayIndex) == null)
                {
                    return false;
                }
            }
            return true;
        }

        private bool Debug_VerifyColumnOrderCache()
        {
            if (this._sortedColumns == null) return false;
            if (this._sortedColumns.Count != this.ItemsInternal.Count) return false;

            int index = 0;
            while (index < this._sortedColumns.Count - 1)
            {
                if (this._sortedColumns[index + 1].DisplayIndex !=
                    this._sortedColumns[index].DisplayIndex + 1) return false;
                index++;
            }
            return true;
        }
#endif

        #endregion Debugging Methods
    }
}
