text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

struct FlexBoxLayoutCalculation
{
    using Coord = double;

    FlexBoxLayoutCalculation (const FlexBox& fb, Coord w, Coord h)
       : owner (fb), parentWidth (w), parentHeight (h), numItems (owner.items.size()),
         isRowDirection (fb.flexDirection == FlexBox::Direction::row
                          || fb.flexDirection == FlexBox::Direction::rowReverse),
         containerLineLength (isRowDirection ? parentWidth : parentHeight)
    {
        lineItems.calloc (numItems * numItems);
        lineInfo.calloc (numItems);
    }

    struct ItemWithState
    {
        ItemWithState (FlexItem& source) noexcept   : item (&source) {}

        FlexItem* item;
        Coord lockedWidth = 0, lockedHeight = 0;
        Coord lockedMarginLeft = 0, lockedMarginRight = 0, lockedMarginTop = 0, lockedMarginBottom = 0;
        Coord preferredWidth = 0, preferredHeight = 0;
        bool locked = false;

        void resetItemLockedSize() noexcept
        {
            lockedWidth        = preferredWidth;
            lockedHeight       = preferredHeight;
            lockedMarginLeft   = getValueOrZeroIfAuto (item->margin.left);
            lockedMarginRight  = getValueOrZeroIfAuto (item->margin.right);
            lockedMarginTop    = getValueOrZeroIfAuto (item->margin.top);
            lockedMarginBottom = getValueOrZeroIfAuto (item->margin.bottom);
        }

        void setWidthChecked (Coord newWidth) noexcept
        {
            if (isAssigned (item->maxWidth))  newWidth = jmin (newWidth, static_cast<Coord> (item->maxWidth));
            if (isAssigned (item->minWidth))  newWidth = jmax (newWidth, static_cast<Coord> (item->minWidth));

            lockedWidth = newWidth;
        }

        void setHeightChecked (Coord newHeight) noexcept
        {
            if (isAssigned (item->maxHeight))  newHeight = jmin (newHeight, (Coord) item->maxHeight);
            if (isAssigned (item->minHeight))  newHeight = jmax (newHeight, (Coord) item->minHeight);

            lockedHeight = newHeight;
        }
    };

    struct RowInfo
    {
        int numItems;
        Coord crossSize, lineY, totalLength;
    };

    const FlexBox& owner;
    const Coord parentWidth, parentHeight;
    const int numItems;
    const bool isRowDirection;
    const Coord containerLineLength;

    int numberOfRows = 1;
    Coord containerCrossLength = 0;

    HeapBlock<ItemWithState*> lineItems;
    HeapBlock<RowInfo> lineInfo;
    Array<ItemWithState> itemStates;

    ItemWithState& getItem (int x, int y) const noexcept     { return *lineItems[y * numItems + x]; }

    static bool isAuto (Coord value) noexcept                { return value == FlexItem::autoValue; }
    static bool isAssigned (Coord value) noexcept            { return value != FlexItem::notAssigned; }
    static Coord getValueOrZeroIfAuto (Coord value) noexcept { return isAuto (value) ? Coord() : value; }

    //==============================================================================
    void createStates()
    {
        itemStates.ensureStorageAllocated (numItems);

        for (auto& item : owner.items)
            itemStates.add (item);

        itemStates.sort (*this, true);

        for (auto& item : itemStates)
        {
            item.preferredWidth  = getPreferredWidth  (item);
            item.preferredHeight = getPreferredHeight (item);
        }
    }

    void initialiseItems() noexcept
    {
        if (owner.flexWrap == FlexBox::Wrap::noWrap)  // for single-line, all items go in line 1
        {
            lineInfo[0].numItems = numItems;
            int i = 0;

            for (auto& item : itemStates)
            {
                item.resetItemLockedSize();
                lineItems[i++] = &item;
            }
        }
        else // if multi-line, group the flexbox items into multiple lines
        {
            auto currentLength = containerLineLength;
            int column = 0, row = 0;
            bool firstRow = true;

            for (auto& item : itemStates)
            {
                item.resetItemLockedSize();

                const auto flexitemLength = getItemLength (item);

                if (flexitemLength > currentLength)
                {
                    if (! firstRow)
                        row++;

                    if (row >= numItems)
                        break;

                    column = 0;
                    currentLength = containerLineLength;
                    numberOfRows = jmax (numberOfRows, row + 1);
                }

                currentLength -= flexitemLength;
                lineItems[row * numItems + column] = &item;
                ++column;
                lineInfo[row].numItems = jmax (lineInfo[row].numItems, column);
                firstRow = false;
            }
        }
    }

    void resolveFlexibleLengths() noexcept
    {
        for (int row = 0; row < numberOfRows; ++row)
        {
            resetRowItems (row);

            for (int maxLoops = numItems; --maxLoops >= 0;)
            {
                resetUnlockedRowItems (row);

                if (layoutRowItems (row))
                    break;
            }
        }
    }

    void resolveAutoMarginsOnMainAxis() noexcept
    {
        for (int row = 0; row < numberOfRows; ++row)
        {
            Coord allFlexGrow = 0;
            const auto numColumns = lineInfo[row].numItems;
            const auto remainingLength = containerLineLength - lineInfo[row].totalLength;

            for (int column = 0; column < numColumns; ++column)
            {
                auto& item = getItem (column, row);

                if (isRowDirection)
                {
                    if (isAuto (item.item->margin.left))    ++allFlexGrow;
                    if (isAuto (item.item->margin.right))   ++allFlexGrow;
                }
                else
                {
                    if (isAuto (item.item->margin.top))     ++allFlexGrow;
                    if (isAuto (item.item->margin.bottom))  ++allFlexGrow;
                }
            }

            auto changeUnitWidth = remainingLength / allFlexGrow;

            if (changeUnitWidth > 0)
            {
                for (int column = 0; column < numColumns; ++column)
                {
                    auto& item = getItem (column, row);

                    if (isRowDirection)
                    {
                        if (isAuto (item.item->margin.left))    item.lockedMarginLeft  = changeUnitWidth;
                        if (isAuto (item.item->margin.right))   item.lockedMarginRight = changeUnitWidth;
                    }
                    else
                    {
                        if (isAuto (item.item->margin.top))     item.lockedMarginTop    = changeUnitWidth;
                        if (isAuto (item.item->margin.bottom))  item.lockedMarginBottom = changeUnitWidth;
                    }
                }
            }
        }
    }

    void calculateCrossSizesByLine() noexcept
    {
        for (int row = 0; row < numberOfRows; ++row)
        {
            Coord maxSize = 0;
            const auto numColumns = lineInfo[row].numItems;

            for (int column = 0; column < numColumns; ++column)
            {
                auto& item = getItem (column, row);

                maxSize = jmax (maxSize, isRowDirection ? item.lockedHeight + item.lockedMarginTop  + item.lockedMarginBottom
                                                        : item.lockedWidth  + item.lockedMarginLeft + item.lockedMarginRight);
            }

            lineInfo[row].crossSize = maxSize;
        }
    }

    void calculateCrossSizeOfAllItems() noexcept
    {
        for (int row = 0; row < numberOfRows; ++row)
        {
            const auto numColumns = lineInfo[row].numItems;

            for (int column = 0; column < numColumns; ++column)
            {
                auto& item = getItem (column, row);

                if (isAssigned (item.item->maxHeight) && item.lockedHeight > item.item->maxHeight)
                    item.lockedHeight = item.item->maxHeight;

                if (isAssigned (item.item->maxWidth) && item.lockedWidth > item.item->maxWidth)
                    item.lockedWidth = item.item->maxWidth;
            }
        }
    }

