#pragma once
#include "fs/VFS.h"
#include "memory/AddressSpace.h"

class BinaryFormatLoader : public Shareable
{
  public:
    /**
     * @brief Determine if the given executable file matches this binary format.
     *
     * @return Result<bool> An error, or whether the file matches the binary format.
     */
    virtual Result<bool> sniff() = 0;

    /**
     * @brief Load the given executable binary file into an address space.
     *
     * Depending on the binary format, this function may load an arbitrary interpreter instead, which will interpret the
     * file on its own.
     *
     * @param space The address space to load the executable into.
     * @return Result<u64> An error, or the entry point of the executable in memory.
     */
    virtual Result<u64> load(AddressSpace* space) = 0;

    /**
     * @brief Return the short name associated with this format.
     *
     * @return StringView The format's name.
     */
    virtual StringView format() const = 0;

    /**
     * @brief Transform an interpreted program's command line arguments.
     *
     * Example: A script 'foo.sh' with a shebang line '#!/bin/sh' is loaded using a BinaryFormatLoader.
     *
     * This function will then be called with path="/path/to/foo.sh" and args={"foo.sh", "--enable-bar"}
     *
     * The function should return {"/bin/sh", "/path/to/foo.sh", "--enable-bar"} (prepending the interpreter command
     * line and substituting args[0] for the full path of the script).
     *
     * For native executable formats that do not require an interpreter (e.g. ELF), this function should just ignore
     * path and return args unmodified.
     *
     * @param path The path (absolute or relative to the current process's working directory) of the current program
     * file. This should be used instead of args[0] as arbitrary values can be passed there, leaving the interpreter
     * unable to find the target program.
     * @param args The original command line arguments passed to execve().
     * @return Result<Vector<String>> An error, or the transformed command line arguments.
     */
    virtual Result<Vector<String>> cmdline(const String& path, Vector<String> args) = 0;

    virtual ~BinaryFormatLoader() = default;

    /**
     * @brief Construct a new BinaryFormatLoader.
     *
     * This should not be directly used, instead each subclass of BinaryFormatLoader should implement a static create()
     * method which implements the binfmt_loader_creator_t type and register it using
     * BinaryFormat::register_binary_format().
     *
     * Then, anyone that needs an appropriate BinaryFormatLoader for an executable file should call
     * BinaryFormat::create_loader(), which will find an appropriate loader out of all default/registered loaders.
     *
     * @param inode The executable program file to load into memory.
     * @param recursion_level In normal cases, 0. If the BinaryFormatLoader is created inside another loader (for
     * example, a script loader loading the script's interpreter), the caller shall pass its recursion_level + 1 to
     * BinaryFormat::create_loader(), which will forward it to this constructor. This avoids infinite recursion.
     */
    BinaryFormatLoader(SharedPtr<VFS::Inode> inode, int recursion_level);

  protected:
    SharedPtr<VFS::Inode> m_inode;
    int m_recursion_level;
};

/**
 * @brief The factory function signature for binary format loaders.
 */
typedef Result<SharedPtr<BinaryFormatLoader>> (*binfmt_loader_creator_t)(SharedPtr<VFS::Inode>, void*, int);

namespace BinaryFormat
{
    /**
     * @brief Register the default binary format loaders.
     *
     * @return Result<void> Whether the operation succeeded.
     */
    Result<void> init();

    /**
     * @brief Register a new binary format loader type.
     *
     * @param creator A factory function to create said binary format loader. The function shall be passed the inode to
     * load the program from, the arbitrary argument passed to this function, and a recursion index to avoid infinite
     * recursion.
     * @param arg An arbitrary argument that will be passed to the above factory function.
     * @return Result<void> Whether the operation succeeded.
     */
    Result<void> register_binary_format(binfmt_loader_creator_t creator, void* arg);

    /**
     * @brief Create an appropriate loader object for an executable file.
     *
     * @param inode The executable file to create the loader for. If no appropriate loader could be found for this file
     * type, this function shall return ENOEXEC.
     * @param recursion_level In most cases, 0. If called inside another loader, the loader shall pass its
     * own recursion_level variable + 1, to avoid infinite recursion. If recursion_level >= 8, this function shall
     * immediately return ELOOP.
     * @return Result<SharedPtr<BinaryFormatLoader>> An error, or the new loader object created for this file.
     */
    Result<SharedPtr<BinaryFormatLoader>> create_loader(SharedPtr<VFS::Inode> inode, int recursion_level = 0);
}