#include "GGroup.h"
#include "GComponent.h"
#include "utils/ByteBuffer.h"

NS_FGUI_BEGIN
USING_NS_AX;

GGroup::GGroup() : _layout(GroupLayoutType::NONE),
                   _lineGap(0),
                   _columnGap(0),
                   _excludeInvisibles(false),
                   _autoSizeDisabled(false),
                   _mainGridIndex(-1),
                   _mainGridMinSize(10),
                   _mainChildIndex(-1),
                   _totalSize(0),
                   _numChildren(0),
                   _percentReady(false),
                   _boundsChanged(false),
                   _updating(false)
{
    _touchDisabled = true;
}

GGroup::~GGroup()
{
    CALL_LATER_CANCEL(GGroup, ensureBoundsCorrect);
}

void GGroup::setLayout(GroupLayoutType value)
{
    if (_layout != value)
    {
        _layout = value;
        setBoundsChangedFlag(true);
    }
}

void GGroup::setColumnGap(int value)
{
    if (_columnGap != value)
    {
        _columnGap = value;
        setBoundsChangedFlag();
    }
}

void GGroup::setLineGap(int value)
{
    if (_lineGap != value)
    {
        _lineGap = value;
        setBoundsChangedFlag();
    }
}

void GGroup::setExcludeInvisibles(bool value)
{
    if (_excludeInvisibles != value)
    {
        _excludeInvisibles = value;
        setBoundsChangedFlag();
    }
}
void GGroup::setAutoSizeDisabled(bool value)
{
    if (_autoSizeDisabled != value)
    {
        _autoSizeDisabled = value;
        setBoundsChangedFlag();
    }
}

void GGroup::setMainGridIndex(int value)
{
    if (_mainGridIndex != value)
    {
        _mainGridIndex = value;
        setBoundsChangedFlag();
    }
}

void GGroup::setMainGridMinSize(int value)
{
    if (_mainGridMinSize != value)
    {
        _mainGridMinSize = value;
        setBoundsChangedFlag();
    }
}

void GGroup::setBoundsChangedFlag(bool positionChangedOnly)
{
    if (_updating == 0 && _parent != nullptr)
    {
        if (!positionChangedOnly)
            _percentReady = false;

        if (!_boundsChanged)
        {
            _boundsChanged = true;

            if (_layout != GroupLayoutType::NONE)
                CALL_LATER(GGroup, ensureBoundsCorrect);
        }
    }
}

void GGroup::ensureBoundsCorrect()
{
    if (_parent == nullptr || !_boundsChanged)
        return;

    CALL_LATER_CANCEL(GGroup, ensureBoundsCorrect);
    _boundsChanged = false;

    if (_autoSizeDisabled)
        resizeChildren(0, 0);
    else
    {
        handleLayout();
        updateBounds();
    }
}

void GGroup::updateBounds()
{
    int cnt = _parent->numChildren();
    int i;
    GObject* child;
    float ax = FLT_MAX, ay = FLT_MAX;
    float ar = FLT_MIN, ab = FLT_MIN;
    float tmp;
    bool empty = true;

    for (i = 0; i < cnt; i++)
    {
        child = _parent->getChildAt(i);
        if (child->_group != this || (_excludeInvisibles && !child->internalVisible3()))
            continue;

        tmp = child->getX();
        if (tmp < ax)
            ax = tmp;
        tmp = child->getY();
        if (tmp < ay)
            ay = tmp;
        tmp = child->getX() + child->getWidth();
        if (tmp > ar)
            ar = tmp;
        tmp = child->getY() + child->getHeight();
        if (tmp > ab)
            ab = tmp;

        empty = false;
    }

    float w;
    float h;
    if (!empty)
    {
        _updating |= 1;
        setPosition(ax, ay);
        _updating &= 2;

        w = ar - ax;
        h = ab - ay;
    }
    else
        w = h = 0;

    if ((_updating & 2) == 0)
    {
        _updating |= 2;
        setSize(w, h);
        _updating &= 1;
    }
    else
    {
        _updating &= 1;
        resizeChildren(getWidth() - w, getHeight() - h);
    }
}

