/*
 * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "config.h"
#include "AreaAllocator.h"

#if USE(COORDINATED_GRAPHICS)

using namespace WebCore;

namespace WebKit {

AreaAllocator::AreaAllocator(const IntSize& size)
    : m_size(size)
    , m_minAlloc(1, 1)
    , m_margin(0, 0)
{
}

AreaAllocator::~AreaAllocator()
{
}

void AreaAllocator::expand(const IntSize& size)
{
    m_size = m_size.expandedTo(size);
}

void AreaAllocator::expandBy(const IntSize& size)
{
    m_size += size;
}

void AreaAllocator::release(const IntRect&)
{
}

int AreaAllocator::overhead() const
{
    return 0;
}

IntSize AreaAllocator::roundAllocation(const IntSize& size) const
{
    int width = size.width() + m_margin.width();
    int height = size.height() + m_margin.height();
    int extra = width % m_minAlloc.width();
    if (extra)
        width += m_minAlloc.width() - extra;
    extra = height % m_minAlloc.height();
    if (extra)
        height += m_minAlloc.height() - extra;

    return IntSize(width, height);
}

GeneralAreaAllocator::GeneralAreaAllocator(const IntSize& size)
    : AreaAllocator(nextPowerOfTwo(size))
{
    m_root = new Node();
    m_root->rect = IntRect(0, 0, m_size.width(), m_size.height());
    m_root->largestFree = m_size;
    m_nodeCount = 1;
    setMinimumAllocation(IntSize(8, 8));
}

GeneralAreaAllocator::~GeneralAreaAllocator()
{
    freeNode(m_root);
}

void GeneralAreaAllocator::freeNode(Node* node)
{
    if (node) {
        freeNode(node->left);
        freeNode(node->right);
    }
    delete node;
}

void GeneralAreaAllocator::expand(const IntSize& size)
{
    AreaAllocator::expand(nextPowerOfTwo(size));

    if (m_root->rect.size() == m_size)
        return; // No change.

    if (!m_root->left && m_root->largestFree.width() > 0) {
        // No allocations have occurred, so just adjust the root size.
        m_root->rect = IntRect(0, 0, m_size.width(), m_size.height());
        m_root->largestFree = m_size;
        return;
    }

    // Add extra nodes above the current root to expand the tree.
    Node* oldRoot = m_root;
    Split split;
    if (m_size.width() >= m_size.height())
        split = SplitOnX;
    else
        split = SplitOnY;

    while (m_root->rect.size() != m_size) {
        if (m_root->rect.width() == m_size.width())
            split = SplitOnY;
        else if (m_root->rect.height() == m_size.height())
            split = SplitOnX;
        Node* parent = new Node();
        Node* right = new Node();
        m_nodeCount += 2;
        m_root->parent = parent;
        parent->parent = nullptr;
        parent->left = m_root;
        parent->right = right;
        parent->largestFree = m_root->rect.size();
        right->parent = parent;
        right->left = nullptr;
        right->right = nullptr;
        right->largestFree = m_root->rect.size();
        if (split == SplitOnX) {
            parent->rect = IntRect(m_root->rect.x(), m_root->rect.y(),
                m_root->rect.width() * 2, m_root->rect.height());
            right->rect = IntRect(m_root->rect.x() + m_root->rect.width(), m_root->rect.y(),
                m_root->rect.width(), m_root->rect.height());
        } else {
            parent->rect = IntRect(m_root->rect.x(), m_root->rect.y(),
                m_root->rect.width(), m_root->rect.height() * 2);
            right->rect = IntRect(m_root->rect.x(), m_root->rect.y() + m_root->rect.width(),
                m_root->rect.width(), m_root->rect.height());
        }
        split = (split == SplitOnX ? SplitOnY : SplitOnX);
        m_root = parent;
    }
    updateLargestFree(oldRoot);
}

static inline bool fitsWithin(const IntSize& size1, const IntSize& size2)
{
    return size1.width() <= size2.width() && size1.height() <= size2.height();
}

IntRect GeneralAreaAllocator::allocate(const IntSize& size)
{
    IntSize rounded = roundAllocation(size);
    rounded = nextPowerOfTwo(rounded);
    if (rounded.width() <= 0 || rounded.width() > m_size.width()
        || rounded.height() <= 0 || rounded.height() > m_size.height())
        return IntRect();

    IntPoint point = allocateFromNode(rounded, m_root);
    if (point.x() >= 0)
        return IntRect(point, size);
    return IntRect();
}

IntPoint GeneralAreaAllocator::allocateFromNode(const IntSize& size, Node* node)
{
    // Find the best node to insert into, which should be
    // a node with the least amount of unused space that is
    // big enough to contain the requested size.
    while (node) {
        // Go down a level and determine if the left or right
        // sub-tree contains the best chance of allocation.
        Node* left = node->left;
        Node* right = node->right;
        if (left && fitsWithin(size, left->largestFree)) {
            if (right && fitsWithin(size, right->largestFree)) {
                if (left->largestFree.width() < right->largestFree.width()
                    || left->largestFree.height() < right->largestFree.height()) {
                    // The largestFree values may be a little oversized,
                    // so try the left sub-tree and then the right sub-tree.
                    IntPoint point = allocateFromNode(size, left);
                    if (point.x() >= 0)
                        return point;
                    return allocateFromNode(size, right);
                }
                node = right;
            } else
                node = left;
        } else if (right && fitsWithin(size, right->largestFree))
            node = right;
        else if (left || right) {
            // Neither sub-node has enough space to allocate from.
            return IntPoint(-1, -1);
        } else if (fitsWithin(size, node->largestFree)) {
            // Do we need to split this node into smaller pieces?
            Split split;
            if (fitsWithin(IntSize(size.width() * 2, size.height() * 2), node->largestFree)) {
                // Split in either direction: choose the inverse of
                // the parent node's split direction to try to balance
                // out the wasted space as further subdivisions happen.
                if (node->parent
                    && node->parent->left->rect.x() == node->parent->right->rect.x())
                    split = SplitOnX;
                else if (node->parent)
                    split = SplitOnY;
                else if (node->rect.width() >= node->rect.height())
                    split = SplitOnX;
                else
                    split = SplitOnY;
            } else if (fitsWithin(IntSize(size.width() * 2, size.height()), node->largestFree)) {
                // Split along the X direction.
                split = SplitOnX;
            } else if (fitsWithin(IntSize(size.width(), size.height() * 2), node->largestFree)) {
                // Split along the Y direction.
                split = SplitOnY;
            } else {
                // Cannot split further - allocate this node.
                node->largestFree = IntSize(0, 0);
                updateLargestFree(node);
                return node->rect.location();
            }

            // Split the node, then go around again using the left sub-tree.
            node = splitNode(node, split);
        } else {
            // Cannot possibly fit into this node.
            break;
        }
    }
    return IntPoint(-1, -1);
}

GeneralAreaAllocator::Node* GeneralAreaAllocator::splitNode(Node* node, Split split)
{
    Node* left = new Node();
    left->parent = node;
    Node* right = new Node();
    right->parent = node;
    node->left = left;
    node->right = right;
    m_nodeCount += 2;

    if (split == SplitOnX) {
        left->rect = IntRect(node->rect.x(), node->rect.y(),
            node->rect.width() / 2, node->rect.height());
        right->rect = IntRect(left->rect.maxX(), node->rect.y(),
            node->rect.width() / 2, node->rect.height());
    } else {
        left->rect = IntRect(node->rect.x(), node->rect.y(),
            node->rect.width(), node->rect.height() / 2);
        right->rect = IntRect(node->rect.x(), left->rect.maxY(),
            node->rect.width(), node->rect.height() / 2);
    }

    left->largestFree = left->rect.size();
    right->largestFree = right->rect.size();
    node->largestFree = right->largestFree;
    return left;
}

void GeneralAreaAllocator::updateLargestFree(Node* node)
{
    while ((node = node->parent)) {
        node->largestFree = IntSize(
            std::max(node->left->largestFree.width(), node->right->largestFree.width()),
            std::max(node->left->largestFree.height(), node->right->largestFree.height())
            );
    }
}

void GeneralAreaAllocator::release(const IntRect& rect)
{
    // Locate the node that contains the allocated region.
    Node* node = m_root;
    IntPoint point = rect.location();
    while (node) {
        if (node->left && node->left->rect.contains(point))
            node = node->left;
        else if (node->right && node->right->rect.contains(point))
            node = node->right;
        else if (node->rect.contains(point))
            break;
        else
            return; // Point is completely outside the tree.
    }
    if (!node)
        return;

    // Mark the node as free and then work upwards through the tree
    // recombining and deleting nodes until we reach a sibling
    // that is still allocated.
    node->largestFree = node->rect.size();
    while (node->parent) {
        if (node->parent->left == node) {
            if (node->parent->right->largestFree != node->parent->right->rect.size())
                break;
        } else {
            if (node->parent->left->largestFree != node->parent->left->rect.size())
                break;
        }
        node = node->parent;
        freeNode(node->left);
        freeNode(node->right);
        m_nodeCount -= 2;
        node->left = nullptr;
        node->right = nullptr;
        node->largestFree = node->rect.size();
    }

    // Make the rest of our ancestors have the correct "largest free size".
    updateLargestFree(node);
}

int GeneralAreaAllocator::overhead() const
{
    return m_nodeCount * sizeof(Node);
}

} // namespace WebKit

#endif // USE(COORDINATED_GRAPHICS)
