
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only

/*
 *  Prism Launcher - Minecraft Launcher
 *  Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
 *
 *  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, version 3.
 *
 *  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 <https://www.gnu.org/licenses/>.
 */

#include "LocalWorldSaveParseTask.h"

#include "FileSystem.h"
#include "archive/ArchiveReader.h"

#include <QDir>
#include <QFileInfo>
#include <tuple>

namespace WorldSaveUtils {

bool process(WorldSave& pack, ProcessingLevel level)
{
    switch (pack.type()) {
        case ResourceType::FOLDER:
            return WorldSaveUtils::processFolder(pack, level);
        case ResourceType::ZIPFILE:
            return WorldSaveUtils::processZIP(pack, level);
        default:
            qWarning() << "Invalid type for world save parse task!";
            return false;
    }
}

/// @brief checks a folder structure to see if it contains a level.dat
/// @param dir the path to check
/// @param saves used in recursive call if a "saves" dir was found
/// @return std::tuple of (
///             bool <found level.dat>,
///             QString <name of folder containing level.dat>,
///             bool <saves folder found>
///         )
static std::tuple<bool, QString, bool> contains_level_dat(QDir dir, bool saves = false)
{
    for (auto const& entry : dir.entryInfoList()) {
        if (!entry.isDir()) {
            continue;
        }
        if (!saves && entry.fileName() == "saves") {
            return contains_level_dat(QDir(entry.filePath()), true);
        }
        QFileInfo level_dat(FS::PathCombine(entry.filePath(), "level.dat"));
        if (level_dat.exists() && level_dat.isFile()) {
            return std::make_tuple(true, entry.fileName(), saves);
        }
    }
    return std::make_tuple(false, "", saves);
}

bool processFolder(WorldSave& save, ProcessingLevel level)
{
    Q_ASSERT(save.type() == ResourceType::FOLDER);

    auto [found, save_dir_name, found_saves_dir] = contains_level_dat(QDir(save.fileinfo().filePath()));

    if (!found) {
        return false;
    }

    save.setSaveDirName(save_dir_name);

    if (found_saves_dir) {
        save.setSaveFormat(WorldSaveFormat::MULTI);
    } else {
        save.setSaveFormat(WorldSaveFormat::SINGLE);
    }

    if (level == ProcessingLevel::BasicInfoOnly) {
        return true;  // only need basic info already checked
    }

    // reserved for more intensive processing

    return true;  // all tests passed
}

/// @brief checks a folder structure to see if it contains a level.dat
/// @param zip the zip file to check
/// @return std::tuple of (
///             bool <found level.dat>,
///             QString <name of folder containing level.dat>,
///             bool <saves folder found>
///         )
static std::tuple<bool, QString, bool> contains_level_dat(QString fileName)
{
    MMCZip::ArchiveReader zip(fileName);
    if (!zip.collectFiles()) {
        return std::make_tuple(false, "", false);
    }
    bool saves = false;
    if (zip.exists("/saves")) {
        saves = true;
    }

    for (auto file : zip.getFiles()) {
        QString relativePath = file;
        if (saves) {
            if (!relativePath.startsWith("saves/", Qt::CaseInsensitive))
                continue;
            relativePath = relativePath.mid(QString("saves/").length());
        }
        if (!relativePath.endsWith("/level.dat", Qt::CaseInsensitive))
            continue;

        int slashIndex = relativePath.indexOf('/');
        if (slashIndex == -1)
            continue;  // malformed: no slash between saves/ and level.dat

        QString worldName = relativePath.left(slashIndex);
        QString remaining = relativePath.mid(slashIndex + 1);

        // Check that there's nothing between worldName/ and level.dat
        if (remaining == "level.dat") {
            return std::make_tuple(true, worldName, saves);
        }
    }

    return std::make_tuple(false, "", saves);
}

bool processZIP(WorldSave& save, ProcessingLevel level)
{
    Q_ASSERT(save.type() == ResourceType::ZIPFILE);

    auto [found, save_dir_name, found_saves_dir] = contains_level_dat(save.fileinfo().filePath());

    if (!found) {
        return false;
    }
    if (save_dir_name.endsWith("/")) {
        save_dir_name.chop(1);
    }

    save.setSaveDirName(save_dir_name);

    if (found_saves_dir) {
        save.setSaveFormat(WorldSaveFormat::MULTI);
    } else {
        save.setSaveFormat(WorldSaveFormat::SINGLE);
    }

    if (level == ProcessingLevel::BasicInfoOnly) {
        return true;  // only need basic info already checked
    }

    // reserved for more intensive processing

    return true;
}

bool validate(QFileInfo file)
{
    WorldSave sp{ file };
    return WorldSaveUtils::process(sp, ProcessingLevel::BasicInfoOnly) && sp.valid();
}

}  // namespace WorldSaveUtils

LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(false), m_token(token), m_save(save) {}

bool LocalWorldSaveParseTask::abort()
{
    m_aborted = true;
    return true;
}

void LocalWorldSaveParseTask::executeTask()
{
    if (!WorldSaveUtils::process(m_save)) {
        emitFailed("this is not a world");
        return;
    }

    if (m_aborted)
        emitAborted();
    else
        emitSucceeded();
}