void GGroup::handleLayout()
{
    _updating |= 1;

    if (_layout == GroupLayoutType::HORIZONTAL)
    {
        float curX = getX();
        int cnt = _parent->numChildren();
        for (int i = 0; i < cnt; i++)
        {
            GObject* child = _parent->getChildAt(i);
            if (child->_group != this)
                continue;
            if (_excludeInvisibles && !child->internalVisible3())
                continue;

            child->setXMin(curX);
            if (child->getWidth() != 0)
                curX += child->getWidth() + _columnGap;
        }
    }
    else if (_layout == GroupLayoutType::VERTICAL)
    {
        float curY = getY();
        int cnt = _parent->numChildren();
        for (int i = 0; i < cnt; i++)
        {
            GObject* child = _parent->getChildAt(i);
            if (child->_group != this)
                continue;
            if (_excludeInvisibles && !child->internalVisible3())
                continue;

            child->setYMin(curY);
            if (child->getHeight() != 0)
                curY += child->getHeight() + _lineGap;
        }
    }

    _updating &= 2;
}

void GGroup::moveChildren(float dx, float dy)
{
    if ((_updating & 1) != 0 || _parent == nullptr)
        return;

    _updating |= 1;

    int cnt = _parent->numChildren();
    int i;
    GObject* child;
    for (i = 0; i < cnt; i++)
    {
        child = _parent->getChildAt(i);
        if (child->_group == this)
        {
            child->setPosition(child->getX() + dx, child->getY() + dy);
        }
    }

    _updating &= 2;
}