    void alignLinesPerAlignContent() noexcept
    {
        containerCrossLength = isRowDirection ? parentHeight : parentWidth;

        if (owner.alignContent == FlexBox::AlignContent::flexStart)
        {
            for (int row = 0; row < numberOfRows; ++row)
                for (int row2 = row; row2 < numberOfRows; ++row2)
                    lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
        }
        else if (owner.alignContent == FlexBox::AlignContent::flexEnd)
        {
            for (int row = 0; row < numberOfRows; ++row)
            {
                Coord crossHeights = 0;

                for (int row2 = row; row2 < numberOfRows; ++row2)
                    crossHeights += lineInfo[row2].crossSize;

                lineInfo[row].lineY = containerCrossLength - crossHeights;
            }
        }
        else
        {
            Coord totalHeight = 0;

            for (int row = 0; row < numberOfRows; ++row)
                totalHeight += lineInfo[row].crossSize;

            if (owner.alignContent == FlexBox::AlignContent::stretch)
            {
                const auto difference = jmax (Coord(), (containerCrossLength - totalHeight) / numberOfRows);

                for (int row = 0; row < numberOfRows; ++row)
                {
                    lineInfo[row].crossSize += difference;
                    lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
                }
            }
            else if (owner.alignContent == FlexBox::AlignContent::center)
            {
                const auto additionalength = (containerCrossLength - totalHeight) / 2;

                for (int row = 0; row < numberOfRows; ++row)
                    lineInfo[row].lineY = row == 0 ? additionalength : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
            }
            else if (owner.alignContent == FlexBox::AlignContent::spaceBetween)
            {
                const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight)
                                                                                / static_cast<Coord> (numberOfRows - 1));
                lineInfo[0].lineY = 0;

                for (int row = 1; row < numberOfRows; ++row)
                    lineInfo[row].lineY += additionalength + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
            }
            else if (owner.alignContent == FlexBox::AlignContent::spaceAround)
            {
                const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight)
                                                                                / static_cast<Coord> (2 + (2 * (numberOfRows - 1))));

                lineInfo[0].lineY = additionalength;

                for (int row = 1; row < numberOfRows; ++row)
                    lineInfo[row].lineY += (2 * additionalength) + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
            }
        }
    }

    void resolveAutoMarginsOnCrossAxis() noexcept
    {
        for (int row = 0; row < numberOfRows; ++row)
        {
            const auto numColumns = lineInfo[row].numItems;
            const auto crossSizeForLine = lineInfo[row].crossSize;

            for (int column = 0; column < numColumns; ++column)
            {
                auto& item = getItem (column, row);

                if (isRowDirection)
                {
                    if (isAuto (item.item->margin.top) && isAuto (item.item->margin.bottom))
                        item.lockedMarginTop = (crossSizeForLine - item.lockedHeight) / 2;
                    else if (isAuto (item.item->margin.top))
                        item.lockedMarginTop = crossSizeForLine - item.lockedHeight - item.item->margin.bottom;
                }
                else if (isAuto (item.item->margin.left) && isAuto (item.item->margin.right))
                {
                    item.lockedMarginLeft = jmax (Coord(), (crossSizeForLine - item.lockedWidth) / 2);
                }
                else if (isAuto (item.item->margin.top))
                {
                    item.lockedMarginLeft = jmax (Coord(), crossSizeForLine - item.lockedHeight - item.item->margin.bottom);
                }
            }
        }
    }

    void alignItemsInCrossAxisInLinesPerAlignItems() noexcept
    {
        for (int row = 0; row < numberOfRows; ++row)
        {
            const auto numColumns = lineInfo[row].numItems;
            const auto lineSize = lineInfo[row].crossSize;

            for (int column = 0; column < numColumns; ++column)
            {
                auto& item = getItem (column, row);

                if (item.item->alignSelf == FlexItem::AlignSelf::autoAlign)
                {
                    if (owner.alignItems == FlexBox::AlignItems::stretch)
                    {
                        item.lockedMarginTop = item.item->margin.top;

                        if (isRowDirection)
                            item.setHeightChecked (lineSize - item.item->margin.top - item.item->margin.bottom);
                    }
                    else if (owner.alignItems == FlexBox::AlignItems::flexStart)
                    {
                        item.lockedMarginTop = item.item->margin.top;
                    }
                    else if (owner.alignItems == FlexBox::AlignItems::flexEnd)
                    {
                        item.lockedMarginTop = lineSize - item.lockedHeight - item.item->margin.bottom;
                    }
                    else if (owner.alignItems == FlexBox::AlignItems::center)
                    {
                        item.lockedMarginTop = (lineSize - item.lockedHeight - item.item->margin.top - item.item->margin.bottom) / 2;
                    }
                }
            }
        }
    }

    void alignLinesPerAlignSelf() noexcept
    {
        for (int row = 0; row < numberOfRows; ++row)
        {
            const auto numColumns = lineInfo[row].numItems;
            const auto lineSize = lineInfo[row].crossSize;

            for (int column = 0; column < numColumns; ++column)
            {
                auto& item = getItem (column, row);

                if (! isAuto (item.item->margin.top))
                {
                    if (item.item->alignSelf == FlexItem::AlignSelf::flexStart)
                    {
                        if (isRowDirection)
                            item.lockedMarginTop = item.item->margin.top;
                        else
                            item.lockedMarginLeft = item.item->margin.left;
                    }
                    else if (item.item->alignSelf == FlexItem::AlignSelf::flexEnd)
                    {
                        if (isRowDirection)
                            item.lockedMarginTop = lineSize - item.lockedHeight - item.item->margin.bottom;
                        else
                            item.lockedMarginLeft = lineSize - item.lockedWidth - item.item->margin.right;
                    }
                    else if (item.item->alignSelf == FlexItem::AlignSelf::center)
                    {
                        if (isRowDirection)
                            item.lockedMarginTop = item.item->margin.top + (lineSize - item.lockedHeight - item.item->margin.top - item.item->margin.bottom) / 2;
                        else
                            item.lockedMarginLeft = item.item->margin.left + (lineSize - item.lockedWidth - item.item->margin.left - item.item->margin.right) / 2;
                    }
                    else if (item.item->alignSelf == FlexItem::AlignSelf::stretch)
                    {
                        item.lockedMarginTop  = item.item->margin.top;
                        item.lockedMarginLeft = item.item->margin.left;

                        if (isRowDirection)
                            item.setHeightChecked (isAssigned (item.item->height) ? getPreferredHeight (item)
                                                                                : lineSize - item.item->margin.top - item.item->margin.bottom);
                        else
                            item.setWidthChecked (isAssigned (item.item->width) ? getPreferredWidth (item)
                                                                                : lineSize - item.item->margin.left - item.item->margin.right);
                    }
                }
            }
        }
    }

    void alignItemsByJustifyContent() noexcept
    {
        Coord additionalMarginRight = 0, additionalMarginLeft = 0;

        recalculateTotalItemLengthPerLineArray();

        for (int row = 0; row < numberOfRows; ++row)
        {
            const auto numColumns = lineInfo[row].numItems;
            Coord x = 0;

            if (owner.justifyContent == FlexBox::JustifyContent::flexEnd)
            {
                x = containerLineLength - lineInfo[row].totalLength;
            }
            else if (owner.justifyContent == FlexBox::JustifyContent::center)
            {
                x = (containerLineLength - lineInfo[row].totalLength) / 2;
            }
            else if (owner.justifyContent == FlexBox::JustifyContent::spaceBetween)
            {
                additionalMarginRight
                    = jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, numColumns - 1));
            }
            else if (owner.justifyContent == FlexBox::JustifyContent::spaceAround)
            {
                additionalMarginLeft = additionalMarginRight
                    = jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, 2 * numColumns));
            }

            for (int column = 0; column < numColumns; ++column)
            {
                auto& item = getItem (column, row);

                if (isRowDirection)
                {
                    item.lockedMarginLeft  += additionalMarginLeft;
                    item.lockedMarginRight += additionalMarginRight;
                    item.item->currentBounds.setPosition ((float) (x + item.lockedMarginLeft), (float) item.lockedMarginTop);
                    x += item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
                }
                else
                {
                    item.lockedMarginTop    += additionalMarginLeft;
                    item.lockedMarginBottom += additionalMarginRight;
                    item.item->currentBounds.setPosition ((float) item.lockedMarginLeft, (float) (x + item.lockedMarginTop));
                    x += item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
                }
            }
        }
    }

    void layoutAllItems() noexcept
    {
        for (int row = 0; row < numberOfRows; ++row)
        {
            const auto lineY = lineInfo[row].lineY;
            const auto numColumns = lineInfo[row].numItems;

            for (int column = 0; column < numColumns; ++column)
            {
                auto& item = getItem (column, row);

                if (isRowDirection)
                    item.item->currentBounds.setY ((float) (lineY + item.lockedMarginTop));
                else
                    item.item->currentBounds.setX ((float) (lineY + item.lockedMarginLeft));

                item.item->currentBounds.setSize ((float) item.lockedWidth,
                                                  (float) item.lockedHeight);
            }
        }

        reverseLocations();
        reverseWrap();
    }

    static int compareElements (const ItemWithState& i1, const ItemWithState& i2) noexcept
    {
        return i1.item->order < i2.item->order ? -1 : (i2.item->order < i1.item->order ? 1 : 0);
    }

