using System;
using System.Linq;

using C1.Silverlight.Data;
using C1.Silverlight.DataGrid;
using System.Windows;
using System.Windows.Data;
using System.Collections.Generic;
using System.Windows.Controls;

namespace C1DataGrid_Demo2010
{
    public class BindingExtractorDependencyObject
        : DependencyObject
    {
        public object Property
        {
            get { return (object)GetValue(PropertyProperty); }
            set { SetValue(PropertyProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Property.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PropertyProperty =
            DependencyProperty.Register("Property", typeof(object), typeof(BindingExtractorDependencyObject), new PropertyMetadata(null));
    }


    public static class MergingHelper
    {
        #region ** Merge

        static public List<DataGridCellsRange> Merge(Orientation orientation, DataGridRow[] rows, DataGridColumn[] columns, bool hierarchical)
        {
            var merges = new List<DataGridCellsRange>();

            if ((rows.Length == 0) || (columns.Length == 0))
                return merges;

            var datagrid = rows[0].DataGrid;
            DataGridCellsRange currentRange = null;
            var iterationLength = (orientation == Orientation.Vertical) ? rows.Length : columns.Length;
            int i = 0;

            while (i < iterationLength)
            {
                // skip empty cells 
                DataGridCell nextCell = null;
                while (nextCell == null && i < iterationLength)
                {
                    nextCell = (orientation == Orientation.Vertical) ? datagrid[rows[i], columns[0]] : datagrid[rows[0], columns[i]];
                    i++;
                }

                // there are no more cell in this column, end iteration
                if (nextCell == null)
                    break;

                // can expand the merge?
                if (CanMerge(orientation, currentRange, nextCell))
                {
                    // expand the merged range
                    currentRange = MergingHelper.ExpandRange(currentRange, nextCell);
                }
                else
                {
                    // cannot merge anymore, add the last range we have
                    merges.Add(currentRange);
                    currentRange = MergingHelper.ExpandRange(null, nextCell);
                }
            }

            // add last merge to the collection
            if (currentRange != null)
                merges.Add(currentRange);


            // recursion
            var pendingColumns = (orientation == Orientation.Vertical) ? columns.Skip(1).ToArray() : columns;
            var pendingRows = (orientation == Orientation.Vertical) ? rows : rows.Skip(1).ToArray();
            var innerMerges = new List<DataGridCellsRange>();

            if (!hierarchical)
            {
                // treat each row independently
                // and add inner merges to the results
                var tmp = Merge(orientation, pendingRows, pendingColumns, hierarchical);
                merges.AddRange(tmp);
            }
            else
            {
                // merge in the other direction, bounding to the parent range limits
                foreach (var range in new List<DataGridCellsRange>(merges))
                {
                    innerMerges = (orientation == Orientation.Vertical)
                                ? Merge(orientation, range.Rows.ToArray(), pendingColumns, hierarchical)
                                : Merge(orientation, pendingRows, range.Columns.ToArray(), hierarchical);

                    // look into the inner merged ranges, to check if possible to expand the current merge in the other direction
                    var continueMerging = true;
                    var expandedRange = range;

                    while (innerMerges.Count > 0 && continueMerging)
                    {
                        var tmp = innerMerges[0];
                        if (CanMerge(orientation.Opposite(), expandedRange, tmp))
                        {
                            expandedRange = MergingHelper.ExpandRange(expandedRange, tmp.BottomRightCell);
                            innerMerges.Remove(tmp);
                        }
                        else
                        {
                            continueMerging = false;
                        }
                    }

                    // replace range for the expanded one
                    if (expandedRange != range)
                    {
                        merges[merges.IndexOf(range)] = expandedRange;
                    }

                    // and add inner merges to the results
                    merges.AddRange(innerMerges);
                }
            }

            return merges;
        }

        #endregion

        #region ** CanMerge

        private static bool CanMerge(Orientation orientation, DataGridCellsRange currentRange, DataGridCellsRange newRange)
        {
            if (currentRange == null)
                return true;

            var datagrid = newRange.TopLeftCell.DataGrid;

            if (orientation == Orientation.Vertical)
            {
                return (object.Equals(MergingHelper.GetCellValue(currentRange.TopLeftCell), MergingHelper.GetCellValue(newRange.TopLeftCell))
                        && (currentRange.TopLeftCell.Column == newRange.TopLeftCell.Column)
                        && (currentRange.BottomRightCell.Column == newRange.BottomRightCell.Column));
            }
            else
            {
                return (object.Equals(MergingHelper.GetCellValue(currentRange.TopLeftCell), MergingHelper.GetCellValue(newRange.TopLeftCell))
                        && (currentRange.TopLeftCell.Row == newRange.TopLeftCell.Row)
                        && (currentRange.BottomRightCell.Row == newRange.BottomRightCell.Row));
            }
        }

        private static bool CanMerge(Orientation orientation, DataGridCellsRange currentRange, DataGridCell cell)
        {
            if (currentRange == null)
                return true;

            var datagrid = cell.DataGrid;
            var last = currentRange.BottomRightCell;
            var first = currentRange.TopLeftCell;

            if (orientation == Orientation.Vertical)
            {
                return (object.Equals(MergingHelper.GetCellValue(first), MergingHelper.GetCellValue(cell)))
                           && (last.Row.Index == cell.Row.Index - 1)
                           && (last.Row.ParentGroup == cell.Row.ParentGroup)
                           && (datagrid.FreezingSection(cell) == datagrid.FreezingSection(last));
            }
            else
            {
                return (object.Equals(MergingHelper.GetCellValue(first), MergingHelper.GetCellValue(cell)))
                       && (last.Column.DisplayIndex == cell.Column.DisplayIndex - 1)
                       && (last.Row.ParentGroup == cell.Row.ParentGroup)
                       && (datagrid.FreezingSection(cell) == datagrid.FreezingSection(last));
            }
        }

        #endregion

        static Binding CopyBinding(Binding binding)
        {
            var b = new Binding(binding.Path.Path);
            b.Mode = BindingMode.OneTime;
            b.Converter = binding.Converter;
            b.ConverterParameter = binding.ConverterParameter;

            if (binding.Source != null)
                b.Source = binding.Source;

            return b;
        }

        public static object GetCellValue(DataGridCell cell)
        {
            // We used the binding here previously, but that doesn't work for column headers.
            object content = cell.Presenter;
            while (true)
            {
                if (content is ContentControl)
                {
                    content = (content as ContentControl).Content;
                }
                else if (content is TextBlock)
                {
                    return (content as TextBlock).Text;
                }
                else if (content is TextBox)
                {
                    return (content as TextBox).Text;
                }
                else
                {
                    return content;
                }
            }
        }

        public static DataGridCellsRange ExpandRange(DataGridCellsRange currentRange, DataGridCell cell)
        {
            if (currentRange == null)
            {
                return new DataGridCellsRange(cell);
            }
            else
            {
                return new DataGridCellsRange(currentRange.TopLeftCell, cell);
            }
        }
    }

    public static class MergeExtensions
    {
        public static void ReMerge(this C1DataGrid datagrid)
        {
            datagrid.Refresh();
        }

        /// <summary>
        /// Returns a value different value for each viewport section (having freezing into account).
        /// The matrix is defined as follows, to facilitate calculations.
        /// 
        ///     -5  |  -3  |  -1
        ///    -------------------
        ///     -2  |   0  |   2
        ///     -------------------
        ///      1  |   3  |   5
        /// </summary>
        /// <param name="datagrid"></param>
        /// <param name="cell"></param>
        /// <returns></returns>
        public static int FreezingSection(this C1DataGrid datagrid, DataGridCell cell)
        {
            int section = 0;

            // vertical freezing
            if (cell.Row.Index < datagrid.FrozenTopRowsCount)
                section -= 3;
            else if ((datagrid.Rows.Count - cell.Row.Index) < datagrid.FrozenBottomRowsCount)
                section += 3;

            // horizontal freezing (only supports left freezing by now)
            if (cell.Column.DisplayIndex < datagrid.FrozenColumnCount)
                section -= 2;
            //else if ((datagrid.Columns.Count - cell.Column.DisplayIndex) < datagrid.RightFrozenColumnCount)
            //    section += 2;

            return section;
        }

        public static Orientation Opposite(this Orientation orientation)
        {
            return (orientation == Orientation.Vertical)
                    ? Orientation.Horizontal
                    : Orientation.Vertical;
        }
    }

}
