/*
 *  The Mana Client
 *  Copyright (C) 2007-2009  The Mana World Development Team
 *  Copyright (C) 2009-2012  The Mana Developers
 *
 *  This file is part of The Mana Client.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "gui/widgets/layout.h"

#include <cassert>

ContainerPlacer ContainerPlacer::at(int x, int y)
{
    return ContainerPlacer(mContainer, &mCell->at(x, y));
}

LayoutCell &ContainerPlacer::operator()
    (int x, int y, gcn::Widget *wg, int w, int h)
{
    mContainer->add(wg);
    return mCell->place(wg, x, y, w, h);
}

LayoutCell::~LayoutCell()
{
    if (mType == ARRAY)
        delete mArray;
}

LayoutArray &LayoutCell::getArray()
{
    assert(mType != WIDGET);
    if (mType == ARRAY)
        return *mArray;
    mArray = new LayoutArray;
    mType = ARRAY;
    mExtent[0] = 1;
    mExtent[1] = 1;
    mPadding = 0;
    mAlign[0] = FILL;
    mAlign[1] = FILL;
    return *mArray;
}

void LayoutCell::reflow(int nx, int ny, int nw, int nh)
{
    assert(mType != NONE);
    nx += mPadding;
    ny += mPadding;
    nw -= 2 * mPadding;
    nh -= 2 * mPadding;
    if (mType == ARRAY)
        mArray->reflow(nx, ny, nw, nh);
    else
        mWidget->setDimension(gcn::Rectangle(nx, ny, nw, nh));
}

void LayoutCell::computeSizes()
{
    assert(mType == ARRAY);

    for (auto &row : mArray->mCells)
    {
        for (auto cell : row)
        {
            if (cell && cell->mType == ARRAY)
                cell->computeSizes();
        }
    }

    mSize[0] = mArray->getSize(0);
    mSize[1] = mArray->getSize(1);
}

LayoutArray::LayoutArray(): mSpacing(4)
{
}

LayoutArray::~LayoutArray()
{
    for (auto &row : mCells)
    {
        for (auto &cell : row)
        {
            delete cell;
        }
    }
}

LayoutCell &LayoutArray::at(int x, int y, int w, int h)
{
    resizeGrid(x + w, y + h);
    LayoutCell *&cell = mCells[y][x];
    if (!cell)
    {
        cell = new LayoutCell;
    }
    return *cell;
}

void LayoutArray::resizeGrid(int w, int h)
{
    bool extW = w && w > (int)mSizes[0].size(),
         extH = h && h > (int)mSizes[1].size();
    if (!extW && !extH)
        return;

    if (extH)
    {
        mSizes[1].resize(h, Layout::AUTO_DEF);
        mCells.resize(h);
        if (!extW) w = mSizes[0].size();
    }

    if (extW)
    {
        mSizes[0].resize(w, Layout::AUTO_DEF);
    }

    for (auto &row : mCells)
    {
        row.resize(w, nullptr);
    }
}

void LayoutArray::setColWidth(int n, int w)
{
    resizeGrid(n + 1, 0);
    mSizes[0][n] = w;
}

void LayoutArray::setRowHeight(int n, int h)
{
    resizeGrid(0, n + 1);
    mSizes[1][n] = h;
}

void LayoutArray::matchColWidth(int n1, int n2)
{
    resizeGrid(std::max(n1, n2) + 1, 0);
    std::vector< short > widths = getSizes(0, Layout::AUTO_DEF);
    int s = std::max(widths[n1], widths[n2]);
    mSizes[0][n1] = s;
    mSizes[0][n2] = s;
}

void LayoutArray::extend(int x, int y, int w, int h)
{
    LayoutCell &cell = at(x, y, w, h);
    cell.mExtent[0] = w;
    cell.mExtent[1] = h;
}

LayoutCell &LayoutArray::place(gcn::Widget *widget, int x, int y, int w, int h)
{
    LayoutCell &cell = at(x, y, w, h);
    assert(cell.mType == LayoutCell::NONE);
    cell.mType = LayoutCell::WIDGET;
    cell.mWidget = widget;
    cell.mSize[0] = w == 1 ? widget->getWidth() : 0;
    cell.mSize[1] = h == 1 ? widget->getHeight() : 0;
    cell.mExtent[0] = w;
    cell.mExtent[1] = h;
    cell.mPadding = 0;
    cell.mAlign[0] = LayoutCell::FILL;
    cell.mAlign[1] = LayoutCell::FILL;
    short &cs = mSizes[0][x];
    short &rs = mSizes[1][y];
    if (cs == Layout::AUTO_DEF && w == 1)
        cs = 0;
    if (rs == Layout::AUTO_DEF && h == 1)
        rs = 0;
    return cell;
}

void LayoutArray::align(int &pos, int &size, int dim,
                        LayoutCell const &cell, const short *sizes) const
{
    int size_max = sizes[0];
    for (int i = 1; i < cell.mExtent[dim]; ++i)
        size_max += sizes[i] + mSpacing;
    size = std::min<int>(cell.mSize[dim], size_max);

    switch (cell.mAlign[dim])
    {
        case LayoutCell::LEFT:
            return;
        case LayoutCell::RIGHT:
            pos += size_max - size;
            return;
        case LayoutCell::CENTER:
            pos += (size_max - size) / 2;
            return;
        case LayoutCell::FILL:
            size = size_max;
            return;
    }
}

std::vector< short > LayoutArray::getSizes(int dim, int upp) const
{
    int gridW = mSizes[0].size();
    int gridH = mSizes[1].size();
    std::vector< short > sizes = mSizes[dim];

    // Compute minimum sizes.
    for (int gridY = 0; gridY < gridH; ++gridY)
    {
        for (int gridX = 0; gridX < gridW; ++gridX)
        {
            LayoutCell const *cell = mCells[gridY][gridX];
            if (!cell || cell->mType == LayoutCell::NONE)
                continue;

            if (cell->mExtent[dim] == 1)
            {
                int n = dim == 0 ? gridX : gridY;
                int s = cell->mSize[dim] + cell->mPadding * 2;
                if (s > sizes[n]) sizes[n] = s;
            }
        }
    }

    if (upp == Layout::AUTO_DEF) return sizes;

    // Compute the FILL sizes.
    int nb = sizes.size();
    int nbFill = 0;
    for (int i = 0; i < nb; ++i)
    {
        if (mSizes[dim][i] <= Layout::AUTO_DEF)
        {
            ++nbFill;
            if (mSizes[dim][i] == Layout::AUTO_SET ||
                sizes[i] <= Layout::AUTO_DEF)
            {
                sizes[i] = 0;
            }
        }
        upp -= sizes[i] + mSpacing;
    }
    upp = upp + mSpacing;

    if (nbFill == 0)
        return sizes;

    for (int i = 0; i < nb; ++i)
    {
        if (mSizes[dim][i] > Layout::AUTO_DEF)
            continue;
        int s = upp / nbFill;
        sizes[i] += s;
        upp -= s;
        --nbFill;
    }

    return sizes;
}

int LayoutArray::getSize(int dim) const
{
    std::vector< short > sizes = getSizes(dim, Layout::AUTO_DEF);
    int size = 0;
    int nb = sizes.size();
    for (int i = 0; i < nb; ++i)
    {
        if (sizes[i] > Layout::AUTO_DEF) size += sizes[i];
        size += mSpacing;
    }
    return size - mSpacing;
}

void LayoutArray::reflow(int nx, int ny, int nw, int nh)
{
    int gridW = mSizes[0].size();
    int gridH = mSizes[1].size();

    std::vector< short > widths  = getSizes(0, nw);
    std::vector< short > heights = getSizes(1, nh);

    int y = ny;
    for (int gridY = 0; gridY < gridH; ++gridY)
    {
        int x = nx;
        for (int gridX = 0; gridX < gridW; ++gridX)
        {
            LayoutCell *cell = mCells[gridY][gridX];
            if (cell && cell->mType != LayoutCell::NONE)
            {
                int dx = x, dy = y, dw, dh;
                align(dx, dw, 0, *cell, &widths[gridX]);
                align(dy, dh, 1, *cell, &heights[gridY]);
                cell->reflow(dx, dy, dw, dh);
            }
            x += widths[gridX] + mSpacing;
        }
        y += heights[gridY] + mSpacing;
    }
}

Layout::Layout(): mComputed(false)
{
    getArray();
    setPadding(6);
}

void Layout::reflow(int &nw, int &nh)
{
    if (!mComputed)
    {
        computeSizes();
        mComputed = true;
    }

    nw = nw == 0 ? mSize[0] + 2 * mPadding : nw;
    nh = nh == 0 ? mSize[1] + 2 * mPadding : nh;
    LayoutCell::reflow(0, 0, nw, nh);
}
