using System;
using System.Linq;
using System.Dynamic;
using System.Collections;
using System.Windows.Data;
using System.ComponentModel;
using System.Collections.Generic;

namespace DrillDown
{
    public class DataLayer : Bindable
    {
        private int _depth = 0;
        private string _bindingX;
        private string _binding;
        private string _groupBy;
        private object _current;
        private object _parent;
        private IEnumerable _itemsSource;
        private ICollectionView _view;
        private List<object> _chartView;
        private AggregateType _aggType = AggregateType.Sum;
        private Dictionary<string, List<object>> _histories = new Dictionary<string, List<object>>();

        public DataLayer(IEnumerable dataSource)
        {
            _view = CollectionViewSource.GetDefaultView(dataSource);
            BackCommand = new RelayCommand(p => Back(p));
        }

        public int Depth
        {
            get { return _depth; }
            set
            {
                SetProperty(ref _depth, value, "Depth");
            }
        }

        public string GroupBy
        {
            get { return _groupBy; }
            set
            {
                if (_groupBy != value)
                {
                    _groupBy = value;
                    _view.GroupDescriptions.Clear();
                    _histories.Clear();
                    foreach (var groupName in GroupNames)
                    {
                        _view.GroupDescriptions.Add(new PropertyGroupDescription(groupName));
                        _histories.Add(groupName, null);
                    }
                    Depth = 0;
                    ChartView = _view.Groups.ToList();
                    Parent = Current = null;
                    Notify("GroupNames");
                }
            }
        }

        public string Binding
        {
            get { return _binding; }
            set
            {
                SetProperty(ref _binding, value, "Binding");
            }
        }

        public string BindingX
        {
            get { return _bindingX; }
            set
            {
                SetProperty(ref _bindingX, value, "BindingX");
            }
        }

        public AggregateType AggregateType
        {
            get { return _aggType; }
            set
            {
                _aggType = value;
                UpdateChartItemsSource();
            }
        }

        public object Current
        {
            get { return _current; }
            set
            {
                SetProperty(ref _current, value, "Current");
            }
        }

        public object Parent
        {
            get { return _parent; }
            set
            {
                SetProperty(ref _parent, value, "Parent");
            }
        }

        public IEnumerable ItemsSource
        {
            get { return _itemsSource; }
            set
            {
                SetProperty(ref _itemsSource, value, "ItemsSource");
            }
        }

        internal List<object> ChartView
        {
            get { return _chartView; }
            set
            {
                _chartView = value;
                OnChartViewChanged();
            }
        }

        public List<string> GroupNames
        {
            get { return GroupBy.Split(',').ToList(); }
        }

        public RelayCommand BackCommand
        {
            get;set;
        }

        private double Aggregate(object item, AggregateType type)
        {
            var group = item as CollectionViewGroup;
            if (group != null)
            {
                var sum = group.Items.Sum(sub => Aggregate(sub, AggregateType.Sum));
                var sum2 = Sum(item);
                var cnt = group.ItemCount;
                var avg = sum / cnt;
                switch (type)
                {
                    case AggregateType.Avg:
                        return Math.Floor(avg);
                    case AggregateType.Max:
                        return group.Items.Max(sub => Aggregate(sub, type));
                    case AggregateType.Min:
                        return group.Items.Min(sub => Aggregate(sub, type));
                    case AggregateType.Rng:
                        return group.Items.Max(sub => Aggregate(sub, AggregateType.Max)) - group.Items.Min(sub => Aggregate(sub, AggregateType.Min));
                    case AggregateType.Cnt:
                        return cnt;
                    case AggregateType.Std:
                        return Math.Floor(Math.Sqrt((sum2 / cnt - avg * avg) * cnt / (cnt - 1)));
                    case AggregateType.Var:
                        return Math.Floor((sum2 / cnt - avg * avg) * cnt / (cnt - 1));
                    case AggregateType.StdPop:
                        return Math.Floor(Math.Sqrt(sum2 / cnt - avg * avg));
                    case AggregateType.VarPop:
                        return Math.Floor(sum2 / cnt - avg * avg);
                    case AggregateType.Sum:
                    default:
                        return sum;
                }
            }
            else
            {
                return (item as Item).Amount;
            }
        }

        private double Sum(object item)
        {
            var group = item as CollectionViewGroup;
            if (group != null)
            {
                return group.Items.Sum(sub => Sum(sub));
            }
            else
            {
                var amount = (item as Item).Amount;
                return amount * amount;
            }
        }

        private void OnChartViewChanged()
        {
            _histories[GroupNames[Depth]] = ChartView;
            UpdateChartItemsSource();
        }

        private void UpdateChartItemsSource()
        {
            List<object> groups = new List<object>();
            var groupName = GroupNames[Depth];
            foreach (var item in ChartView)
            {
                dynamic groupItem = new ExpandoObject();
                var dt = groupItem as IDictionary<string, object>;
                dt[groupName] = (item as CollectionViewGroup).Name;
                dt["Value"] = Aggregate(item, AggregateType);
                groups.Add(groupItem);
            }
            BindingX = groupName;
            ItemsSource = groups;
        }

        public virtual void DrillDown(int index)
        {
            if (Depth >= GroupNames.Count - 1)
                return;

            Depth++;
            var selectedGroup = ChartView[index] as CollectionViewGroup;
            Parent = Current;
            Current = selectedGroup;
            ChartView = selectedGroup.Items.ToList();
        }

        public virtual void Back(string chartViewPath)
        {
            if (_histories.Keys.Contains(chartViewPath))
            {
                Depth = GroupNames.IndexOf(chartViewPath);
                ChartView = _histories[chartViewPath];
            }
        }
    }
}
