/*************************************************************************
 *  Copyright (C) 2008 by Volker Lanz <vl@fidra.de>                      *
 *  Copyright (C) 2016 by Andrius Štikonas <andrius@stikonas.eu>         *
 *                                                                       *
 *  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 3 of       *
 *  the License, or (at your option) 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 "core/partitionnode.h"

#include "core/partition.h"
#include "core/partitionrole.h"

#include "fs/filesystem.h"

/** Tries to find the predecessor for a Partition.
    @param p the Partition to find a predecessor for
    @return pointer to the predecessor or nullptr if none was found
*/
Partition* PartitionNode::predecessor(Partition& p)
{
    Q_ASSERT(p.parent());

    Partitions& plist = p.parent()->isRoot() == false ? p.parent()->children() : children();

    for (int idx = 1; idx < plist.size(); idx++)
        if (plist[idx] == &p)
            return plist[idx - 1];

    return nullptr;
}

/**
    @overload
*/
const Partition* PartitionNode::predecessor(const Partition& p) const
{
    Q_ASSERT(p.parent());

    const Partitions& plist = p.parent()->isRoot() == false ? p.parent()->children() : children();

    for (int idx = 1; idx < plist.size(); idx++)
        if (plist[idx] == &p)
            return plist[idx - 1];

    return nullptr;
}

/** Tries to find the successor for a Partition.
    @param p the Partition to find a successor for
    @return pointer to the successor or nullptr if none was found
 */
Partition* PartitionNode::successor(Partition& p)
{
    Q_ASSERT(p.parent());

    Partitions& plist = p.parent()->isRoot() == false ? p.parent()->children() : children();

    for (int idx = plist.size() - 2; idx >= 0; idx--)
        if (plist[idx] == &p)
            return plist[idx + 1];

    return nullptr;
}

/**
    @overload
*/
const Partition* PartitionNode::successor(const Partition& p) const
{
    Q_ASSERT(p.parent());

    const Partitions& plist = p.parent()->isRoot() == false ? p.parent()->children() : children();

    for (int idx = plist.size() - 2; idx >= 0; idx--)
        if (plist[idx] == &p)
            return plist[idx + 1];

    return nullptr;
}

/** Inserts a Partition into a PartitionNode's children
    @param p pointer to the Partition to insert. May be nullptr.
    @return true on success
*/
bool PartitionNode::insert(Partition* p)
{
    if (p == nullptr)
        return false;

    for (int idx = 0; idx < children().size(); idx++) {
        if (children()[idx]->firstSector() > p->firstSector()) {
            children().insert(idx, p);
            return true;
        }
    }

    children().insert(children().size(), p);

    return true;
}

/** Removes a Partition from the PartitionNode's children.
    @param p pointer to the Partition to remove. May be nullptr.
    @return true on success.
*/
bool PartitionNode::remove(Partition* p)
{
    if (p == nullptr)
        return false;

    return children().removeOne(p);
}

/** Deletes all children */
void PartitionNode::clearChildren()
{
    qDeleteAll(children());
    children().clear();
}

/** Finds a Partition by sector.
    @param s the sector the Partition is at
    @param role the PartitionRole the Partition is supposed to have
    @return pointer to the Partition found or nullptr if none was found
*/
Partition* PartitionNode::findPartitionBySector(qint64 s, const PartitionRole& role)
{
    const auto partitions = children();
    for (auto &p : partitions) {
        // (women and) children first. ;-)
        const auto pChildren = p->children();
        for (auto &child : pChildren)
            if ((child->roles().roles() & role.roles()) && s >= child->firstSector() && s <= child->lastSector())
                return child;

        if ((p->roles().roles() & role.roles()) && s >= p->firstSector() && s <= p->lastSector())
            return p;
    }

    return nullptr;
}

/**
    @overload
*/
const Partition* PartitionNode::findPartitionBySector(qint64 s, const PartitionRole& role) const
{
    for (const auto *p : children()) {
        for (const auto &child : p->children())
            if ((child->roles().roles() & role.roles()) && s >= child->firstSector() && s <= child->lastSector())
                return child;

        if ((p->roles().roles() & role.roles()) && s >= p->firstSector() && s <= p->lastSector())
            return p;
    }

    return nullptr;
}

/** Reparents a Partition to this PartitionNode
    @param p the Partition to reparent
*/
void PartitionNode::reparent(Partition& p)
{
    p.setParent(this);

    if (!isRoot())
        p.setRoles(PartitionRole(PartitionRole::Logical));
    else if (!p.roles().has(PartitionRole::Extended))
        p.setRoles(PartitionRole(PartitionRole::Primary));
    else
        p.setRoles(PartitionRole(PartitionRole::Extended));
}

/** @return the number of the highest mounted child, e.g. 7 if /dev/sdd7 is a child of this PartitionNode and mounted and /dev/sdd8 and /dev/sdd9 and so on aren't
*/
qint32 PartitionNode::highestMountedChild() const
{
    qint32 result = -1;

    for (const auto * p : children())
        if (p->number() > result && p->isMounted())
            result = p->number();

    return result;
}

/** @return true if any of the partition's children are mounted */
bool PartitionNode::isChildMounted() const
{
    for (const auto * child : children())
        if (child->isMounted() || (child->hasChildren() && child->isChildMounted()))
            return true;

    return false;
}