private:
    void resetRowItems (const int row) noexcept
    {
        const auto numColumns = lineInfo[row].numItems;

        for (int column = 0; column < numColumns; ++column)
            resetItem (getItem (column, row));
    }

    void resetUnlockedRowItems (const int row) noexcept
    {
        const auto numColumns = lineInfo[row].numItems;

        for (int column = 0; column < numColumns; ++column)
        {
            auto& item = getItem (column, row);

            if (! item.locked)
                resetItem (item);
        }
    }

    void resetItem (ItemWithState& item) noexcept
    {
        item.locked = false;
        item.lockedWidth  = getPreferredWidth (item);
        item.lockedHeight = getPreferredHeight (item);
    }

    bool layoutRowItems (const int row) noexcept
    {
        const auto numColumns = lineInfo[row].numItems;
        auto flexContainerLength = containerLineLength;
        Coord totalItemsLength = 0, totalFlexGrow = 0, totalFlexShrink = 0;

        for (int column = 0; column < numColumns; ++column)
        {
            const auto& item = getItem (column, row);

            if (item.locked)
            {
                flexContainerLength -= getItemLength (item);
            }
            else
            {
                totalItemsLength += getItemLength (item);
                totalFlexGrow   += item.item->flexGrow;
                totalFlexShrink += item.item->flexShrink;
            }
        }

        Coord changeUnit = 0;
        const auto difference = flexContainerLength - totalItemsLength;
        const bool positiveFlexibility = difference > 0;

        if (positiveFlexibility)
        {
            if (totalFlexGrow != 0.0)
                changeUnit = difference / totalFlexGrow;
        }
        else
        {
            if (totalFlexShrink != 0.0)
                changeUnit = difference / totalFlexShrink;
        }

        bool ok = true;

        for (int column = 0; column < numColumns; ++column)
        {
            auto& item = getItem (column, row);

            if (! item.locked)
                if (! addToItemLength (item, (positiveFlexibility ? item.item->flexGrow
                                                                  : item.item->flexShrink) * changeUnit, row))
                    ok = false;
        }

        return ok;
    }

    void recalculateTotalItemLengthPerLineArray() noexcept
    {
        for (int row = 0; row < numberOfRows; ++row)
        {
            lineInfo[row].totalLength = 0;
            const auto numColumns = lineInfo[row].numItems;

            for (int column = 0; column < numColumns; ++column)
            {
                const auto& item = getItem (column, row);

                lineInfo[row].totalLength += isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight
                                                            : item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
            }
        }
    }

    void reverseLocations() noexcept
    {
        if (owner.flexDirection == FlexBox::Direction::rowReverse)
        {
            for (auto& item : owner.items)
                item.currentBounds.setX ((float) (containerLineLength - item.currentBounds.getRight()));
        }
        else if (owner.flexDirection == FlexBox::Direction::columnReverse)
        {
            for (auto& item : owner.items)
                item.currentBounds.setY ((float) (containerLineLength - item.currentBounds.getBottom()));
        }
    }

    void reverseWrap() noexcept
    {
        if (owner.flexWrap == FlexBox::Wrap::wrapReverse)
        {
            if (isRowDirection)
            {
                for (auto& item : owner.items)
                    item.currentBounds.setY ((float) (containerCrossLength - item.currentBounds.getBottom()));
            }
            else
            {
                for (auto& item : owner.items)
                    item.currentBounds.setX ((float) (containerCrossLength - item.currentBounds.getRight()));
            }
        }
    }

    Coord getItemLength (const ItemWithState& item) const noexcept
    {
        return isRowDirection ? item.lockedWidth  + item.lockedMarginLeft + item.lockedMarginRight
                              : item.lockedHeight + item.lockedMarginTop  + item.lockedMarginBottom;
    }

    Coord getItemCrossSize (const ItemWithState& item) const noexcept
    {
        return isRowDirection ? item.lockedHeight + item.lockedMarginTop  + item.lockedMarginBottom
                              : item.lockedWidth  + item.lockedMarginLeft + item.lockedMarginRight;
    }

    bool addToItemLength (ItemWithState& item, const Coord length, int row) const noexcept
    {
        bool ok = false;

        if (isRowDirection)
        {
            const auto prefWidth = getPreferredWidth (item);

            if (isAssigned (item.item->maxWidth) && item.item->maxWidth < prefWidth + length)
            {
                item.lockedWidth = item.item->maxWidth;
                item.locked = true;
            }
            else if (isAssigned (prefWidth) && item.item->minWidth > prefWidth + length)
            {
                item.lockedWidth = item.item->minWidth;
                item.locked = true;
            }
            else
            {
                ok = true;
                item.lockedWidth = prefWidth + length;
            }

            lineInfo[row].totalLength += item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
        }
        else
        {
            const auto prefHeight = getPreferredHeight (item);

            if (isAssigned (item.item->maxHeight) && item.item->maxHeight < prefHeight + length)
            {
                item.lockedHeight = item.item->maxHeight;
                item.locked = true;
            }
            else if (isAssigned (prefHeight) && item.item->minHeight > prefHeight + length)
            {
                item.lockedHeight = item.item->minHeight;
                item.locked = true;
            }
            else
            {
                ok = true;
                item.lockedHeight = prefHeight + length;
            }

            lineInfo[row].totalLength += item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
        }

        return ok;
    }

    Coord getPreferredWidth (const ItemWithState& itemWithState) const noexcept
    {
        const auto& item = *itemWithState.item;
        auto preferredWidth = (item.flexBasis > 0 && isRowDirection)
                                 ? item.flexBasis
                                 : (isAssigned (item.width) ? item.width : item.minWidth);

        if (isAssigned (item.minWidth) && preferredWidth < item.minWidth)  return item.minWidth;
        if (isAssigned (item.maxWidth) && preferredWidth > item.maxWidth)  return item.maxWidth;

        return preferredWidth;
    }

    Coord getPreferredHeight (const ItemWithState& itemWithState) const noexcept
    {
        const auto& item = *itemWithState.item;
        auto preferredHeight = (item.flexBasis > 0 && ! isRowDirection)
                                 ? item.flexBasis
                                 : (isAssigned (item.height) ? item.height : item.minHeight);

        if (isAssigned (item.minHeight) && preferredHeight < item.minHeight)  return item.minHeight;
        if (isAssigned (item.maxHeight) && preferredHeight > item.maxHeight)  return item.maxHeight;

        return preferredHeight;
    }
};

//==============================================================================
FlexBox::FlexBox() noexcept {}
FlexBox::~FlexBox() noexcept {}

FlexBox::FlexBox (JustifyContent jc) noexcept  : justifyContent (jc) {}

FlexBox::FlexBox (Direction d, Wrap w, AlignContent ac, AlignItems ai, JustifyContent jc) noexcept
    : flexDirection (d), flexWrap (w), alignContent (ac), alignItems (ai), justifyContent (jc)
{
}

void FlexBox::performLayout (Rectangle<float> targetArea)
{
    if (! items.isEmpty())
    {
        FlexBoxLayoutCalculation layout (*this, targetArea.getWidth(), targetArea.getHeight());

        layout.createStates();
        layout.initialiseItems();
        layout.resolveFlexibleLengths();
        layout.resolveAutoMarginsOnMainAxis();
        layout.calculateCrossSizesByLine();
        layout.calculateCrossSizeOfAllItems();
        layout.alignLinesPerAlignContent();
        layout.resolveAutoMarginsOnCrossAxis();
        layout.alignItemsInCrossAxisInLinesPerAlignItems();
        layout.alignLinesPerAlignSelf();
        layout.alignItemsByJustifyContent();
        layout.layoutAllItems();

        for (auto& item : items)
        {
            item.currentBounds += targetArea.getPosition();

            if (auto* comp = item.associatedComponent)
                comp->setBounds (Rectangle<int>::leftTopRightBottom ((int) item.currentBounds.getX(),
                                                                     (int) item.currentBounds.getY(),
                                                                     (int) item.currentBounds.getRight(),
                                                                     (int) item.currentBounds.getBottom()));

            if (auto* box = item.associatedFlexBox)
                box->performLayout (item.currentBounds);
        }
    }
}

void FlexBox::performLayout (Rectangle<int> targetArea)
{
    performLayout (targetArea.toFloat());
}

//==============================================================================
FlexItem::FlexItem() noexcept {}
FlexItem::FlexItem (float w, float h) noexcept                  : currentBounds (w, h), minWidth (w), minHeight (h) {}
FlexItem::FlexItem (float w, float h, Component& c) noexcept    : FlexItem (w, h)  { associatedComponent = &c; }
FlexItem::FlexItem (float w, float h, FlexBox& fb) noexcept     : FlexItem (w, h)  { associatedFlexBox = &fb; }
FlexItem::FlexItem (Component& c) noexcept                      : associatedComponent (&c) {}
FlexItem::FlexItem (FlexBox& fb) noexcept                       : associatedFlexBox (&fb) {}

FlexItem::Margin::Margin() noexcept                                     : left(), right(), top(), bottom() {}
FlexItem::Margin::Margin (float v) noexcept                             : left (v), right (v), top (v), bottom (v) {}
FlexItem::Margin::Margin (float t, float r, float b, float l) noexcept  : left (l), right (r), top (t), bottom (b) {}

//==============================================================================
FlexItem FlexItem::withFlex (float newFlexGrow) const noexcept
{
    auto fi = *this;
    fi.flexGrow = newFlexGrow;
    return fi;
}

FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink) const noexcept
{
    auto fi = withFlex (newFlexGrow);
    fi.flexShrink = newFlexShrink;
    return fi;
}

FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink, float newFlexBasis) const noexcept
{
    auto fi = withFlex (newFlexGrow, newFlexShrink);
    fi.flexBasis = newFlexBasis;
    return fi;
}

FlexItem FlexItem::withWidth (float newWidth) const noexcept         { auto fi = *this; fi.width = newWidth; return fi; }
FlexItem FlexItem::withMinWidth (float newMinWidth) const noexcept   { auto fi = *this; fi.minWidth = newMinWidth; return fi; }
FlexItem FlexItem::withMaxWidth (float newMaxWidth) const noexcept   { auto fi = *this; fi.maxWidth = newMaxWidth; return fi; }

FlexItem FlexItem::withMinHeight (float newMinHeight) const noexcept { auto fi = *this; fi.minHeight = newMinHeight; return fi; }
FlexItem FlexItem::withMaxHeight (float newMaxHeight) const noexcept { auto fi = *this; fi.maxHeight = newMaxHeight; return fi; }
FlexItem FlexItem::withHeight (float newHeight) const noexcept       { auto fi = *this; fi.height = newHeight; return fi; }

FlexItem FlexItem::withMargin (Margin m) const noexcept              { auto fi = *this; fi.margin = m; return fi; }
FlexItem FlexItem::withOrder (int newOrder) const noexcept           { auto fi = *this; fi.order = newOrder; return fi; }
FlexItem FlexItem::withAlignSelf (AlignSelf a) const noexcept        { auto fi = *this; fi.alignSelf = a; return fi; }

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

namespace ComponentBuilderHelpers
{
    static String getStateId (const ValueTree& state)
    {
        return state [ComponentBuilder::idProperty].toString();
    }

    static Component* removeComponentWithID (OwnedArray<Component>& components, const String& compId)
    {
        jassert (compId.isNotEmpty());

        for (int i = components.size(); --i >= 0;)
        {
            Component* const c = components.getUnchecked (i);

            if (c->getComponentID() == compId)
                return components.removeAndReturn (i);
        }

        return nullptr;
    }

    static Component* findComponentWithID (Component& c, const String& compId)
    {
        jassert (compId.isNotEmpty());
        if (c.getComponentID() == compId)
            return &c;

        for (auto* child : c.getChildren())
            if (auto* found = findComponentWithID (*child, compId))
                return found;

        return nullptr;
    }

