/*
 * Copyright (c) Atmosphère-NX
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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 <stratosphere.hpp>

namespace ams::ncm {

    PackageSystemUpdateTask::~PackageSystemUpdateTask() {
        if (m_context_path.GetLength() > 0) {
            fs::DeleteFile(m_context_path);
        }
        this->Inactivate();
    }

    void PackageSystemUpdateTask::Inactivate() {
        if (m_gamecard_content_meta_database_active) {
            InactivateContentMetaDatabase(StorageId::GameCard);
            m_gamecard_content_meta_database_active = false;
        }
    }

    Result PackageSystemUpdateTask::Initialize(const char *package_root, const char *context_path, void *buffer, size_t buffer_size, bool requires_exfat_driver, FirmwareVariationId firmware_variation_id) {
        /* Set the firmware variation id. */
        this->SetFirmwareVariationId(firmware_variation_id);

        /* Activate the game card content meta database. */
        R_TRY(ActivateContentMetaDatabase(StorageId::GameCard));
        m_gamecard_content_meta_database_active = true;
        auto meta_db_guard = SCOPE_GUARD { this->Inactivate(); };

        /* Open the game card content meta database. */
        OpenContentMetaDatabase(std::addressof(m_package_db), StorageId::GameCard);

        ContentMetaDatabaseBuilder builder(std::addressof(m_package_db));

        /* Cleanup and build the content meta database. */
        R_TRY(builder.Cleanup());
        R_TRY(builder.BuildFromPackage(package_root));

        /* Create a new context file. */
        fs::DeleteFile(context_path);
        R_TRY(FileInstallTaskData::Create(context_path, GameCardMaxContentMetaCount));
        auto context_guard = SCOPE_GUARD { fs::DeleteFile(context_path); };

        /* Initialize data. */
        R_TRY(m_data.Initialize(context_path));

        /* Initialize PackageInstallTaskBase. */
        u32 config = !requires_exfat_driver ? InstallConfig_SystemUpdate : InstallConfig_SystemUpdate | InstallConfig_RequiresExFatDriver;
        R_TRY(PackageInstallTaskBase::Initialize(package_root, buffer, buffer_size, StorageId::BuiltInSystem, std::addressof(m_data), config));

        /* Cancel guards. */
        context_guard.Cancel();
        meta_db_guard.Cancel();

        /* Set the context path. */
        m_context_path.Assign(context_path);
        R_SUCCEED();
    }

    util::optional<ContentMetaKey> PackageSystemUpdateTask::GetSystemUpdateMetaKey() {
        StorageContentMetaKey storage_keys[0x10];
        s32 ofs = 0;

        s32 count = 0;
        do {
            /* List content meta keys. */
            if (R_FAILED(this->ListContentMetaKey(std::addressof(count), storage_keys, util::size(storage_keys), ofs, ListContentMetaKeyFilter::All))) {
                break;
            }

            /* Add listed keys to the offset. */
            ofs += count;

            /* Check if any of these keys are for a SystemUpdate. */
            for (s32 i = 0; i < count; i++) {
                const ContentMetaKey &key = storage_keys[i].key;
                if (key.type == ContentMetaType::SystemUpdate) {
                    return key;
                }
            }
        } while (count > 0);

        return util::nullopt;
    }

    Result PackageSystemUpdateTask::GetInstallContentMetaInfo(InstallContentMetaInfo *out, const ContentMetaKey &key) {
        /* Get the content info for the key. */
        ContentInfo info;
        R_TRY(this->GetContentInfoOfContentMeta(std::addressof(info), key));

        /* Create a new install content meta info. */
        *out = InstallContentMetaInfo::MakeUnverifiable(info.GetId(), info.GetSize(), key);
        R_SUCCEED();
    }

    Result PackageSystemUpdateTask::PrepareInstallContentMetaData() {
        /* Obtain a SystemUpdate key. */
        ContentMetaKey key;
        auto list_count = m_package_db.ListContentMeta(std::addressof(key), 1, ContentMetaType::SystemUpdate);
        R_UNLESS(list_count.written > 0, ncm::ResultSystemUpdateNotFoundInPackage());

        /* Get the content info for the key. */
        ContentInfo info;
        R_TRY(this->GetContentInfoOfContentMeta(std::addressof(info), key));

        /* Prepare the content meta. */
        R_RETURN(this->PrepareContentMeta(InstallContentMetaInfo::MakeUnverifiable(info.GetId(), info.GetSize(), key), key, util::nullopt));
    }

    Result PackageSystemUpdateTask::PrepareDependency() {
        R_RETURN(this->PrepareSystemUpdateDependency());
    }

    Result PackageSystemUpdateTask::GetContentInfoOfContentMeta(ContentInfo *out, const ContentMetaKey &key) {
        s32 ofs = 0;
        while (true) {
            /* List content infos. */
            s32 count;
            ContentInfo info;
            R_TRY(m_package_db.ListContentInfo(std::addressof(count), std::addressof(info), 1, key, ofs++));

            /* No content infos left to list. */
            if (count == 0) {
                break;
            }

            /* Check if the info is for meta content. */
            if (info.GetType() == ContentType::Meta) {
                *out = info;
                R_SUCCEED();
            }
        }

        /* Not found. */
        R_THROW(ncm::ResultContentInfoNotFound());
    }

}
