#pragma once
#include "fs/FileDescriptor.h"
#include "interrupts/Context.h"
#include "memory/AddressSpace.h"
#include "memory/UserHeap.h"
#include "sys/elf/Image.h"
#include <sys/types.h>

#define TASK_MAX_FDS 32

enum class BlockReason
{
    None,
    Reading,
    Waiting,
};

// FIXME: To make this struct more C++-styled, maybe we could make a lot of these variables private and add
// getters/setters?

struct Task
{
    enum TaskState
    {
        Idle,
        Running,
        Sleeping,
        Dying,
        Blocking,
        Exited
    };

    uint64_t id;
    uint64_t ppid;
    Context regs;

    int64_t task_sleep = 0;

    int64_t exit_status;

    int64_t task_time = 0;

    uid_t uid;
    uid_t euid;
    gid_t gid;
    gid_t egid;

    Task* next_task = nullptr;
    Task* prev_task = nullptr;

    uint64_t allocated_stack = 0;

    TaskState state;

    uint64_t cpu_time = 0;

    char floating_region[512] __attribute__((aligned(16)));
    bool floating_saved = false;

    bool user_task = true;

    bool is_user_task();

    ELFImage* image = nullptr; // FIXME: we probably don't need to keep track of this anymore since the ELF sections are
                               // freed automatically when calling destroy() or clear() on the address space.

    Descriptor files[TASK_MAX_FDS];

    AddressSpace address_space;

    UserHeap allocator;

    int alloc_fd();

    int alloc_fd_greater_than_or_equal(int base_fd);

    void save_context(Context* context);
    void restore_context(Context* context);

    void save_floating();
    void restore_floating();

    void switch_to_address_space();

    bool has_died();

    char name[128];

    mode_t umask;

    BlockReason block_reason;

    union {
        struct
        {
            size_t size;
            int fd;
            char* buf;
        } blocking_read_info;
        struct
        {
            int64_t pid;
            int* wstatus;
        } blocking_wait_info;
    };

    void resume();

    bool is_still_blocking();

    // FIXME: These two functions are a bit clunky.
    Descriptor* open_descriptor_from_fd(int fd, int& error);
    Descriptor* descriptor_from_fd(int fd, int& error);

    bool is_superuser();

  private:
    void resume_read();
    void resume_wait();

    bool is_read_still_blocking();
    bool is_wait_still_blocking();
};