    static Component* createNewComponent (ComponentBuilder::TypeHandler& type,
                                          const ValueTree& state, Component* parent)
    {
        Component* const c = type.addNewComponentFromState (state, parent);
        jassert (c != nullptr && c->getParentComponent() == parent);
        c->setComponentID (getStateId (state));
        return c;
    }

    static void updateComponent (ComponentBuilder& builder, const ValueTree& state)
    {
        if (Component* topLevelComp = builder.getManagedComponent())
        {
            ComponentBuilder::TypeHandler* const type = builder.getHandlerForState (state);
            const String uid (getStateId (state));

            if (type == nullptr || uid.isEmpty())
            {
                // ..handle the case where a child of the actual state node has changed.
                if (state.getParent().isValid())
                    updateComponent (builder, state.getParent());
            }
            else
            {
                if (Component* const changedComp = findComponentWithID (*topLevelComp, uid))
                    type->updateComponentFromState (changedComp, state);
            }
        }
    }
}

//==============================================================================
const Identifier ComponentBuilder::idProperty ("id");

ComponentBuilder::ComponentBuilder()
    : imageProvider (nullptr)
{
}

ComponentBuilder::ComponentBuilder (const ValueTree& state_)
    : state (state_), imageProvider (nullptr)
{
    state.addListener (this);
}

ComponentBuilder::~ComponentBuilder()
{
    state.removeListener (this);

   #if JUCE_DEBUG
    // Don't delete the managed component!! The builder owns that component, and will delete
    // it automatically when it gets deleted.
    jassert (componentRef.get() == static_cast<Component*> (component));
   #endif
}

Component* ComponentBuilder::getManagedComponent()
{
    if (component == nullptr)
    {
        component = createComponent();

       #if JUCE_DEBUG
        componentRef = component;
       #endif
    }

    return component;
}

Component* ComponentBuilder::createComponent()
{
    jassert (types.size() > 0);  // You need to register all the necessary types before you can load a component!

    if (TypeHandler* const type = getHandlerForState (state))
        return ComponentBuilderHelpers::createNewComponent (*type, state, nullptr);

    jassertfalse; // trying to create a component from an unknown type of ValueTree
    return nullptr;
}

void ComponentBuilder::registerTypeHandler (ComponentBuilder::TypeHandler* const type)
{
    jassert (type != nullptr);

    // Don't try to move your types around! Once a type has been added to a builder, the
    // builder owns it, and you should leave it alone!
    jassert (type->builder == nullptr);

    types.add (type);
    type->builder = this;
}

ComponentBuilder::TypeHandler* ComponentBuilder::getHandlerForState (const ValueTree& s) const
{
    const Identifier targetType (s.getType());

    for (int i = 0; i < types.size(); ++i)
    {
        TypeHandler* const t = types.getUnchecked(i);

        if (t->type == targetType)
            return t;
    }

    return nullptr;
}

int ComponentBuilder::getNumHandlers() const noexcept
{
    return types.size();
}

ComponentBuilder::TypeHandler* ComponentBuilder::getHandler (const int index) const noexcept
{
    return types [index];
}

void ComponentBuilder::registerStandardComponentTypes()
{
    Drawable::registerDrawableTypeHandlers (*this);
}

void ComponentBuilder::setImageProvider (ImageProvider* newImageProvider) noexcept
{
    imageProvider = newImageProvider;
}

ComponentBuilder::ImageProvider* ComponentBuilder::getImageProvider() const noexcept
{
    return imageProvider;
}

void ComponentBuilder::valueTreePropertyChanged (ValueTree& tree, const Identifier&)
{
    ComponentBuilderHelpers::updateComponent (*this, tree);
}

void ComponentBuilder::valueTreeChildAdded (ValueTree& tree, ValueTree&)
{
    ComponentBuilderHelpers::updateComponent (*this, tree);
}

void ComponentBuilder::valueTreeChildRemoved (ValueTree& tree, ValueTree&, int)
{
    ComponentBuilderHelpers::updateComponent (*this, tree);
}

void ComponentBuilder::valueTreeChildOrderChanged (ValueTree& tree, int, int)
{
    ComponentBuilderHelpers::updateComponent (*this, tree);
}

void ComponentBuilder::valueTreeParentChanged (ValueTree& tree)
{
    ComponentBuilderHelpers::updateComponent (*this, tree);
}

//==============================================================================
ComponentBuilder::TypeHandler::TypeHandler (const Identifier& valueTreeType)
   : type (valueTreeType), builder (nullptr)
{
}

ComponentBuilder::TypeHandler::~TypeHandler()
{
}

ComponentBuilder* ComponentBuilder::TypeHandler::getBuilder() const noexcept
{
    // A type handler needs to be registered with a ComponentBuilder before using it!
    jassert (builder != nullptr);
    return builder;
}

void ComponentBuilder::updateChildComponents (Component& parent, const ValueTree& children)
{
    using namespace ComponentBuilderHelpers;

    auto numExistingChildComps = parent.getNumChildComponents();

    Array<Component*> componentsInOrder;
    componentsInOrder.ensureStorageAllocated (numExistingChildComps);

    {
        OwnedArray<Component> existingComponents;
        existingComponents.ensureStorageAllocated (numExistingChildComps);

        for (int i = 0; i < numExistingChildComps; ++i)
            existingComponents.add (parent.getChildComponent (i));

        auto newNumChildren = children.getNumChildren();

        for (int i = 0; i < newNumChildren; ++i)
        {
            auto childState = children.getChild (i);
            auto* c = removeComponentWithID (existingComponents, getStateId (childState));

            if (c == nullptr)
            {
                if (auto* type = getHandlerForState (childState))
                    c = ComponentBuilderHelpers::createNewComponent (*type, childState, &parent);
                else
                    jassertfalse;
            }

            if (c != nullptr)
                componentsInOrder.add (c);
        }

        // (remaining unused items in existingComponents get deleted here as it goes out of scope)
    }

    // Make sure the z-order is correct..
    if (componentsInOrder.size() > 0)
    {
        componentsInOrder.getLast()->toFront (false);

        for (int i = componentsInOrder.size() - 1; --i >= 0;)
            componentsInOrder.getUnchecked(i)->toBehind (componentsInOrder.getUnchecked (i + 1));
    }
}

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    Models a 1-dimensional position that can be dragged around by the user, and which
    will then continue moving with a customisable physics behaviour when released.

    This is useful for things like scrollable views or objects that can be dragged and
    thrown around with the mouse/touch, and by writing your own behaviour class, you can
    customise the trajectory that it follows when released.

    The class uses its own Timer to continuously change its value when a drag ends, and
    Listener objects can be registered to receive callbacks whenever the value changes.

    The value is stored as a double, and can be used to represent whatever units you need.

    The template parameter Behaviour must be a class that implements various methods to
    return the physics of the value's movement - you can use the classes provided for this
    in the AnimatedPositionBehaviours namespace, or write your own custom behaviour.

    @see AnimatedPositionBehaviours::ContinuousWithMomentum,
         AnimatedPositionBehaviours::SnapToPageBoundaries
*/
template <typename Behaviour>
class AnimatedPosition  : private Timer
{
public:
    AnimatedPosition()
        : position(), grabbedPos(), releaseVelocity(),
          range (-std::numeric_limits<double>::max(),
                  std::numeric_limits<double>::max())
    {
    }

    /** Sets a range within which the value will be constrained. */
    void setLimits (Range<double> newRange) noexcept
    {
        range = newRange;
    }

    //==============================================================================
    /** Called to indicate that the object is now being controlled by a
        mouse-drag or similar operation.

        After calling this method, you should make calls to the drag() method
        each time the mouse drags the position around, and always be sure to
        finish with a call to endDrag() when the mouse is released, which allows
        the position to continue moving freely according to the specified behaviour.
    */
    void beginDrag()
    {
        grabbedPos = position;
        releaseVelocity = 0;
        stopTimer();
    }

    /** Called during a mouse-drag operation, to indicate that the mouse has moved.
        The delta is the difference between the position when beginDrag() was called
        and the new position that's required.
    */
    void drag (double deltaFromStartOfDrag)
    {
        moveTo (grabbedPos + deltaFromStartOfDrag);
    }

    /** Called after beginDrag() and drag() to indicate that the drag operation has
        now finished.
    */
    void endDrag()
    {
        startTimerHz (60);
    }

    /** Called outside of a drag operation to cause a nudge in the specified direction.
        This is intended for use by e.g. mouse-wheel events.
    */
    void nudge (double deltaFromCurrentPosition)
    {
        startTimerHz (10);
        moveTo (position + deltaFromCurrentPosition);
    }

    //==============================================================================
    /** Returns the current position. */
    double getPosition() const noexcept
    {
        return position;
    }

    /** Explicitly sets the position and stops any further movement.
        This will cause a synchronous call to any listeners if the position actually
        changes.
    */
    void setPosition (double newPosition)
    {
        stopTimer();
        setPositionAndSendChange (newPosition);
    }

    //==============================================================================
    /** Implement this class if you need to receive callbacks when the value of
        an AnimatedPosition changes.
        @see AnimatedPosition::addListener, AnimatedPosition::removeListener
    */
    class Listener
    {
    public:
        virtual ~Listener() {}

        /** Called synchronously when an AnimatedPosition changes. */
        virtual void positionChanged (AnimatedPosition&, double newPosition) = 0;
    };

    /** Adds a listener to be called when the value changes. */
    void addListener (Listener* listener)       { listeners.add (listener); }

    /** Removes a previously-registered listener. */
    void removeListener (Listener* listener)    { listeners.remove (listener); }

    //==============================================================================
    /** The behaviour object.
        This is public to let you tweak any parameters that it provides.
    */
    Behaviour behaviour;

private:
    //==============================================================================
    double position, grabbedPos, releaseVelocity;
    Range<double> range;
    Time lastUpdate, lastDrag;
    ListenerList<Listener> listeners;

