using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Collections.Generic;

namespace GeoDashboard
{
    /// <summary>
    /// Similar to a WrapPanel, but uses space more effectively when children
    /// have varying sizes.
    /// </summary>
    public class TilePanel : Panel
    {
        bool _dirty;
        int _childCount;
        Size _panelSize;

        // overrides
        protected override Size MeasureOverride(Size availableSize)
        {
            _dirty = true;
            return Arrange(availableSize, false);
        }
        protected override Size ArrangeOverride(Size finalSize)
        {
            if (_dirty || finalSize != _panelSize || _childCount != Children.Count)
            {
                finalSize = Arrange(finalSize, true);
                _dirty = false;
                _childCount = Children.Count;
                _panelSize = finalSize;
            }
            return finalSize;
        }

        // measure/arrange
        Size Arrange(Size sz, bool arrange)
        {
            // measure children
            if (!arrange)
            {
                foreach (var child in Children)
                {
                    child.Measure(sz);
                }
            }

            // build position list
            var list = new List<Rect>();
            foreach (var child in Children)
            {
                var rc = PositionChild(child, sz, list);
                list.Add(rc);
                if (arrange)
                {
                    child.Arrange(rc);
                }
            }

            // compute size
            sz = new Size(0, 0);
            foreach (var rc in list)
            {
                sz.Width = Math.Max(sz.Width, rc.Right);
                sz.Height = Math.Max(sz.Height, rc.Bottom);
            }

            // done
            return sz;
        }
        Rect PositionChild(UIElement child, Size bounds, List<Rect> list)
        {
            var ds = child.DesiredSize;
            var rr = new Rect(0, 0, ds.Width, ds.Height);

            // empty list? easy
            if (list.Count == 0)
            {
                return rr;
            }

            rr = Rect.Empty;
            foreach (var rc in list)
            {
                // try rect to the right
                var r1 = new Rect(rc.Right, rc.Y, ds.Width, ds.Height);
                if (TryRect(r1, bounds, list, rr))
                {
                    rr = r1;
                }

                // try rect below
                var r2 = new Rect(rc.X, rc.Bottom, ds.Width, ds.Height);
                if (TryRect(r2, bounds, list, rr))
                {
                    rr = r2;
                }
            }

            // return best rect
            return rr;
        }
        bool TryRect(Rect tr, Size bounds, List<Rect> list, Rect rr)
        {
            // choose lowest y
            if (tr.Y > rr.Y)
            {
                return false;
            }

            // choose lowest x
            if (tr.Y == rr.Y && tr.X > rr.X)
            {
                return false;
            }

            // check bounds
            if (tr.X > 0 && tr.Right > bounds.Width)
            {
                return false;
            }

            // check for overlaps
            foreach (var rc in list)
            {
                var ir = tr;
                ir.Intersect(rc);
                if (ir.Width > 0 && ir.Height > 0)
                {
                    return false;
                }
            }

            // seems OK
            return true;
        }
    }
}
