// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <functional>
#include <iosfwd>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/vfs/vfs.h"

namespace Core {
class System;
}

namespace FileSys {
class NACP;
} // namespace FileSys

namespace Kernel {
struct AddressMapping;
class KProcess;
} // namespace Kernel

namespace Loader {

/// File types supported by CTR
enum class FileType {
    Error,
    Unknown,
    NSO,
    NRO,
    NCA,
    NSP,
    XCI,
    NAX,
    KIP,
    DeconstructedRomDirectory,
};

/**
 * Identifies the type of a bootable file based on the magic value in its header.
 * @param file open file
 * @return FileType of file
 */
FileType IdentifyFile(FileSys::VirtualFile file);

/**
 * Guess the type of a bootable file from its name
 * @param name String name of bootable file
 * @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine
 * a filetype, and will never return FileType::Error.
 */
FileType GuessFromFilename(const std::string& name);

/**
 * Convert a FileType into a string which can be displayed to the user.
 */
std::string GetFileTypeString(FileType type);

/// Return type for functions in Loader namespace
enum class ResultStatus : u16 {
    Success,
    ErrorAlreadyLoaded,
    ErrorNotImplemented,
    ErrorNotInitialized,
    ErrorBadNPDMHeader,
    ErrorBadACIDHeader,
    ErrorBadACIHeader,
    ErrorBadFileAccessControl,
    ErrorBadFileAccessHeader,
    ErrorBadKernelCapabilityDescriptors,
    ErrorBadPFSHeader,
    ErrorIncorrectPFSFileSize,
    ErrorBadNCAHeader,
    ErrorMissingProductionKeyFile,
    ErrorMissingHeaderKey,
    ErrorIncorrectHeaderKey,
    ErrorNCA2,
    ErrorNCA0,
    ErrorMissingTitlekey,
    ErrorMissingTitlekek,
    ErrorInvalidRightsID,
    ErrorMissingKeyAreaKey,
    ErrorIncorrectKeyAreaKey,
    ErrorIncorrectTitlekeyOrTitlekek,
    ErrorXCIMissingProgramNCA,
    ErrorNCANotProgram,
    ErrorNoExeFS,
    ErrorBadXCIHeader,
    ErrorXCIMissingPartition,
    ErrorNullFile,
    ErrorMissingNPDM,
    Error32BitISA,
    ErrorUnableToParseKernelMetadata,
    ErrorNoRomFS,
    ErrorIncorrectELFFileSize,
    ErrorLoadingNRO,
    ErrorLoadingNSO,
    ErrorNoIcon,
    ErrorNoControl,
    ErrorBadNAXHeader,
    ErrorIncorrectNAXFileSize,
    ErrorNAXKeyHMACFailed,
    ErrorNAXValidationHMACFailed,
    ErrorNAXKeyDerivationFailed,
    ErrorNAXInconvertibleToNCA,
    ErrorBadNAXFilePath,
    ErrorMissingSDSeed,
    ErrorMissingSDKEKSource,
    ErrorMissingAESKEKGenerationSource,
    ErrorMissingAESKeyGenerationSource,
    ErrorMissingSDSaveKeySource,
    ErrorMissingSDNCAKeySource,
    ErrorNSPMissingProgramNCA,
    ErrorBadBKTRHeader,
    ErrorBKTRSubsectionNotAfterRelocation,
    ErrorBKTRSubsectionNotAtEnd,
    ErrorBadRelocationBlock,
    ErrorBadSubsectionBlock,
    ErrorBadRelocationBuckets,
    ErrorBadSubsectionBuckets,
    ErrorMissingBKTRBaseRomFS,
    ErrorNoPackedUpdate,
    ErrorBadKIPHeader,
    ErrorBLZDecompressionFailed,
    ErrorBadINIHeader,
    ErrorINITooManyKIPs,
    ErrorIntegrityVerificationNotImplemented,
    ErrorIntegrityVerificationFailed,
};

std::string GetResultStatusString(ResultStatus status);
std::ostream& operator<<(std::ostream& os, ResultStatus status);

/// Interface for loading an application
class AppLoader {
public:
    CITRON_NON_COPYABLE(AppLoader);
    CITRON_NON_MOVEABLE(AppLoader);

    struct LoadParameters {
        s32 main_thread_priority;
        u64 main_thread_stack_size;
    };
    using LoadResult = std::pair<ResultStatus, std::optional<LoadParameters>>;

    explicit AppLoader(FileSys::VirtualFile file_);
    virtual ~AppLoader();

    /**
     * Returns the type of this file
     *
     * @return FileType corresponding to the loaded file
     */
    virtual FileType GetFileType() const = 0;