    static double getSpeed (const Time last, double lastPos,
                            const Time now, double newPos)
    {
        const double elapsedSecs = jmax (0.005, (now - last).inSeconds());
        const double v = (newPos - lastPos) / elapsedSecs;
        return std::abs (v) > 0.2 ? v : 0.0;
    }

    void moveTo (double newPos)
    {
        const Time now (Time::getCurrentTime());
        releaseVelocity = getSpeed (lastDrag, position, now, newPos);
        behaviour.releasedWithVelocity (newPos, releaseVelocity);
        lastDrag = now;

        setPositionAndSendChange (newPos);
    }

    void setPositionAndSendChange (double newPosition)
    {
        newPosition = range.clipValue (newPosition);

        if (position != newPosition)
        {
            position = newPosition;
            listeners.call (&Listener::positionChanged, *this, newPosition);
        }
    }

    void timerCallback() override
    {
        const Time now = Time::getCurrentTime();

        const double elapsed = jlimit (0.001, 0.020, (now - lastUpdate).inSeconds());
        lastUpdate = now;

        const double newPos = behaviour.getNextPosition (position, elapsed);

        if (behaviour.isStopped (newPos))
            stopTimer();
        else
            startTimerHz (60);

        setPositionAndSendChange (newPos);
    }

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimatedPosition)
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    A component that draws an outline around itself and has an optional title at
    the top, for drawing an outline around a group of controls.

*/
class JUCE_API  GroupComponent    : public Component
{
public:
    //==============================================================================
    /** Creates a GroupComponent.

        @param componentName    the name to give the component
        @param labelText        the text to show at the top of the outline
    */
    GroupComponent (const String& componentName = String(),
                    const String& labelText = String());

    /** Destructor. */
    ~GroupComponent();

    //==============================================================================
    /** Changes the text that's shown at the top of the component. */
    void setText (const String& newText);

    /** Returns the currently displayed text label. */
    String getText() const;

    /** Sets the positioning of the text label.

        (The default is Justification::left)

        @see getTextLabelPosition
    */
    void setTextLabelPosition (Justification justification);

    /** Returns the current text label position.

        @see setTextLabelPosition
    */
    Justification getTextLabelPosition() const noexcept           { return justification; }

    //==============================================================================
    /** A set of colour IDs to use to change the colour of various aspects of the component.

        These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
        methods.

        @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
    */
    enum ColourIds
    {
        outlineColourId     = 0x1005400,    /**< The colour to use for drawing the line around the edge. */
        textColourId        = 0x1005410     /**< The colour to use to draw the text label. */
    };

    //==============================================================================
    /** This abstract base class is implemented by LookAndFeel classes. */
    struct JUCE_API  LookAndFeelMethods
    {
        virtual ~LookAndFeelMethods() {}

        virtual void drawGroupComponentOutline (Graphics&, int w, int h, const String& text,
                                                const Justification&, GroupComponent&) = 0;
    };

    //==============================================================================
    /** @internal */
    void paint (Graphics&) override;
    /** @internal */
    void enablementChanged() override;
    /** @internal */
    void colourChanged() override;

private:
    String text;
    Justification justification;

    JUCE_DECLARE_NON_COPYABLE (GroupComponent)
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/** An object that watches for any movement of a component or any of its parent components.

    This makes it easy to check when a component is moved relative to its top-level
    peer window. The normal Component::moved() method is only called when a component
    moves relative to its immediate parent, and sometimes you want to know if any of
    components higher up the tree have moved (which of course will affect the overall
    position of all their sub-components).

    It also includes a callback that lets you know when the top-level peer is changed.

    This class is used by specialised components like WebBrowserComponent
    because they need to keep their custom windows in the right place and respond to
    changes in the peer.
*/
class JUCE_API  ComponentMovementWatcher    : public ComponentListener
{
public:
    //==============================================================================
    /** Creates a ComponentMovementWatcher to watch a given target component. */
    ComponentMovementWatcher (Component* component);

    /** Destructor. */
    ~ComponentMovementWatcher();

    //==============================================================================
    /** This callback happens when the component that is being watched is moved
        relative to its top-level peer window, or when it is resized. */
    virtual void componentMovedOrResized (bool wasMoved, bool wasResized) = 0;

    /** This callback happens when the component's top-level peer is changed. */
    virtual void componentPeerChanged() = 0;

    /** This callback happens when the component's visibility state changes, possibly due to
        one of its parents being made visible or invisible.
    */
    virtual void componentVisibilityChanged() = 0;

    /** Returns the component that's being watched. */
    Component* getComponent() const noexcept         { return component; }

    //==============================================================================
    /** @internal */
    void componentParentHierarchyChanged (Component&) override;
    /** @internal */
    void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
    /** @internal */
    void componentBeingDeleted (Component&) override;
    /** @internal */
    void componentVisibilityChanged (Component&) override;

private:
    //==============================================================================
    WeakReference<Component> component;
    uint32 lastPeerID = 0;
    Array<Component*> registeredParentComps;
    bool reentrant = false, wasShowing;
    Rectangle<int> lastBounds;

    void unregister();
    void registerWithParentComps();

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentMovementWatcher)
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

struct Grid::SizeCalculation
{
    static float getTotalAbsoluteSize (const juce::Array<Grid::TrackInfo>& tracks, Px gapSize) noexcept
    {
        float totalCellSize = 0.0f;

        for (const auto& trackInfo : tracks)
            if (! trackInfo.isFraction || trackInfo.hasKeyword)
                totalCellSize += trackInfo.size;

        float totalGap = tracks.size() > 1 ? static_cast<float> ((tracks.size() - 1) * gapSize.pixels)
                                           : 0.0f;

        return totalCellSize + totalGap;
    }

    static float getRelativeUnitSize (float size, float totalAbsolute, const juce::Array<Grid::TrackInfo>& tracks) noexcept
    {
        const float totalRelative = juce::jlimit (0.0f, size, size - totalAbsolute);
        float factorsSum = 0.0f;

        for (const auto& trackInfo : tracks)
            if (trackInfo.isFraction)
                factorsSum += trackInfo.size;

        jassert (factorsSum != 0.0f);
        return totalRelative / factorsSum;
    }

    //==============================================================================
    static float getTotalAbsoluteHeight (const juce::Array<Grid::TrackInfo>& rowTracks, Px rowGap)
    {
        return getTotalAbsoluteSize (rowTracks, rowGap);
    }

    static float getTotalAbsoluteWidth (const juce::Array<Grid::TrackInfo>& columnTracks, Px columnGap)
    {
        return getTotalAbsoluteSize (columnTracks, columnGap);
    }

    static float getRelativeWidthUnit (float gridWidth, Px columnGap, const juce::Array<Grid::TrackInfo>& columnTracks)
    {
        return getRelativeUnitSize (gridWidth, getTotalAbsoluteWidth (columnTracks, columnGap), columnTracks);
    }

    static float getRelativeHeightUnit (float gridHeight, Px rowGap, const juce::Array<Grid::TrackInfo>& rowTracks)
    {
        return getRelativeUnitSize (gridHeight, getTotalAbsoluteHeight (rowTracks, rowGap), rowTracks);
    }

    //==============================================================================
    static bool hasAnyFractions (const juce::Array<Grid::TrackInfo>& tracks)
    {
        for (auto& t : tracks)
            if (t.isFraction)
                return true;

        return false;
    }

    void computeSizes (float gridWidth, float gridHeight,
                       Px columnGapToUse, Px rowGapToUse,
                       const juce::Array<Grid::TrackInfo>& columnTracks,
                       const juce::Array<Grid::TrackInfo>& rowTracks)
    {
        if (hasAnyFractions (columnTracks))
            relativeWidthUnit = getRelativeWidthUnit (gridWidth, columnGapToUse, columnTracks);
        else
            remainingWidth = gridWidth - getTotalAbsoluteSize (columnTracks, columnGapToUse);

        if (hasAnyFractions (rowTracks))
            relativeHeightUnit = getRelativeHeightUnit (gridHeight, rowGapToUse, rowTracks);
        else
            remainingHeight = gridHeight - getTotalAbsoluteSize (rowTracks, rowGapToUse);
    }

    float relativeWidthUnit  = 0.0f;
    float relativeHeightUnit = 0.0f;
    float remainingWidth     = 0.0f;
    float remainingHeight    = 0.0f;
};

//==============================================================================
struct Grid::PlacementHelpers
{
    enum { invalid = -999999 };
    static constexpr auto emptyAreaCharacter = ".";

    //==============================================================================
    struct LineRange { int start, end; };
    struct LineArea  { LineRange column, row; };
    struct LineInfo  { juce::StringArray lineNames; };

    struct NamedArea
    {
        juce::String name;
        LineArea lines;
    };

    //==============================================================================
    static juce::Array<LineInfo> getArrayOfLinesFromTracks (const juce::Array<Grid::TrackInfo>& tracks)
    {
        // fill line info array
        juce::Array<LineInfo> lines;

        for (int i = 1; i <= tracks.size(); ++i)
        {
            const auto& currentTrack = tracks.getReference (i - 1);

            if (i == 1) // start line
            {
                LineInfo li;
                li.lineNames.add (currentTrack.startLineName);
                lines.add (li);
            }

            if (i > 1 && i <= tracks.size()) // two lines in between tracks
            {
                const auto& prevTrack = tracks.getReference (i - 2);

                LineInfo li;
                li.lineNames.add (prevTrack.endLineName);
                li.lineNames.add (currentTrack.startLineName);

                lines.add (li);
            }

            if (i == tracks.size()) // end line
            {
                LineInfo li;
                li.lineNames.add (currentTrack.endLineName);
                lines.add (li);
            }
        }

        jassert (lines.size() == tracks.size() + 1);

        return lines;
    }