void GGroup::resizeChildren(float dw, float dh)
{
    if (_layout == GroupLayoutType::NONE || (_updating & 2) != 0 || _parent == nullptr)
        return;

    _updating |= 2;

    if (_boundsChanged)
    {
        _boundsChanged = false;
        if (!_autoSizeDisabled)
        {
            updateBounds();
            return;
        }
    }

    int cnt = _parent->numChildren();

    if (!_percentReady)
    {
        _percentReady = true;
        _numChildren = 0;
        _totalSize = 0;
        _mainChildIndex = -1;

        int j = 0;
        for (int i = 0; i < cnt; i++)
        {
            GObject* child = _parent->getChildAt(i);
            if (child->_group != this)
                continue;

            if (!_excludeInvisibles || child->internalVisible3())
            {
                if (j == _mainGridIndex)
                    _mainChildIndex = i;

                _numChildren++;

                if (_layout == GroupLayoutType::HORIZONTAL)
                    _totalSize += child->getWidth();
                else
                    _totalSize += child->getHeight();
            }

            j++;
        }

        if (_mainChildIndex != -1)
        {
            if (_layout == GroupLayoutType::HORIZONTAL)
            {
                GObject* child = _parent->getChildAt(_mainChildIndex);
                _totalSize += _mainGridMinSize - child->getWidth();
                child->_sizePercentInGroup = _mainGridMinSize / _totalSize;
            }
            else
            {
                GObject* child = _parent->getChildAt(_mainChildIndex);
                _totalSize += _mainGridMinSize - child->getHeight();
                child->_sizePercentInGroup = _mainGridMinSize / _totalSize;
            }
        }

        for (int i = 0; i < cnt; i++)
        {
            GObject* child = _parent->getChildAt(i);
            if (child->_group != this)
                continue;

            if (i == _mainChildIndex)
                continue;

            if (_totalSize > 0)
                child->_sizePercentInGroup = (_layout == GroupLayoutType::HORIZONTAL ? child->getWidth() : child->getHeight()) / _totalSize;
            else
                child->_sizePercentInGroup = 0;
        }
    }

    float remainSize = 0;
    float remainPercent = 1;
    bool priorHandled = false;

    if (_layout == GroupLayoutType::HORIZONTAL)
    {
        remainSize = getWidth() - (_numChildren - 1) * _columnGap;
        if (_mainChildIndex != -1 && remainSize >= _totalSize)
        {
            GObject* child = _parent->getChildAt(_mainChildIndex);
            child->setSize(remainSize - (_totalSize - _mainGridMinSize), child->_rawSize.height + dh, true);
            remainSize -= child->getWidth();
            remainPercent -= child->_sizePercentInGroup;
            priorHandled = true;
        }

        float curX = getX();
        for (int i = 0; i < cnt; i++)
        {
            GObject* child = _parent->getChildAt(i);
            if (child->_group != this)
                continue;

            if (_excludeInvisibles && !child->internalVisible3())
            {
                child->setSize(child->_rawSize.width, child->_rawSize.height + dh, true);
                continue;
            }

            if (!priorHandled || i != _mainChildIndex)
            {
                child->setSize(round(child->_sizePercentInGroup / remainPercent * remainSize), child->_rawSize.height + dh, true);
                remainPercent -= child->_sizePercentInGroup;
                remainSize -= child->getWidth();
            }

            child->setXMin(curX);
            if (child->getWidth() != 0)
                curX += child->getWidth() + _columnGap;
        }
    }
    else
    {
        remainSize = getHeight() - (_numChildren - 1) * _lineGap;
        if (_mainChildIndex != -1 && remainSize >= _totalSize)
        {
            GObject* child = _parent->getChildAt(_mainChildIndex);
            child->setSize(child->_rawSize.width + dw, remainSize - (_totalSize - _mainGridMinSize), true);
            remainSize -= child->getHeight();
            remainPercent -= child->_sizePercentInGroup;
            priorHandled = true;
        }

        float curY = getY();
        for (int i = 0; i < cnt; i++)
        {
            GObject* child = _parent->getChildAt(i);
            if (child->_group != this)
                continue;

            if (_excludeInvisibles && !child->internalVisible3())
            {
                child->setSize(child->_rawSize.width + dw, child->_rawSize.height, true);
                continue;
            }

            if (!priorHandled || i != _mainChildIndex)
            {
                child->setSize(child->_rawSize.width + dw, round(child->_sizePercentInGroup / remainPercent * remainSize), true);
                remainPercent -= child->_sizePercentInGroup;
                remainSize -= child->getHeight();
            }

            child->setYMin(curY);
            if (child->getHeight() != 0)
                curY += child->getHeight() + _lineGap;
        }
    }

    _updating &= 1;
}

void GGroup::handleAlphaChanged()
{
    GObject::handleAlphaChanged();

    if (_underConstruct)
        return;

    int cnt = _parent->numChildren();
    for (int i = 0; i < cnt; i++)
    {
        GObject* child = _parent->getChildAt(i);
        if (child->_group == this)
            child->setAlpha(_alpha);
    }
}

void GGroup::handleVisibleChanged()
{
    if (!_parent)
        return;

    int cnt = _parent->numChildren();
    for (int i = 0; i < cnt; i++)
    {
        GObject* child = _parent->getChildAt(i);
        if (child->_group == this)
            child->handleVisibleChanged();
    }
}

void GGroup::setup_beforeAdd(ByteBuffer* buffer, int beginPos)
{
    GObject::setup_beforeAdd(buffer, beginPos);

    buffer->seek(beginPos, 5);

    _layout = (GroupLayoutType)buffer->readByte();
    _lineGap = buffer->readInt();
    _columnGap = buffer->readInt();
    if (buffer->version >= 2)
    {
        _excludeInvisibles = buffer->readBool();
        _autoSizeDisabled = buffer->readBool();
        _mainGridIndex = buffer->readShort();
    }
}

void GGroup::setup_afterAdd(ByteBuffer* buffer, int beginPos)
{
    GObject::setup_afterAdd(buffer, beginPos);

    if (!_visible)
        handleVisibleChanged();
}

NS_FGUI_END