/*************************************************************************
 *  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 "ops/newoperation.h"

#include "core/partition.h"
#include "core/device.h"
#include "core/partitionnode.h"

#include "jobs/createpartitionjob.h"
#include "jobs/createfilesystemjob.h"
#include "jobs/setfilesystemlabeljob.h"
#include "jobs/setpartflagsjob.h"
#include "jobs/checkfilesystemjob.h"

#include "fs/filesystem.h"
#include "fs/filesystemfactory.h"

#include "util/capacity.h"

#include <QString>

#include <KLocalizedString>

/** Creates a new NewOperation.
    @param d the Device to create a new Partition on
    @param p pointer to the new Partition to create. May not be nullptr.
*/
NewOperation::NewOperation(Device& d, Partition* p) :
    Operation(),
    m_TargetDevice(d),
    m_NewPartition(p),
    m_CreatePartitionJob(new CreatePartitionJob(targetDevice(), newPartition())),
    m_CreateFileSystemJob(nullptr),
    m_SetPartFlagsJob(nullptr),
    m_SetFileSystemLabelJob(nullptr),
    m_CheckFileSystemJob(nullptr)
{
    addJob(createPartitionJob());

    const FileSystem& fs = newPartition().fileSystem();

    if (fs.type() != FileSystem::Extended) {
        // It would seem tempting to skip the CreateFileSystemJob or the
        // SetFileSystemLabelJob if either has nothing to do (unformatted FS or
        // empty label). However, the user might later on decide to change FS or
        // label. The operation stack will merge these operations with this one here
        // and if the jobs don't exist things will break.

        m_CreateFileSystemJob = new CreateFileSystemJob(targetDevice(), newPartition(), fs.label());
        addJob(createFileSystemJob());

        if (fs.type() == FileSystem::Lvm2_PV) {
            m_SetPartFlagsJob = new SetPartFlagsJob(targetDevice(), newPartition(), PartitionTable::FlagLvm);
            addJob(setPartFlagsJob());
        }

        m_SetFileSystemLabelJob = new SetFileSystemLabelJob(newPartition(), fs.label());
        addJob(setLabelJob());

        m_CheckFileSystemJob = new CheckFileSystemJob(newPartition());
        addJob(checkJob());
    }
}

NewOperation::~NewOperation()
{
    if (status() == StatusPending)
        delete m_NewPartition;
}

bool NewOperation::targets(const Device& d) const
{
    return d == targetDevice();
}

bool NewOperation::targets(const Partition& p) const
{
    return p == newPartition();
}

void NewOperation::preview()
{
    insertPreviewPartition(targetDevice(), newPartition());
}

void NewOperation::undo()
{
    removePreviewPartition(targetDevice(), newPartition());
}

QString NewOperation::description() const
{
    return xi18nc("@info:status", "Create a new partition (%1, %2) on <filename>%3</filename>", Capacity::formatByteSize(newPartition().capacity()), newPartition().fileSystem().name(), targetDevice().deviceNode());
}

/** Can a Partition be created somewhere?
    @param p the Partition where a new Partition is to be created, may be nullptr
    @return true if a new Partition can be created in @p p
 */
bool NewOperation::canCreateNew(const Partition* p)
{
    return p != nullptr && p->roles().has(PartitionRole::Unallocated);
}

Partition* NewOperation::createNew(const Partition& cloneFrom,
                                   FileSystem::Type type)
{
    Partition* p = new Partition(cloneFrom);

    p->deleteFileSystem();
    p->setFileSystem(FileSystemFactory::create(type,
                     p->firstSector(),
                     p->lastSector(),
                     p->sectorSize()));
    p->setState(Partition::StateNew);
    p->setPartitionPath(QString());

    return p;
}