    //==============================================================================
    static int deduceAbsoluteLineNumberFromLineName (GridItem::Property prop,
                                                     const juce::Array<Grid::TrackInfo>& tracks)
    {
        jassert (prop.hasAbsolute());

        const auto lines = getArrayOfLinesFromTracks (tracks);
        int count = 0;

        for (int i = 0; i < lines.size(); i++)
        {
            for (const auto& name : lines.getReference (i).lineNames)
            {
                if (prop.name == name)
                {
                    ++count;
                    break;
                }
            }

            if (count == prop.number)
                return i + 1;
        }

        jassertfalse;
        return count;
    }

    static int deduceAbsoluteLineNumber (GridItem::Property prop,
                                         const juce::Array<Grid::TrackInfo>& tracks)
    {
        jassert (prop.hasAbsolute());

        if (prop.hasName())
            return deduceAbsoluteLineNumberFromLineName (prop, tracks);

        return prop.number > 0 ? prop.number : tracks.size() + 2 + prop.number;
    }

    static int deduceAbsoluteLineNumberFromNamedSpan (int startLineNumber,
                                                      GridItem::Property propertyWithSpan,
                                                      const juce::Array<Grid::TrackInfo>& tracks)
    {
        jassert (propertyWithSpan.hasSpan());

        const auto lines = getArrayOfLinesFromTracks (tracks);
        int count = 0;

        for (int i = startLineNumber; i < lines.size(); i++)
        {
            for (const auto& name : lines.getReference (i).lineNames)
            {
                if (propertyWithSpan.name == name)
                {
                    ++count;
                    break;
                }
            }

            if (count == propertyWithSpan.number)
                return i + 1;
        }

        jassertfalse;
        return count;
    }

    static int deduceAbsoluteLineNumberBasedOnSpan (int startLineNumber,
                                                    GridItem::Property propertyWithSpan,
                                                    const juce::Array<Grid::TrackInfo>& tracks)
    {
        jassert (propertyWithSpan.hasSpan());

        if (propertyWithSpan.hasName())
            return deduceAbsoluteLineNumberFromNamedSpan (startLineNumber, propertyWithSpan, tracks);

        return startLineNumber + propertyWithSpan.number;
    }

    //==============================================================================
    static LineRange deduceLineRange (GridItem::StartAndEndProperty prop, const juce::Array<Grid::TrackInfo>& tracks)
    {
        LineRange s;

        jassert (! (prop.start.hasAuto() && prop.end.hasAuto()));

        if (prop.start.hasAbsolute() && prop.end.hasAuto())
        {
            prop.end = GridItem::Span (1);
        }
        else if (prop.start.hasAuto() && prop.end.hasAbsolute())
        {
            prop.start = GridItem::Span (1);
        }

        if (prop.start.hasAbsolute() && prop.end.hasAbsolute())
        {
            s.start = deduceAbsoluteLineNumber (prop.start, tracks);
            s.end   = deduceAbsoluteLineNumber (prop.end, tracks);
        }
        else if (prop.start.hasAbsolute() && prop.end.hasSpan())
        {
            s.start = deduceAbsoluteLineNumber (prop.start, tracks);
            s.end   = deduceAbsoluteLineNumberBasedOnSpan (s.start, prop.end, tracks);
        }
        else if (prop.start.hasSpan() && prop.end.hasAbsolute())
        {
            s.start = deduceAbsoluteLineNumber (prop.end, tracks);
            s.end   = deduceAbsoluteLineNumberBasedOnSpan (s.start, prop.start, tracks);
        }
        else
        {
            // Can't have an item with spans on both start and end.
            jassertfalse;
            s.start = s.end = {};
        }

        // swap if start overtakes end
        if (s.start > s.end)
            std::swap (s.start, s.end);
        else if (s.start == s.end)
            s.end = s.start + 1;

        return s;
    }

    static LineArea deduceLineArea (const GridItem& item,
                                    const Grid& grid,
                                    const std::map<juce::String, LineArea>& namedAreas)
    {
        if (item.area.isNotEmpty() && ! grid.templateAreas.isEmpty())
            return namedAreas.at (item.area);

        return { deduceLineRange (item.column, grid.templateColumns),
                 deduceLineRange (item.row,    grid.templateRows) };
    }

    //==============================================================================
    static juce::Array<juce::StringArray> parseAreasProperty (const juce::StringArray& areasStrings)
    {
        juce::Array<juce::StringArray> strings;

        for (const auto& areaString : areasStrings)
            strings.add (juce::StringArray::fromTokens (areaString, false));

        if (strings.size() > 0)
            for (auto s : strings)
                jassert (s.size() == strings[0].size()); // all rows must have the same number of columns

        return strings;
    }

    static NamedArea findArea (juce::Array<juce::StringArray>& stringsArrays)
    {
        NamedArea area;

        for (auto& stringArray : stringsArrays)
        {
            for (auto& string : stringArray)
            {
                // find anchor
                if (area.name.isEmpty())
                {
                    if (string != emptyAreaCharacter)
                    {
                        area.name = string;
                        area.lines.row.start = stringsArrays.indexOf (stringArray) + 1; // non-zero indexed;
                        area.lines.column.start = stringArray.indexOf (string) + 1; // non-zero indexed;

                        area.lines.row.end = stringsArrays.indexOf (stringArray) + 2;
                        area.lines.column.end = stringArray.indexOf (string) + 2;

                        // mark as visited
                        string = emptyAreaCharacter;
                    }
                }
                else
                {
                    if (string == emptyAreaCharacter)
                    {
                        break;
                    }
                    else if (string == area.name)
                    {
                        area.lines.row.end = stringsArrays.indexOf (stringArray) + 2;
                        area.lines.column.end = stringArray.indexOf (string) + 2;

                        // mark as visited
                        string = emptyAreaCharacter;
                    }
                }
            }
        }

        return area;
    }

    //==============================================================================
    static std::map<juce::String, LineArea> deduceNamedAreas (const juce::StringArray& areasStrings)
    {
        auto stringsArrays = parseAreasProperty (areasStrings);

        std::map<juce::String, LineArea> areas;

        for (auto area = findArea (stringsArrays); area.name.isNotEmpty(); area = findArea (stringsArrays))
        {
            if (areas.count (area.name) == 0)
                areas[area.name] = area.lines;
            else
                // Make sure your template-areas property only has one area with the same name and is well-formed
                jassertfalse;
        }

        return areas;
    }

    //==============================================================================
    static float getCoord (int trackNumber, float relativeUnit, Px gap, const juce::Array<Grid::TrackInfo>& tracks)
    {
        float c = 0;

        for (const auto* it = tracks.begin(); it != tracks.begin() + trackNumber - 1; ++it)
            c += (it->isFraction ? it->size * relativeUnit : it->size) + static_cast<float> (gap.pixels);

        return c;
    }

    static juce::Rectangle<float> getCellBounds (int columnNumber, int rowNumber,
                                                 const juce::Array<Grid::TrackInfo>& columnTracks,
                                                 const juce::Array<Grid::TrackInfo>& rowTracks,
                                                 Grid::SizeCalculation calculation,
                                                 Px columnGap, Px rowGap)
    {
        jassert (columnNumber >= 1 && columnNumber <= columnTracks.size());
        jassert (rowNumber >= 1 && rowNumber <= rowTracks.size());

        const auto x = getCoord (columnNumber, calculation.relativeWidthUnit, columnGap, columnTracks);
        const auto y = getCoord (rowNumber, calculation.relativeHeightUnit, rowGap, rowTracks);

        const auto& columnTrackInfo = columnTracks.getReference (columnNumber - 1);
        const float width = columnTrackInfo.isFraction ? columnTrackInfo.size * calculation.relativeWidthUnit
                                                       : columnTrackInfo.size;

        const auto& rowTrackInfo = rowTracks.getReference (rowNumber - 1);
        const float height = rowTrackInfo.isFraction ? rowTrackInfo.size * calculation.relativeHeightUnit
                                                     : rowTrackInfo.size;

        return { x, y, width, height };
    }

    static juce::Rectangle<float> alignCell (juce::Rectangle<float> area,
                                             int columnNumber, int rowNumber,
                                             int numberOfColumns, int numberOfRows,
                                             Grid::SizeCalculation calculation,
                                             Grid::AlignContent alignContent,
                                             Grid::JustifyContent justifyContent)
    {
        if (alignContent == Grid::AlignContent::end)
            area.setY (area.getY() + calculation.remainingHeight);

        if (justifyContent == Grid::JustifyContent::end)
            area.setX (area.getX() + calculation.remainingWidth);

        if (alignContent == Grid::AlignContent::center)
            area.setY (area.getY() + calculation.remainingHeight / 2);

        if (justifyContent == Grid::JustifyContent::center)
            area.setX (area.getX() + calculation.remainingWidth / 2);

        if (alignContent == Grid::AlignContent::spaceBetween)
        {
            const auto shift = ((rowNumber - 1) * (calculation.remainingHeight / float(numberOfRows - 1)));
            area.setY (area.getY() + shift);
        }

        if (justifyContent == Grid::JustifyContent::spaceBetween)
        {
            const auto shift = ((columnNumber - 1) * (calculation.remainingWidth / float(numberOfColumns - 1)));
            area.setX (area.getX() + shift);
        }

        if (alignContent == Grid::AlignContent::spaceEvenly)
        {
            const auto shift = (rowNumber * (calculation.remainingHeight / float(numberOfRows + 1)));
            area.setY (area.getY() + shift);
        }

        if (justifyContent == Grid::JustifyContent::spaceEvenly)
        {
            const auto shift = (columnNumber * (calculation.remainingWidth / float(numberOfColumns + 1)));
            area.setX (area.getX() + shift);
        }

        if (alignContent == Grid::AlignContent::spaceAround)
        {
            const auto inbetweenShift = calculation.remainingHeight / float(numberOfRows);
            const auto sidesShift = inbetweenShift / 2;
            auto shift = (rowNumber - 1) * inbetweenShift + sidesShift;

            area.setY (area.getY() + shift);
        }

        if (justifyContent == Grid::JustifyContent::spaceAround)
        {
            const auto inbetweenShift = calculation.remainingWidth / float(numberOfColumns);
            const auto sidesShift = inbetweenShift / 2;
            auto shift = (columnNumber - 1) * inbetweenShift + sidesShift;

            area.setX (area.getX() + shift);
        }

        return area;
    }

