// (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.ObjectModel;
using System.Diagnostics;

namespace AtomUI.Desktop.Controls;

internal class DataGridColumnCollection : ObservableCollection<DataGridColumn>
{
    internal int AutoGeneratedColumnCount { get; set; }
    internal List<int> DisplayIndexMap { get; set; }
    internal DataGridFillerColumn? FillerColumn { get; private set; }

    internal DataGridColumn? FirstColumn => GetFirstColumn(null /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);

    internal DataGridColumn? FirstVisibleColumn => GetFirstColumn(true /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);

    internal DataGridColumn? FirstVisibleNonFillerColumn
    {
        get
        {
            DataGridColumn? dataGridColumn = FirstVisibleColumn;
            if (dataGridColumn != null && dataGridColumn == RowGroupSpacerColumn)
            {
                dataGridColumn = GetNextVisibleColumn(dataGridColumn);
            }
            return dataGridColumn;
        }
    }

    internal DataGridColumn? FirstVisibleWritableColumn => GetFirstColumn(true /*isVisible*/, null /*isFrozen*/, false /*isReadOnly*/);

    internal DataGridColumn? FirstVisibleScrollingColumn => GetFirstColumn(true /*isVisible*/, false /*isFrozen*/, null /*isReadOnly*/);
    internal List<DataGridColumn> ItemsInternal { get; private set; }

