/*
    SPDX-FileCopyrightText: 2008-2010 Volker Lanz <vl@fidra.de>
    SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo@oxygen-icons.org>
    SPDX-FileCopyrightText: 2014-2017 Andrius Štikonas <andrius@stikonas.eu
    SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org>

    SPDX-License-Identifier: GPL-3.0-or-later
*/

#include "gui/partwidgetbase.h"
#include "gui/partwidget.h"

#include "core/partition.h"

#include <cmath>

const qint32 PartWidgetBase::m_Spacing = 2;
const qint32 PartWidgetBase::m_BorderWidth = 3;
const qint32 PartWidgetBase::m_BorderHeight = 3;
const qint32 PartWidgetBase::m_MinWidth = 30;

template<typename T>
T sum(const QList<T>& list)
{
    T rval = 0;
    for (const T & val : list)
        rval += val;
    return rval;
}

bool distributeLostPixels(QList<qint32>& childrenWidth, qint32 lostPixels)
{
    if (lostPixels == 0 || childrenWidth.size() == 0)
        return false;

    while (lostPixels > 0)
        for (qint32 i = 0; i < childrenWidth.size() && lostPixels > 0; i++) {
            childrenWidth[i]++;
            lostPixels--;
        }

    return true;
}

bool levelChildrenWidths(QList<qint32>& childrenWidth, const QList<qint32>& minChildrenWidth, const qint32 destWidgetWidth)
{
    if (childrenWidth.size() == 0)
        return false;

    distributeLostPixels(childrenWidth, destWidgetWidth - sum(childrenWidth));

    // if we find out a partition is too narrow, adjust its screen
    // width to its minimum width and increase adjust by how much we had to increase the
    // screen width. thus, in the end, we have the number of pixels we need
    // to find somewhere else in adjust.
    qint32 adjust = 0;
    for (qint32 i = 0; i < childrenWidth.size(); i++)
        if (childrenWidth[i] < minChildrenWidth[i]) {
            adjust += minChildrenWidth[i] - childrenWidth[i];
            childrenWidth[i] = minChildrenWidth[i];
        }

    // find out how many partitions are wide enough to have their width reduced; we'd love to
    // check for w > minWidth - (pixels_to_reduce_by), but that last value _depends_ on the
    // number we're trying to find here...
    qint32 numReducable = 0;
    for (qint32 i = 0; i < childrenWidth.size(); i++)
        if (childrenWidth[i] > minChildrenWidth[i])
            numReducable++;

    // no need to do anything... or nothing can be done because all are too narrow
    if (adjust == 0 || numReducable == 0)
        return false;

    // if we have adjusted one or more partitions (and not ALL of them, because in that
    // case, nothing will help us), go through the partitions again and reduce the
    // on screen widths of those big enough anyway
    const qint32 reduce = static_cast<qint32>(std::ceil(1.0 * adjust / numReducable));
    for (qint32 i = 0; i < childrenWidth.size(); i++)
        if (childrenWidth[i] > minChildrenWidth[i])
            childrenWidth[i] -= reduce;

    // distribute pixels lost due to rounding errors
    distributeLostPixels(childrenWidth, destWidgetWidth - sum(childrenWidth));

    return true;
}

void PartWidgetBase::positionChildren(const QWidget* destWidget, const PartitionNode::Partitions& partitions, QList<PartWidget*> widgets) const
{
    if (partitions.size() == 0)
        return;

    QList<qint32> childrenWidth;
    QList<qint32> minChildrenWidth;
    const qint32 destWidgetWidth = destWidget->width() - 2 * borderWidth() - (partitions.size() - 1) * spacing();

    if (destWidgetWidth < 0)
        return;

    qint64 totalLength = 0;
    for (const auto &p : partitions)
        totalLength += p->length();

    if (totalLength < 1)
        return;

    // calculate unleveled width for each child and store it
    for (const auto &p : partitions) {
        childrenWidth.append(static_cast<qint32>(p->length() * destWidgetWidth / totalLength));

        // Calculate the minimum width for the widget. This is easy for primary and logical partitions: they
        // just have a fixed min width (configured in m_MinWidth). But for extended partitions things
        // are not quite as simple. We need to calc the sum of the min widths for each child, taking
        // spacing and borders into account, and add our own min width.
        qint32 min = (minWidth() + 2 * borderWidth() + spacing()) * p->children().size() - spacing() + 2 * borderWidth();

        // if it's too small, this partition is a primary or logical so just use the configured value
        if (min < minWidth())
            min = minWidth();
        minChildrenWidth.append(min);
    }

    // now go level the widths as long as required
    while (levelChildrenWidths(childrenWidth, minChildrenWidth, destWidgetWidth))
        ;

    // move the children to their positions and resize them
    for (int i = 0, x = borderWidth(); i < widgets.size(); i++) {
        widgets[i]->setMinimumWidth(minChildrenWidth[i]);
        widgets[i]->move(x, borderHeight());
        widgets[i]->resize(childrenWidth[i], destWidget->height() - 2 * borderHeight());
        x += childrenWidth[i] + spacing();
    }
}

const QList<PartWidget*> PartWidgetBase::childWidgets() const
{
    QList<PartWidget*> rval;

    for (auto &o : children())
        if (PartWidget* w = qobject_cast<PartWidget*>(o))
            rval.append(w);

    return rval;
}