    static juce::Rectangle<float> getAreaBounds (int columnLineNumberStart, int columnLineNumberEnd,
                                                 int rowLineNumberStart, int rowLineNumberEnd,
                                                 const juce::Array<Grid::TrackInfo>& columnTracks,
                                                 const juce::Array<Grid::TrackInfo>& rowTracks,
                                                 Grid::SizeCalculation calculation,
                                                 Grid::AlignContent alignContent,
                                                 Grid::JustifyContent justifyContent,
                                                 Px columnGap, Px rowGap)
    {
        auto startCell = getCellBounds (columnLineNumberStart, rowLineNumberStart,
                                        columnTracks, rowTracks,
                                        calculation,
                                        columnGap, rowGap);

        auto endCell = getCellBounds (columnLineNumberEnd - 1, rowLineNumberEnd - 1,
                                      columnTracks, rowTracks,
                                      calculation,
                                      columnGap, rowGap);

        startCell = alignCell (startCell,
                               columnLineNumberStart, rowLineNumberStart,
                               columnTracks.size(), rowTracks.size(),
                               calculation,
                               alignContent,
                               justifyContent);

        endCell = alignCell (endCell,
                             columnLineNumberEnd - 1, rowLineNumberEnd - 1,
                             columnTracks.size(), rowTracks.size(),
                             calculation,
                             alignContent,
                             justifyContent);

        return startCell.getUnion (endCell);
    }
};

//==============================================================================
struct Grid::AutoPlacement
{
    using ItemPlacementArray = juce::Array<std::pair<GridItem*, Grid::PlacementHelpers::LineArea>>;

    //==============================================================================
    struct OccupancyPlane
    {
        struct Cell { int column, row; };

        OccupancyPlane (int highestColumnToUse, int highestRowToUse, bool isColoumnFirst)
            : highestCrossDimension (isColoumnFirst ? highestRowToUse : highestColumnToUse),
              columnFirst (isColoumnFirst)
        {}

        Grid::PlacementHelpers::LineArea setCell (Cell cell, int columnSpan, int rowSpan)
        {
            for (int i = 0; i < columnSpan; i++)
                for (int j = 0; j < rowSpan; j++)
                    setCell (cell.column + i, cell.row + j);

            return { { cell.column, cell.column + columnSpan }, { cell.row, cell.row + rowSpan } };
        }

        Grid::PlacementHelpers::LineArea setCell (Cell start, Cell end)
        {
            return setCell (start, std::abs (end.column - start.column),
                                   std::abs (end.row - start.row));
        }

        Cell nextAvailable (Cell referenceCell, int columnSpan, int rowSpan)
        {
            while (isOccupied (referenceCell, columnSpan, rowSpan) || isOutOfBounds (referenceCell, columnSpan, rowSpan))
                referenceCell = advance (referenceCell);

            return referenceCell;
        }

        Cell nextAvailableOnRow (Cell referenceCell, int columnSpan, int rowSpan, int rowNumber)
        {
            if (columnFirst && (rowNumber + rowSpan) > highestCrossDimension)
                highestCrossDimension = rowNumber + rowSpan;

            while (isOccupied (referenceCell, columnSpan, rowSpan)
                   || (referenceCell.row != rowNumber))
                referenceCell = advance (referenceCell);

            return referenceCell;
        }

        Cell nextAvailableOnColumn (Cell referenceCell, int columnSpan, int rowSpan, int columnNumber)
        {
            if (! columnFirst && (columnNumber + columnSpan) > highestCrossDimension)
                highestCrossDimension = columnNumber + columnSpan;

            while (isOccupied (referenceCell, columnSpan, rowSpan)
                   || (referenceCell.column != columnNumber))
                referenceCell = advance (referenceCell);

            return referenceCell;
        }

    private:
        struct SortableCell
        {
            int column, row;
            bool columnFirst;

            bool operator< (const SortableCell& other) const
            {
                if (columnFirst)
                {
                    if (row == other.row)
                        return column < other.column;

                    return row < other.row;
                }

                if (row == other.row)
                    return column < other.column;

                return row < other.row;
            }
        };

        void setCell (int column, int row)
        {
            occupiedCells.insert ({ column, row, columnFirst });
        }

        bool isOccupied (Cell cell) const
        {
            return occupiedCells.count ({ cell.column, cell.row, columnFirst }) > 0;
        }

        bool isOccupied (Cell cell, int columnSpan, int rowSpan) const
        {
            for (int i = 0; i < columnSpan; i++)
                for (int j = 0; j < rowSpan; j++)
                    if (isOccupied ({ cell.column + i, cell.row + j }))
                        return true;

            return false;
        }

        bool isOutOfBounds (Cell cell, int columnSpan, int rowSpan) const
        {
            const auto crossSpan = columnFirst ? rowSpan : columnSpan;

            return (getCrossDimension (cell) + crossSpan) > getHighestCrossDimension();
        }

        int getHighestCrossDimension() const
        {
            Cell cell { 1, 1 };

            if (occupiedCells.size() > 0)
                cell = { occupiedCells.crbegin()->column, occupiedCells.crbegin()->row };

            return std::max (getCrossDimension (cell), highestCrossDimension);
        }

        Cell advance (Cell cell) const
        {
            if ((getCrossDimension (cell) + 1) >= getHighestCrossDimension())
                return fromDimensions (getMainDimension (cell) + 1, 1);

            return fromDimensions (getMainDimension (cell), getCrossDimension (cell) + 1);
        }

        int getMainDimension (Cell cell) const   { return columnFirst ? cell.column : cell.row; }
        int getCrossDimension (Cell cell) const  { return columnFirst ? cell.row : cell.column; }

        Cell fromDimensions (int mainDimension, int crossDimension) const
        {
            if (columnFirst)
                return { mainDimension, crossDimension };

            return { crossDimension, mainDimension };
        }

        int highestCrossDimension;
        bool columnFirst;
        std::set<SortableCell> occupiedCells;
    };

    //==============================================================================
    static bool isFixed (GridItem::StartAndEndProperty prop)
    {
        return prop.start.hasName() || prop.start.hasAbsolute() || prop.end.hasName() || prop.end.hasAbsolute();
    }

    static bool hasFullyFixedPlacement (const GridItem& item)
    {
        if (item.area.isNotEmpty())
            return true;

        if (isFixed (item.column) && isFixed (item.row))
            return true;

        return false;
    }

    static bool hasPartialFixedPlacement (const GridItem& item)
    {
        if (item.area.isNotEmpty())
            return false;

        if (isFixed (item.column) ^ isFixed (item.row))
            return true;

        return false;
    }

    static bool hasAutoPlacement (const GridItem& item)
    {
        return ! hasFullyFixedPlacement (item) && ! hasPartialFixedPlacement (item);
    }

    //==============================================================================
    static bool hasDenseAutoFlow (Grid::AutoFlow autoFlow)
    {
        return autoFlow == Grid::AutoFlow::columnDense
            || autoFlow == Grid::AutoFlow::rowDense;
    }

    static bool isColumnAutoFlow (Grid::AutoFlow autoFlow)
    {
        return autoFlow == Grid::AutoFlow::column
            || autoFlow == Grid::AutoFlow::columnDense;
    }

    //==============================================================================
    static int getSpanFromAuto (GridItem::StartAndEndProperty prop)
    {
        if (prop.end.hasSpan())
            return prop.end.number;

        if (prop.start.hasSpan())
            return prop.start.number;

        return 1;
    }

    //==============================================================================
    ItemPlacementArray deduceAllItems (Grid& grid) const
    {
        const auto namedAreas = Grid::PlacementHelpers::deduceNamedAreas (grid.templateAreas);

        OccupancyPlane plane (juce::jmax (grid.templateColumns.size() + 1, 2),
                              juce::jmax (grid.templateRows.size() + 1, 2),
                              isColumnAutoFlow (grid.autoFlow));

        ItemPlacementArray itemPlacementArray;
        juce::Array<GridItem*> sortedItems;

        for (auto& item : grid.items)
            sortedItems.add (&item);

        sortedItems.sort (*this, true);

        // place fixed items first
        for (auto* item : sortedItems)
        {
            if (hasFullyFixedPlacement (*item))
            {
                const auto a = Grid::PlacementHelpers::deduceLineArea (*item, grid, namedAreas);
                plane.setCell ({ a.column.start, a.row.start }, { a.column.end, a.row.end });
                itemPlacementArray.add ({ item, a });
            }
        }

        OccupancyPlane::Cell lastInsertionCell = { 1, 1 };

        for (auto* item : sortedItems)
        {
            if (hasPartialFixedPlacement (*item))
            {
                if (isFixed (item->column))
                {
                    const auto p = Grid::PlacementHelpers::deduceLineRange (item->column, grid.templateColumns);
                    const auto columnSpan = std::abs (p.start - p.end);
                    const auto rowSpan = getSpanFromAuto (item->row);

                    const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { p.start, 1 }
                                                                                : lastInsertionCell;
                    const auto nextAvailableCell = plane.nextAvailableOnColumn (insertionCell, columnSpan, rowSpan, p.start);
                    const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
                    lastInsertionCell = nextAvailableCell;

                    itemPlacementArray.add ({ item, lineArea });
                }
                else if (isFixed (item->row))
                {
                    const auto p = Grid::PlacementHelpers::deduceLineRange (item->row, grid.templateRows);
                    const auto columnSpan = getSpanFromAuto (item->column);
                    const auto rowSpan = std::abs (p.start - p.end);

                    const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { 1, p.start }
                                                                                : lastInsertionCell;

                    const auto nextAvailableCell = plane.nextAvailableOnRow (insertionCell, columnSpan, rowSpan, p.start);
                    const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);

                    lastInsertionCell = nextAvailableCell;

                    itemPlacementArray.add ({ item, lineArea });
                }
            }
        }