    internal DataGridColumn? LastVisibleColumn => GetLastColumn(true /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
    internal DataGridColumn? LastVisibleScrollingColumn => GetLastColumn(true /*isVisible*/, false /*isFrozen*/, null /*isReadOnly*/);
    internal DataGridColumn? LastVisibleWritableColumn => GetLastColumn(true /*isVisible*/, null /*isFrozen*/, false /*isReadOnly*/);

    internal DataGridFillerColumn? RowGroupSpacerColumn { get; private set; }

    internal int VisibleColumnCount { get; private set; }

    internal double VisibleEdgedColumnsWidth { get; private set; }

    /// <summary>
    /// The number of star columns that are currently visible.
    /// NOTE: Requires that EnsureVisibleEdgedColumnsWidth has been called.
    /// </summary>
    internal int VisibleStarColumnCount { get; private set; }
    
    internal int AutogeneratedColumnCount { get; set; }
    
    private readonly DataGrid _owningGrid;
    
    public DataGridColumnCollection(DataGrid owningGrid)
    {
        _owningGrid          = owningGrid;
        DisplayIndexMap      = new List<int>();
        ItemsInternal        = new List<DataGridColumn>();
        FillerColumn         = new DataGridFillerColumn(owningGrid);
        RowGroupSpacerColumn = new DataGridFillerColumn(owningGrid);
    }
    
    protected override void ClearItems()
    {
        try
        {
            _owningGrid.NoCurrentCellChangeCount++;
            if (ItemsInternal.Count > 0)
            {
                if (_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();
                }

                _owningGrid.HandleClearingColumns();
                for (int columnIndex = 0; columnIndex < ItemsInternal.Count; columnIndex++)
                {
                    // Detach the column...
                    ItemsInternal[columnIndex].OwningGrid = null;
                }
                ItemsInternal.Clear();
                DisplayIndexMap.Clear();
                AutogeneratedColumnCount = 0;
                _owningGrid.HandleColumnCollectionChangedPreNotification(false /*columnsGrew*/);
                base.ClearItems();
                VisibleEdgedColumnsWidth = 0;
                _owningGrid.HandleColumnCollectionChangedPostNotification(false /*columnsGrew*/);
            }
        }
        finally
        {
            _owningGrid.NoCurrentCellChangeCount--;
        }
    }
    
    protected override void InsertItem(int columnIndex, DataGridColumn dataGridColumn)
    {
        try
        {
            _owningGrid.NoCurrentCellChangeCount++;
            if (_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(nameof(dataGridColumn));
            }

            int columnIndexWithFiller = columnIndex;
            if (dataGridColumn != RowGroupSpacerColumn && RowGroupSpacerColumn != null && RowGroupSpacerColumn.IsRepresented)
            {
                columnIndexWithFiller++;
            }

            // get the new current cell coordinates
            DataGridCellCoordinates newCurrentCellCoordinates = _owningGrid.HandleInsertingColumn(columnIndex, dataGridColumn);

            // insert the column into our internal list
            ItemsInternal.Insert(columnIndexWithFiller, dataGridColumn);
            dataGridColumn.Index      = columnIndexWithFiller;
            dataGridColumn.OwningGrid = _owningGrid;
            dataGridColumn.RemoveEditingElement();
            if (dataGridColumn.IsVisible)
            {
                VisibleEdgedColumnsWidth += dataGridColumn.ActualWidth;
            }

            // continue with the base insert
            _owningGrid.HandleInsertedColumnPreNotification(dataGridColumn);
            _owningGrid.HandleColumnCollectionChangedPreNotification(true /*columnsGrew*/);

            if (dataGridColumn != RowGroupSpacerColumn)
            {
                base.InsertItem(columnIndex, dataGridColumn);
            }
            _owningGrid.HandleInsertedColumnPostNotification(newCurrentCellCoordinates, dataGridColumn.DisplayIndex);
            _owningGrid.HandleColumnCollectionChangedPostNotification(true /*columnsGrew*/);
        }
        finally
        {
            _owningGrid.NoCurrentCellChangeCount--;
        }
    }
    
    internal DataGridColumn? GetFirstColumn(bool? isVisible, bool? isFrozen, bool? isReadOnly)
    {
        Debug.Assert(ItemsInternal != null);
        Debug.Assert(ItemsInternal.Count == DisplayIndexMap.Count);
        int index = 0;
        while (index < DisplayIndexMap.Count)
        {
            DataGridColumn? dataGridColumn = GetColumnAtDisplayIndex(index);
            Debug.Assert(dataGridColumn != null);
            if ((isVisible == null || (dataGridColumn.IsVisible) == isVisible) &&
                (isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
                (isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
            {
                return dataGridColumn;
            }
            index++;
        }
        return null;
    }

    internal DataGridColumn? GetLastColumn(bool? isVisible, bool? isFrozen, bool? isReadOnly)
    {
        Debug.Assert(ItemsInternal != null);
        Debug.Assert(ItemsInternal.Count == DisplayIndexMap.Count);
        int index = DisplayIndexMap.Count - 1;
        while (index >= 0)
        {
            DataGridColumn? dataGridColumn = GetColumnAtDisplayIndex(index);
            Debug.Assert(dataGridColumn != null);
            if ((isVisible == null || (dataGridColumn.IsVisible) == isVisible) &&
                (isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
                (isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
            {
                return dataGridColumn;
            }
            index--;
        }
        return null;
    }
    
    /// <summary>
    /// In addition to ensuring that column widths are valid, method updates the values of
    /// VisibleEdgedColumnsWidth and VisibleStarColumnCount.
    /// </summary>
    internal void EnsureVisibleEdgedColumnsWidth()
    {
        VisibleStarColumnCount   = 0;
        VisibleEdgedColumnsWidth = 0;
        VisibleColumnCount       = 0;
        Debug.Assert(ItemsInternal != null);
        for (int columnIndex = 0; columnIndex < ItemsInternal.Count; columnIndex++)
        {
            var item = ItemsInternal[columnIndex];
            if (item.IsVisible)
            {
                VisibleColumnCount++;
                item.EnsureWidth();
                if (item.Width.IsStar)
                {
                    VisibleStarColumnCount++;
                }
                VisibleEdgedColumnsWidth += item.ActualWidth;
            }
        }
    }
    
    internal DataGridColumn? GetColumnAtDisplayIndex(int displayIndex)
    {
        Debug.Assert(ItemsInternal != null);
        if (displayIndex < 0 || displayIndex >= ItemsInternal.Count || displayIndex >= DisplayIndexMap.Count)
        {
            return null;
        }
        int columnIndex = DisplayIndexMap[displayIndex];
        return ItemsInternal[columnIndex];
    }
    
    internal int GetColumnCount(bool isVisible, bool isFrozen, int fromColumnIndex, int toColumnIndex)
    {
        Debug.Assert(ItemsInternal != null);
        int            columnCount    = 0;
        DataGridColumn? dataGridColumn = ItemsInternal[fromColumnIndex];
        // TODO 需要审查
        while (dataGridColumn != null && dataGridColumn != ItemsInternal[toColumnIndex])
        {
            dataGridColumn = GetNextColumn(dataGridColumn, isVisible, isFrozen, null /*isReadOnly*/);
            columnCount++;
        }
        return columnCount;
    }
    
    internal IEnumerable<DataGridColumn> GetDisplayedColumns()
    {
        Debug.Assert(ItemsInternal != null);
        foreach (int columnIndex in DisplayIndexMap)
        {
            yield return ItemsInternal[columnIndex];
        }
    }

    internal int GetDisplayedColumnCount()
    {
        Debug.Assert(ItemsInternal != null);
        return DisplayIndexMap.Count;
    }

    /// <summary>
    /// Returns an enumeration of all columns that meet the criteria of the filter predicate.
    /// </summary>
    /// <param name="filter">Criteria for inclusion.</param>
    /// <returns>Columns that meet the criteria, in ascending DisplayIndex order.</returns>
    internal IEnumerable<DataGridColumn> GetDisplayedColumns(Predicate<DataGridColumn> filter)
    {
        Debug.Assert(ItemsInternal != null);
        Debug.Assert(filter != null);
        Debug.Assert(ItemsInternal.Count == DisplayIndexMap.Count);
        foreach (int columnIndex in DisplayIndexMap)
        {
            DataGridColumn column = ItemsInternal[columnIndex];
            if (filter(column))
            {
                yield return column;
            }
        }
    }
    
    /// <summary>
    /// Returns an enumeration of all columns that meet the criteria of the filter predicate.
    /// The columns are returned in the order specified by the reverse flag.
    /// </summary>
    /// <param name="reverse">Whether or not to return the columns in descending DisplayIndex order.</param>
    /// <param name="filter">Criteria for inclusion.</param>
    /// <returns>Columns that meet the criteria, in the order specified by the reverse flag.</returns>
    internal IEnumerable<DataGridColumn> GetDisplayedColumns(bool reverse, Predicate<DataGridColumn> filter)
    {
        return reverse ? GetDisplayedColumnsReverse(filter) : GetDisplayedColumns(filter);
    }

    /// <summary>
    /// Returns an enumeration of all columns that meet the criteria of the filter predicate.
    /// The columns are returned in descending DisplayIndex order.
    /// </summary>
    /// <param name="filter">Criteria for inclusion.</param>
    /// <returns>Columns that meet the criteria, in descending DisplayIndex order.</returns>
    internal IEnumerable<DataGridColumn> GetDisplayedColumnsReverse(Predicate<DataGridColumn> filter)
    {
        Debug.Assert(ItemsInternal != null);
        for (int displayIndex = DisplayIndexMap.Count - 1; displayIndex >= 0; displayIndex--)
        {
            DataGridColumn column = ItemsInternal[DisplayIndexMap[displayIndex]];
            if (filter(column))
            {
                yield return column;
            }
        }
    }
    
    internal DataGridColumn? GetNextColumn(DataGridColumn dataGridColumnStart)
    {
        return GetNextColumn(dataGridColumnStart, null /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
    }
    
    internal DataGridColumn? GetNextColumn(DataGridColumn dataGridColumnStart,
                                           bool? isVisible, bool? isFrozen, bool? isReadOnly)
    {
        Debug.Assert(ItemsInternal != null);
        Debug.Assert(dataGridColumnStart != null);
        Debug.Assert(ItemsInternal.Contains(dataGridColumnStart));
        Debug.Assert(ItemsInternal.Count == DisplayIndexMap.Count);

        int index = dataGridColumnStart.DisplayIndexWithFiller + 1;
        while (index < DisplayIndexMap.Count)
        {
            DataGridColumn? dataGridColumn = GetColumnAtDisplayIndex(index);
            Debug.Assert(dataGridColumn != null);
            if ((isVisible == null || (dataGridColumn.IsVisible) == isVisible) &&
                (isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
                (isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
            {
                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? GetNextVisibleWritableColumn(DataGridColumn dataGridColumnStart)
    {
        return GetNextColumn(dataGridColumnStart, true /*isVisible*/, null /*isFrozen*/, false /*isReadOnly*/);
    }

    internal DataGridColumn? GetPreviousColumn(DataGridColumn dataGridColumnStart,
                                               bool? isVisible, bool? isFrozen, bool? isReadOnly)
    {
        int index = dataGridColumnStart.DisplayIndexWithFiller - 1;
        while (index >= 0)
        {
            DataGridColumn? dataGridColumn = GetColumnAtDisplayIndex(index);
            Debug.Assert(dataGridColumn != null);
            if ((isVisible == null || (dataGridColumn.IsVisible) == isVisible) &&
                (isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
                (isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
            {
                return dataGridColumn;
            }
            index--;
        }
        return null;
    }

    internal DataGridColumn? GetPreviousVisibleNonFillerColumn(DataGridColumn dataGridColumnStart)
    {
        DataGridColumn? column = GetPreviousColumn(dataGridColumnStart, true /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
        return (column is DataGridFillerColumn) ? null : column;
    }

    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(ItemsInternal != null);
        int            columnCount    = 0;
        DataGridColumn? dataGridColumn = ItemsInternal[fromColumnIndex];

        while (dataGridColumn != null && dataGridColumn != ItemsInternal[toColumnIndex])
        {
            dataGridColumn = GetNextVisibleColumn(dataGridColumn);
            columnCount++;
        }
        return columnCount;
    }

    internal IEnumerable<DataGridColumn> GetVisibleColumns()
    {
        Predicate<DataGridColumn> filter = column => column.IsVisible;
        return GetDisplayedColumns(filter);
    }

    internal IEnumerable<DataGridColumn> GetVisibleFrozenColumns()
    {
        Predicate<DataGridColumn> filter = column => column.IsVisible && column.IsFrozen;
        return GetDisplayedColumns(filter);
    }

    internal double GetVisibleLeftFrozenEdgedColumnsWidth()
    {
        Debug.Assert(ItemsInternal != null);
        double visibleFrozenColumnsWidth = 0;
        for (int columnIndex = 0; columnIndex < ItemsInternal.Count; columnIndex++)
        {
            if (ItemsInternal[columnIndex].IsVisible && ItemsInternal[columnIndex].IsLeftFrozen)
            {
                visibleFrozenColumnsWidth += ItemsInternal[columnIndex].ActualWidth;
            }
        }
        return visibleFrozenColumnsWidth;
    }
    
    internal double GetVisibleRightFrozenEdgedColumnsWidth()
    {
        Debug.Assert(ItemsInternal != null);
        double visibleFrozenColumnsWidth = 0;
        for (int columnIndex = 0; columnIndex < ItemsInternal.Count; columnIndex++)
        {
            if (ItemsInternal[columnIndex].IsVisible && ItemsInternal[columnIndex].IsRightFrozen)
            {
                visibleFrozenColumnsWidth += ItemsInternal[columnIndex].ActualWidth;
            }
        }
        return visibleFrozenColumnsWidth;
    }
    
    internal double GetVisibleFrozenEdgedColumnsWidth()
    {
        Debug.Assert(ItemsInternal != null);
        double visibleFrozenColumnsWidth = 0;
        for (int columnIndex = 0; columnIndex < ItemsInternal.Count; columnIndex++)
        {
            if (ItemsInternal[columnIndex].IsVisible && ItemsInternal[columnIndex].IsFrozen)
            {
                visibleFrozenColumnsWidth += ItemsInternal[columnIndex].ActualWidth;
            }
        }
        return visibleFrozenColumnsWidth;
    }

    internal IEnumerable<DataGridColumn> GetVisibleScrollingColumns()
    {
        Predicate<DataGridColumn> filter = column => column.IsVisible && !column.IsFrozen;
        return GetDisplayedColumns(filter);
    }

    private void RemoveItemPrivate(int columnIndex, bool isSpacer)
    {
        try
        {
            _owningGrid.NoCurrentCellChangeCount++;

            if (_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();
            }

            int columnIndexWithFiller = columnIndex;
            if (!isSpacer && RowGroupSpacerColumn != null && RowGroupSpacerColumn.IsRepresented)
            {
                columnIndexWithFiller++;
            }

            Debug.Assert(ItemsInternal != null);
            DataGridColumn          dataGridColumn            = ItemsInternal[columnIndexWithFiller];
            DataGridCellCoordinates newCurrentCellCoordinates = _owningGrid.HandleRemovingColumn(dataGridColumn);
            ItemsInternal.RemoveAt(columnIndexWithFiller);
            if (dataGridColumn.IsVisible)
            {
                VisibleEdgedColumnsWidth -= dataGridColumn.ActualWidth;
            }

            dataGridColumn.NotifyOwningGridAboutToDetached();
            dataGridColumn.OwningGrid = null;
            dataGridColumn.RemoveEditingElement();

            // continue with the base remove
            _owningGrid.HandleRemovedColumnPreNotification(dataGridColumn);
            _owningGrid.HandleColumnCollectionChangedPreNotification(false /*columnsGrew*/);
            if (!isSpacer)
            {
                base.RemoveItem(columnIndex);
            }
            _owningGrid.HandleRemovedColumnPostNotification(newCurrentCellCoordinates);
            _owningGrid.HandleColumnCollectionChangedPostNotification(false /*columnsGrew*/);
        }
        finally
        {
            _owningGrid.NoCurrentCellChangeCount--;
        }
    }
    
    protected override void RemoveItem(int columnIndex)
    {
        RemoveItemPrivate(columnIndex, false /*isSpacer*/);
    }
    
    protected override void SetItem(int columnIndex, DataGridColumn dataGridColumn)
    {
        RemoveItem(columnIndex);
        InsertItem(columnIndex, dataGridColumn);
    }
    
    internal bool DisplayInOrder(int columnIndex1, int columnIndex2)
    {
        int displayIndex1 = ItemsInternal[columnIndex1].DisplayIndexWithFiller;
        int displayIndex2 = ItemsInternal[columnIndex2].DisplayIndexWithFiller;
        return displayIndex1 < displayIndex2;
    }
    
    internal bool EnsureRowGrouping(bool rowGrouping)
    {
        // The insert below could cause the first column to be added.  That causes a refresh 
        // which re-enters method so instead of checking RowGroupSpacerColumn.IsRepresented, 
        // we need to check to see if it's actually in our collection instead.
        bool spacerRepresented = (ItemsInternal.Count > 0) && (ItemsInternal[0] == RowGroupSpacerColumn);
        Debug.Assert(RowGroupSpacerColumn != null);
        if (rowGrouping && !spacerRepresented)
        {
            Insert(0, RowGroupSpacerColumn);
            RowGroupSpacerColumn.IsRepresented = true;
            return true;
        }
        if (!rowGrouping && spacerRepresented)
        {
            // We need to set IsRepresented to false before removing the RowGroupSpacerColumn
            // otherwise, we'll remove the column after it
            RowGroupSpacerColumn.IsRepresented = false;
            RemoveItemPrivate(0, true /*isSpacer*/);
            return true;
        }
        return false;
    }
}