    /**
     * Load the application and return the created Process instance
     *
     * @param process The newly created process.
     * @param system  The system that this process is being loaded under.
     *
     * @return The status result of the operation.
     */
    virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0;

    /**
     * Try to verify the integrity of the file.
     */
    virtual ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
        return ResultStatus::ErrorIntegrityVerificationNotImplemented;
    }

    /**
     * Get the code (typically .code section) of the application
     *
     * @param[out] buffer Reference to buffer to store data
     *
     * @return ResultStatus result of function
     */
    virtual ResultStatus ReadCode(std::vector<u8>& buffer) {
        return ResultStatus::ErrorNotImplemented;
    }

    /**
     * Get the icon (typically icon section) of the application
     *
     * @param[out] buffer Reference to buffer to store data
     *
     * @return ResultStatus result of function
     */
    virtual ResultStatus ReadIcon(std::vector<u8>& buffer) {
        return ResultStatus::ErrorNotImplemented;
    }

    /**
     * Get the banner (typically banner section) of the application
     * In the context of NX, this is the animation that displays in the bottom right of the screen
     * when a game boots. Stored in GIF format.
     *
     * @param[out] buffer Reference to buffer to store data
     *
     * @return ResultStatus result of function
     */
    virtual ResultStatus ReadBanner(std::vector<u8>& buffer) {
        return ResultStatus::ErrorNotImplemented;
    }

    /**
     * Get the logo (typically logo section) of the application
     * In the context of NX, this is the static image that displays in the top left of the screen
     * when a game boots. Stored in JPEG format.
     *
     * @param[out] buffer Reference to buffer to store data
     *
     * @return ResultStatus result of function
     */
    virtual ResultStatus ReadLogo(std::vector<u8>& buffer) {
        return ResultStatus::ErrorNotImplemented;
    }

    /**
     * Get the program id of the application
     *
     * @param[out] out_program_id Reference to store program id into
     *
     * @return ResultStatus result of function
     */
    virtual ResultStatus ReadProgramId(u64& out_program_id) {
        return ResultStatus::ErrorNotImplemented;
    }

    /**
     * Get the program ids of the application
     *
     * @param[out] out_program_ids Reference to store program ids into
     *
     * @return ResultStatus result of function
     */
    virtual ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) {
        return ResultStatus::ErrorNotImplemented;
    }

    /**
     * Get the RomFS of the application
     * Since the RomFS can be huge, we return a file reference instead of copying to a buffer
     *
     * @param[out] out_file The directory containing the RomFS
     *
     * @return ResultStatus result of function
     */
    virtual ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) {
        return ResultStatus::ErrorNotImplemented;
    }

    /**
     * Get the raw update of the application, should it come packed with one
     *
     * @param[out] out_file The raw update NCA file (Program-type)
     *
     * @return ResultStatus result of function
     */
    virtual ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) {
        return ResultStatus::ErrorNotImplemented;
    }

    /**
     * Get whether or not updates can be applied to the RomFS.
     * By default, this is true, however for formats where it cannot be guaranteed that the RomFS is
     * the base game it should be set to false.
     *
     * @return bool indicating whether or not the RomFS is updatable.
     */
    virtual bool IsRomFSUpdatable() const {
        return true;
    }

    /**
     * Get the title of the application
     *
     * @param[out] title Reference to store the application title into
     *
     * @return ResultStatus result of function
     */
    virtual ResultStatus ReadTitle(std::string& title) {
        return ResultStatus::ErrorNotImplemented;
    }

    /**
     * Get the control data (CNMT) of the application
     *
     * @param[out] control Reference to store the application control data into
     *
     * @return ResultStatus result of function
     */
    virtual ResultStatus ReadControlData(FileSys::NACP& control) {
        return ResultStatus::ErrorNotImplemented;
    }

    /**
     * Get the RomFS of the manual of the application
     *
     * @param[out] out_file The raw manual RomFS of the game
     *
     * @return ResultStatus result of function
     */
    virtual ResultStatus ReadManualRomFS(FileSys::VirtualFile& out_file) {
        return ResultStatus::ErrorNotImplemented;
    }

    using Modules = std::map<VAddr, std::string>;

    virtual ResultStatus ReadNSOModules(Modules& modules) {
        return ResultStatus::ErrorNotImplemented;
    }

protected:
    FileSys::VirtualFile file;
    bool is_loaded = false;
};

/**
 * Identifies a bootable file and return a suitable loader
 *
 * @param system The system context.
 * @param file   The bootable file.
 * @param program_index Specifies the index within the container of the program to launch.
 *
 * @return the best loader for this file.
 */
std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file,
                                     u64 program_id = 0, std::size_t program_index = 0);

} // namespace Loader