        lastInsertionCell = { 1, 1 };

        for (auto* item : sortedItems)
        {
            if (hasAutoPlacement (*item))
            {
                const auto columnSpan = getSpanFromAuto (item->column);
                const auto rowSpan = getSpanFromAuto (item->row);

                const auto nextAvailableCell = plane.nextAvailable (lastInsertionCell, columnSpan, rowSpan);
                const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);

                if (! hasDenseAutoFlow (grid.autoFlow))
                    lastInsertionCell = nextAvailableCell;

                itemPlacementArray.add ({ item,  lineArea });
            }
        }

        return itemPlacementArray;
    }

    //==============================================================================
    static std::pair<int, int> getHighestEndLinesNumbers (const ItemPlacementArray& items)
    {
        int columnEndLine = 1;
        int rowEndLine = 1;

        for (auto& item : items)
        {
            const auto p = item.second;
            columnEndLine = std::max (p.column.end, columnEndLine);
            rowEndLine = std::max (p.row.end, rowEndLine);
        }

        return { columnEndLine, rowEndLine };
    }

    static std::pair<juce::Array<TrackInfo>, juce::Array<TrackInfo>> createImplicitTracks (const Grid& grid,
                                                                                const ItemPlacementArray& items)
    {
        const auto columnAndRowLineEnds = getHighestEndLinesNumbers (items);

        juce::Array<TrackInfo> implicitColumnTracks, implicitRowTracks;

        for (int i = grid.templateColumns.size() + 1; i < columnAndRowLineEnds.first; i++)
            implicitColumnTracks.add (grid.autoColumns);

        for (int i = grid.templateRows.size() + 1; i < columnAndRowLineEnds.second; i++)
            implicitRowTracks.add (grid.autoRows);

        return { implicitColumnTracks, implicitRowTracks };
    }

    //==============================================================================
    static int compareElements (const GridItem* i1, const GridItem* i2) noexcept
    {
        return i1->order < i2->order ? -1 : (i2->order < i1->order ? 1 : 0);
    }

    //==============================================================================
    static void applySizeForAutoTracks (juce::Array<Grid::TrackInfo>& columns,
                                        juce::Array<Grid::TrackInfo>& rows,
                                        const ItemPlacementArray& itemPlacementArray)
    {
        auto isSpan = [](Grid::PlacementHelpers::LineRange r) -> bool { return std::abs (r.end - r.start) > 1; };

        auto getHighestItemOnRow = [isSpan](int rowNumber, const ItemPlacementArray& itemPlacementArrayToUse) -> float
        {
            float highestRowSize = 0.0f;

            for (const auto& i : itemPlacementArrayToUse)
                if (! isSpan (i.second.row) && i.second.row.start == rowNumber)
                    highestRowSize = std::max (highestRowSize, i.first->height + i.first->margin.top + i.first->margin.bottom);

            return highestRowSize;
        };

        auto getHighestItemOnColumn = [isSpan](int rowNumber, const ItemPlacementArray& itemPlacementArrayToUse) -> float
        {
            float highestColumnSize = 0.0f;
            for (const auto& i : itemPlacementArrayToUse)
                if (! isSpan (i.second.column) && i.second.column.start == rowNumber)
                    highestColumnSize = std::max (highestColumnSize, i.first->width + i.first->margin.left + i.first->margin.right);

            return highestColumnSize;
        };

        for (int i = 0; i < rows.size(); i++)
            if (rows.getReference (i).hasKeyword)
                rows.getReference (i).size = getHighestItemOnRow (i + 1, itemPlacementArray);

        for (int i = 0; i < columns.size(); i++)
            if (columns.getReference (i).hasKeyword)
                columns.getReference (i).size = getHighestItemOnColumn (i + 1, itemPlacementArray);
    }
};

//==============================================================================
struct Grid::BoxAlignment
{
    static juce::Rectangle<float> alignItem (const GridItem& item,
                                             const Grid& grid,
                                             juce::Rectangle<float> area)
    {
        // if item align is auto, inherit value from grid
        Grid::AlignItems alignType = Grid::AlignItems::start;
        Grid::JustifyItems justifyType = Grid::JustifyItems::start;

        if (item.alignSelf == GridItem::AlignSelf::autoValue)
            alignType = grid.alignItems;
        else
            alignType = static_cast<Grid::AlignItems> (item.alignSelf);

        if (item.justifySelf == GridItem::JustifySelf::autoValue)
            justifyType = grid.justifyItems;
        else
            justifyType = static_cast<Grid::JustifyItems> (item.justifySelf);

        // subtract margin from area
        area = juce::BorderSize<float> (item.margin.top, item.margin.left, item.margin.bottom, item.margin.right)
                  .subtractedFrom (area);

        // align and justify
        auto r = area;

        if (item.width != GridItem::notAssigned)
            r.setWidth (item.width);

        if (item.height != GridItem::notAssigned)
            r.setHeight (item.height);

        if (alignType == Grid::AlignItems::start && justifyType == Grid::JustifyItems::start)
            return r;

        if (alignType == Grid::AlignItems::end)
            r.setY (r.getY() + (area.getHeight() - r.getHeight()));

        if (justifyType == Grid::JustifyItems::end)
            r.setX (r.getX() + (area.getWidth() - r.getWidth()));

        if (alignType == Grid::AlignItems::center)
            r.setCentre (r.getCentreX(), area.getCentreY());

        if (justifyType == Grid::JustifyItems::center)
            r.setCentre (area.getCentreX(), r.getCentreY());

        return r;
    }
};

//==============================================================================
Grid::TrackInfo::TrackInfo() noexcept : hasKeyword (true) {}

Grid::TrackInfo::TrackInfo (Px sizeInPixels) noexcept : size (static_cast<float> (sizeInPixels.pixels)), isFraction (false) {}

Grid::TrackInfo::TrackInfo (Fr fractionOfFreeSpace) noexcept : size ((float)fractionOfFreeSpace.fraction), isFraction (true) {}

Grid::TrackInfo::TrackInfo (Px sizeInPixels, const juce::String& endLineNameToUse) noexcept : Grid::TrackInfo (sizeInPixels)
{
    endLineName = endLineNameToUse;
}

Grid::TrackInfo::TrackInfo (Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept : Grid::TrackInfo (fractionOfFreeSpace)
{
    endLineName = endLineNameToUse;
}

Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels) noexcept : Grid::TrackInfo (sizeInPixels)
{
    startLineName = startLineNameToUse;
}

Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace) noexcept : Grid::TrackInfo (fractionOfFreeSpace)
{
    startLineName = startLineNameToUse;
}

Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels, const juce::String& endLineNameToUse) noexcept
  : Grid::TrackInfo (startLineNameToUse, sizeInPixels)
{
    endLineName = endLineNameToUse;
}

Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept
  : Grid::TrackInfo (startLineNameToUse, fractionOfFreeSpace)
{
    endLineName = endLineNameToUse;
}

//==============================================================================
Grid::Grid() noexcept {}
Grid::~Grid() noexcept {}

//==============================================================================
void Grid::performLayout (juce::Rectangle<int> targetArea)
{
    const auto itemsAndAreas = Grid::AutoPlacement().deduceAllItems (*this);

    const auto implicitTracks = Grid::AutoPlacement::createImplicitTracks (*this, itemsAndAreas);
    auto columnTracks = templateColumns;
    auto rowTracks = templateRows;
    columnTracks.addArray (implicitTracks.first);
    rowTracks.addArray (implicitTracks.second);

    Grid::AutoPlacement::applySizeForAutoTracks (columnTracks, rowTracks, itemsAndAreas);

    Grid::SizeCalculation calculation;
    calculation.computeSizes (targetArea.toFloat().getWidth(),
                              targetArea.toFloat().getHeight(),
                              columnGap,
                              rowGap,
                              columnTracks,
                              rowTracks);

    for (auto& itemAndArea : itemsAndAreas)
    {
        const auto a = itemAndArea.second;
        const auto areaBounds = Grid::PlacementHelpers::getAreaBounds (a.column.start, a.column.end,
                                                                       a.row.start, a.row.end,
                                                                       columnTracks,
                                                                       rowTracks,
                                                                       calculation,
                                                                       alignContent,
                                                                       justifyContent,
                                                                       columnGap,
                                                                       rowGap);

        auto* item = itemAndArea.first;
        item->currentBounds = Grid::BoxAlignment::alignItem (*item, *this, areaBounds)
                                + targetArea.toFloat().getPosition();

        if (auto* c = item->associatedComponent)
            c->setBounds (item->currentBounds.toNearestInt());
    }
}

} // namespace juce