using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Data;

namespace OrderEntrySystem
{
    internal class ColumnUtility
    {
        private List<ColumnValues> columnValues = new List<ColumnValues>();

        internal GridViewColumn BuildColumn(PropertyInfo prop)
        {
            GridViewColumn gridViewColumn = new GridViewColumn();
            string description = DisplayUtil.GetColumnDescription(prop);
            double width = DisplayUtil.GetColumnWidth(prop);

            // Set all columns predefined width from entity control attribute.
            this.AddColumnValues(description, width);

            gridViewColumn.Header = description;
            gridViewColumn.Width = double.NaN;
            Logger(gridViewColumn, null);
            gridViewColumn.DisplayMemberBinding = new Binding(prop.Name);
            return gridViewColumn;
        }

        internal void ResizeColumns(IEnumerable<GridViewColumn> columns, double listViewWidth)
        {
            // Updates column values before width adjustments.
            this.UpdateValuesPreAdjustment(columns);
            this.CalculateMultiplier(columns, listViewWidth);
        }

        private void CalculateMultiplier(IEnumerable<GridViewColumn> columns, double listViewWidth)
        {
            Func<double, double> perColumnModifier;
            ColumnWidthType columnWidthType = ColumnWidthType.AllColumns;
            double gridViewWidth = this.columnValues.Sum(c => c.CurrentWidth);
            double widthDelta = listViewWidth - gridViewWidth;

            // If the window width is larger than the list view's width 
            if (widthDelta > 0)
            {
                double sumCurrentMaxDelta = this.columnValues.Sum(c => c.MaxMinusCurrent);

                // If the window width is larger than the list view's width after setting all columns to max auto size.
                // Get the amount of width to add to every column to fill window's width.
                if (widthDelta > sumCurrentMaxDelta)
                {
                    perColumnModifier = x => x + widthDelta / columns.Count();
                }
                else
                {
                    // Increase non max width columns
                    double nonMaxWidth = this.columnValues.Where(c => !c.IsMaxWidth).Sum(c => c.CurrentWidth);
                    double maxWidth = this.columnValues.Where(c => c.IsMaxWidth).Sum(c => c.CurrentWidth);
                    perColumnModifier = x => x * ((listViewWidth - maxWidth) / nonMaxWidth);
                    columnWidthType = ColumnWidthType.NonMax;
                }
            }
            else
            {
                // If the window width is smaller than the list view's width
                columnWidthType = ColumnWidthType.AllColumns;
                double percentModifier = (listViewWidth / gridViewWidth);
                perColumnModifier = x => x * percentModifier;
            }

            this.AdjustColumnWidth(columns, perColumnModifier, columnWidthType);
        }

        private void AddColumnValues(string columnName, double width)
        {
            if (!this.columnValues.Exists(c => c.ColumnName == columnName))
            {
                this.columnValues.Add(new ColumnValues(columnName, width));
            }
        }

        private void AdjustColumnWidth(IEnumerable<GridViewColumn> columns, Func<double, double> multiplier, ColumnWidthType columnType)
        {
            IEnumerable<ColumnValues> filteredColumnValues = this.FilterColumnValues(columns, columnType);

            // Updates the column widths with the modifier.  
            // The modifier value is based on the different between the list view width and the grid view width.
            this.UpdateColumnValue(columns, filteredColumnValues, (cv, c) =>
            {
                double oldWidth = c.Width;
                double result = multiplier.Invoke(cv.CurrentWidth);
                c.Width = result > 20 ? result : 20;
                cv.CurrentWidth = c.Width;
                Logger(c, oldWidth);
            });
        }

        private IEnumerable<ColumnValues> FilterColumnValues(IEnumerable<GridViewColumn> columns, ColumnWidthType columnType)
        {
            IEnumerable<ColumnValues> filteredColumnValues = null;
            switch (columnType)
            {
                case ColumnWidthType.AllColumns:
                    filteredColumnValues = this.columnValues;
                    break;
                case ColumnWidthType.NonMax:
                    filteredColumnValues = from c in this.columnValues where !c.IsMaxWidth select c;
                    break;
            }

            return filteredColumnValues;
        }

        /// <summary>
        /// Updates column widths.
        /// Callback action decides what width to update and how to update.
        /// </summary>
        /// <param name="columns"></param>
        /// <param name="values"></param>
        /// <param name="callBack"></param>
        private void UpdateColumnValue(IEnumerable<GridViewColumn> columns, IEnumerable<ColumnValues> values, Action<ColumnValues, GridViewColumn> callBack)
        {
            foreach (GridViewColumn column in columns)
            {
                ColumnValues columnValues = values.FirstOrDefault(c => c.ColumnName == column.Header.ToString());
                if (columnValues != null)
                {
                    callBack(columnValues, column);
                }
            }
        }

        /// <summary>
        /// Updates the column value classes before making adjustments.
        /// Column value classes store the default, max, and current widths for all columns.
        /// </summary>
        /// <param name="columns"></param>
        private void UpdateValuesPreAdjustment(IEnumerable<GridViewColumn> columns)
        {
            if (columns.Sum(c => c.ActualWidth) != 0)
            {
                // Set non-autosized columns max width if they haven't been set in the column values yet.
                if (!this.columnValues.TrueForAll(c => c.MaxWidthIsSet))
                {
                    this.UpdateColumnValue(columns, this.columnValues, (cv, c) =>
                    {
                        double oldWidth = c.Width;
                        cv.MaxWidth = c.ActualWidth;
                        Console.WriteLine($"{c.Header}: MaxWidth = {c.ActualWidth}, DefaultWidth = {cv.DefaultWidth}");

                        // Set all columns to the default values.  Default values are values from entity column attribute.
                        // Sets width to max width if default is double.nan
                        c.Width = cv.DefaultWidth;
                        cv.CurrentWidth = double.IsNaN(c.Width) ? cv.MaxWidth : c.Width;
                    });
                }
                else
                {
                    // Sets the column values current width equal to the column's width
                    this.UpdateColumnValue(columns, this.columnValues, (cv, c) =>
                    {
                        cv.CurrentWidth = c.Width;
                    });
                }
            }
        }

        private static void Logger(GridViewColumn column, double? oldValue)
        {
            Console.WriteLine($"{column.Header}:  {oldValue}  =>  {column.Width}");
        }
    }
}
