using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;

namespace AtlasGeneratorLib
{
    public struct Vector2<T>
    {
        public T x;
        public T y;

        public Vector2(T _x, T _y)
        {
            x = _x;
            y = _y;
        }
    }

    public struct InsertedImage
    {
        public Vector2<uint> m_Pos;
        public Vector2<uint> m_Space;
        public ImageInfo m_Info;
    }

    public class Atlas
    {
        public Atlas(uint _iSize, uint _iMargin, uint _iAlignment)
        {
            m_iSize = _iSize;
            m_LargestFree = new Vector2<uint>();
            m_LargestFree.x = _iSize;
            m_LargestFree.y = _iSize;
            m_lInsertedImages = new List<InsertedImage>();
            m_lFreeSpaces = new List<InsertedImage>();
            var temp = new InsertedImage();
            var offset = (_iMargin > _iAlignment) ? _iMargin : _iAlignment;
            temp.m_Space.x = _iSize - offset;
            temp.m_Space.y = _iSize - offset;
            temp.m_Pos.x = offset;
            temp.m_Pos.y = offset;
            m_lFreeSpaces.Add(temp);
        }

        public uint m_iSize;
        public Vector2<uint> m_LargestFree;
        public List<InsertedImage> m_lInsertedImages;
        public List<InsertedImage> m_lFreeSpaces;
    }

    class SimpleBestFitDecreasing
    {
        public static List<Atlas> BuildAtlases(List<ImageInfo> _lImages, uint _iAtlasSize, uint _iMargin, uint _iAlignment)
        {
            // since the images are loaded parallel, the sort order of equally sized images is not deterministic
            // therefore we have to sort equally sized images by their path and filename
            _lImages.Sort(delegate(ImageInfo x, ImageInfo y)
            {
                int result = Math.Max(y.m_iHeight, y.m_iWidth).CompareTo(Math.Max(x.m_iHeight, x.m_iWidth));
                if (result == 0)
                {
                    result = (y.m_iHeight + y.m_iWidth).CompareTo(x.m_iHeight + x.m_iWidth);
                    if (result == 0)
                    {
                        result = y.m_sPath.CompareTo(x.m_sPath);
                    }
                }
                return result;
            });

            List<Atlas> lAtlases = new List<Atlas>();
            lAtlases.Add(new Atlas(_iAtlasSize, _iMargin, _iAlignment));
            foreach (var image in _lImages)
            {
                uint j = 0;
                bool bDidInsertImage = false;
                foreach (var atlas in lAtlases)
                {
                    if (TryInsert(atlas, image, _iMargin, _iAlignment))
                    {
                        bDidInsertImage = true;
                        break;
                    }
                    j++;
                }
                if (!bDidInsertImage)
                {
                    var tempAtlas = new Atlas(_iAtlasSize, _iMargin, _iAlignment);
                    if (!TryInsert(tempAtlas, image, _iMargin, _iAlignment))
                    {
                        throw new Exception("Image too large for specified atlas size: " + image.m_sPath);
                    }
                    lAtlases.Add(tempAtlas);
                }
            }

            return lAtlases;
        }

        static bool TryInsert(Atlas _atlas, ImageInfo _imageInfo, uint _iMargin, uint _iAlignment)
        {
            uint iHeight = Align((uint)_imageInfo.m_iHeight + _iMargin, _iAlignment);
            uint iWidth = Align((uint)_imageInfo.m_iWidth + _iMargin, _iAlignment);
            int k;
            int indexMatch1 = -1;
            int indexMatch2 = -1;
            int indexMatch3 = -1;
            for (k = _atlas.m_lFreeSpaces.Count - 1; k >= 0; k--)
            {
                var temp = _atlas.m_lFreeSpaces[k];
                if (temp.m_Space.x >= iWidth && temp.m_Space.y >= iHeight)
                {
                    if (indexMatch3 < 0)
                    {
                        indexMatch3 = k;
                    }
                    if (temp.m_Space.x == iWidth || temp.m_Space.y == iHeight)
                    {
                        if (indexMatch2 < 0)
                        {
                            indexMatch2 = k;
                        }
                        if (temp.m_Space.x == iWidth && temp.m_Space.y == iHeight)
                        {
                            if (indexMatch1 < 0)
                            {
                                indexMatch1 = k;
                            }
                        }
                    }
                }
            }

            k = indexMatch1;
            if (k < 0)
            {
                k = indexMatch2;
            }
            if (k < 0)
            {
                k = indexMatch3;
            }
            if (k < 0)
            {
                return false;
            }
            else
            {
                var temp = _atlas.m_lFreeSpaces[k];
                var tempImage = new InsertedImage();
                tempImage.m_Pos = temp.m_Pos;
                tempImage.m_Space = new Vector2<uint>((uint)_imageInfo.m_iWidth, (uint)_imageInfo.m_iHeight);
                tempImage.m_Info = _imageInfo;
                _atlas.m_lInsertedImages.Add(tempImage);
                _atlas.m_lFreeSpaces.RemoveRange(k, 1);

                //temp.m_pos and temp.m_space are already guaranteed to be in allignment because they were generated in allignment

                var newFreeSpace1 = new InsertedImage();
                newFreeSpace1.m_Pos = new Vector2<uint>(temp.m_Pos.x, temp.m_Pos.y + iHeight);
                newFreeSpace1.m_Space = new Vector2<uint>(temp.m_Space.x, temp.m_Space.y - iHeight);
                var newFreeSpace2 = new InsertedImage();
                newFreeSpace2.m_Pos = new Vector2<uint>(temp.m_Pos.x + iWidth, temp.m_Pos.y);
                newFreeSpace2.m_Space = new Vector2<uint>(temp.m_Space.x - iWidth, iHeight);

                _atlas.m_lFreeSpaces.Add(newFreeSpace1);
                _atlas.m_lFreeSpaces.Add(newFreeSpace2);
                _atlas.m_lFreeSpaces.Sort((x, y) =>
                    {
                        int result = Math.Max(y.m_Space.x, y.m_Space.y).CompareTo(Math.Max(x.m_Space.x, x.m_Space.y));
                        if (result == 0)
                        {
                            result = (y.m_Space.x + y.m_Space.y).CompareTo(x.m_Space.x + x.m_Space.y);
                            if (result == 0)
                            {
                                result = y.m_Pos.y.CompareTo(x.m_Pos.y);
                                if (result == 0)
                                {
                                    result = y.m_Pos.x.CompareTo(x.m_Pos.x);
                                }
                            }
                        }
                        return result;
                    });

                return true;
            }
        }

        static uint Align(uint _x, uint _iAlignment)
        {
            return _x + _x % _iAlignment;
        }
    }
}
