using System;
using System.Windows;
using System.Windows.Controls;

namespace HandyControl.Controls
{
    public class HoneycombPanel : Panel
    {
        private double _unitLength;

        private HoneycombStuffer _stuffer;

        private static int GetXCount(int count)
        {
            if (count == 0) return 0;

            count -= 1;

            var index = (int) Math.Floor(Math.Pow((12.0 * count + 25) / 36, 0.5) - 5.0 / 6);
            var valeIndex = 3 * index * index + 5 * index;
            var centerValue = valeIndex + 2;

            return count >= centerValue
                ? 4 * index + 6
                : count > valeIndex
                    ? 4 * index + 4
                    : 4 * index + 2;
        }

        private static int GetYCount(int count)
        {
            if (count == 0) return 0;

            count -= 1;

            var index = (int)Math.Floor(Math.Pow(count / 3.0 + 0.25, 0.5) - 0.5);
            var valeIndex = 3 * index * index + 3 * index;

            return count > valeIndex
                ? 2 * index + 2
                : 2 * index;
        }

        /*
         *                layout order
         *
         *                  ●       ●
         *                 (7)     (8)
         *
         *              ●       ●       ●
         *             (6)     (1)     (9)
         *
         *          ●       ●       ●       ...
         *         (5)     (0)     (2)
         *
         *              ●       ●       ...
         *             (4)     (3)
         *
         */
        protected override Size MeasureOverride(Size availableSize)
        {
            var maxSize = new Size();

            foreach (UIElement child in InternalChildren)
            {
                if (child != null)
                {
                    child.Measure(availableSize);
                    maxSize.Width = Math.Max(maxSize.Width, child.DesiredSize.Width);
                    maxSize.Height = Math.Max(maxSize.Height, child.DesiredSize.Height);
                }
            }

            _unitLength = Math.Max(maxSize.Width, maxSize.Height) / 2;

            var xCount = GetXCount(InternalChildren.Count);
            var yCount = GetYCount(InternalChildren.Count);

            var availableWidth = xCount * _unitLength;
            var availableHeight = yCount * Math.Pow(3, 0.5) * _unitLength + _unitLength * 2;

            return new Size(availableWidth, availableHeight);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            var childLength = _unitLength * 2;
            _stuffer = new HoneycombStuffer(new Rect(finalSize.Width / 2 - _unitLength,
                finalSize.Height / 2 - _unitLength, childLength, childLength));

            foreach (UIElement child in InternalChildren)
            {
                child.Arrange(_stuffer.Move());
            }

            return finalSize;
        }

        private class HoneycombStuffer
        {
            private int _turns;

            private int _maxIndex;

            private int _currentIndex = -1;

            private readonly double _offsetX;

            private readonly double _offsetY;

            private Rect _childBounds;

            private readonly double[] _offsetXArr;

            private readonly double[] _offsetYArr;

            public HoneycombStuffer(Rect childBounds)
            {
                _childBounds = childBounds;
                _offsetX = childBounds.Width / 2;
                _offsetY = Math.Pow(3, 0.5) * _offsetX;

                _offsetXArr = new[]
                {
                    2 * _offsetX,
                    _offsetX,
                    -_offsetX,
                    -2 * _offsetX,
                    -_offsetX,
                    _offsetX
                };

                _offsetYArr = new[]
                {
                    0,
                    _offsetY,
                    _offsetY,
                    0,
                    -_offsetY,
                    -_offsetY
                };
            }

            public Rect Move()
            {
                _currentIndex++;
                if (_currentIndex > _maxIndex)
                {
                    _turns++;
                    _maxIndex = _turns * 6 - 1;
                    _currentIndex = 0;
                    _childBounds.Offset(_offsetX, -_offsetY);
                    return _childBounds;
                }

                if (_turns > 0)
                {
                    var index = _currentIndex / _turns;
                    _childBounds.Offset(_offsetXArr[index], _offsetYArr[index]);
                }

                return _childBounds;
            }
        }
    }
}