#ifndef _KERNEL_TASKING_THREAD_H
#define _KERNEL_TASKING_THREAD_H

#include <fs/vfs.h>
#include <libkern/lock.h>
#include <libkern/types.h>
#include <platform/generic/fpu/fpu.h>
#include <platform/generic/tasking/context.h>
#include <platform/generic/tasking/trapframe.h>
#include <tasking/signal.h>
#include <time/time_manager.h>

enum THREAD_STATUS {
    THREAD_STATUS_INVALID = 0,
    THREAD_STATUS_ALLOCATED,
    THREAD_STATUS_RUNNING,
    THREAD_STATUS_STOPPED,
    THREAD_STATUS_BLOCKED,
    THREAD_STATUS_DYING,
};

struct thread;
struct blocker {
    int reason;
    bool (*should_unblock)(struct thread* thread);
    bool should_unblock_for_signal;
};
typedef struct blocker blocker_t;

enum BLOCKER_REASON {
    BLOCKER_INVALID,
    BLOCKER_JOIN,
    BLOCKER_READ,
    BLOCKER_WRITE,
    BLOCKER_SLEEP,
    BLOCKER_SELECT,
    BLOCKER_DUMPING,
    BLOCKER_STOP, // Just waiting for signal which will continue the thread.
};

struct blocker_join {
    struct thread* joinee;
    int join_pid;
};
typedef struct blocker_join blocker_join_t;

struct blocker_rw {
    file_descriptor_t* fd;
};
typedef struct blocker_rw blocker_rw_t;

struct blocker_sleep {
    timespec_t until;
};
typedef struct blocker_sleep blocker_sleep_t;

struct blocker_select {
    int nfds;
    fd_set_t readfds;
    fd_set_t writefds;
    fd_set_t exceptfds;

    bool is_until_time_set;
    timespec_t until;
};
typedef struct blocker_select blocker_select_t;

struct proc;
struct thread {
    struct proc* process;
    pid_t tid;
    uint32_t status;

    /* Kernel data */
    kmemzone_t kstack;
    context_t* context; // context of kernel's registers
    trapframe_t* tf;
#ifdef FPU_ENABLED
    fpu_state_t* fpu_state;
#endif

    /* Scheduler data */
    struct thread* sched_prev;
    struct thread* sched_next;
    int last_cpu;
    time_t ticks_until_preemption;
    time_t start_time_in_ticks; // Time when the task was put to run.

    /* Blocker data */
    blocker_t blocker;
    union {
        blocker_join_t join;
        blocker_rw_t rw;
        blocker_sleep_t sleep;
        blocker_select_t select;
    } blocker_data;

    /* Stat data */
    time_t stat_total_running_ticks;

    uint32_t signals_mask;
    uint32_t pending_signals_mask;
    void* signal_handlers[SIGNALS_CNT];
};
typedef struct thread thread_t;

#define THREADS_PER_NODE (128)
struct thread_list_node {
    thread_t thread_storage[THREADS_PER_NODE];
    struct thread_list_node* next;
    int empty_spots;
};
typedef struct thread_list_node thread_list_node_t;

struct thread_list {
    struct thread_list_node* head;
    struct thread_list_node* next_empty_node;
    int next_empty_index;
    spinlock_t lock;
    struct thread_list_node* tail;
};
typedef struct thread_list thread_list_t;

/**
 * THREAD FUNCTIONS
 */

int thread_setup_main(struct proc* p, thread_t* thread);
int thread_setup(struct proc* p, thread_t* thread);
int thread_setup_kstack(thread_t* thread);
int thread_copy_of(thread_t* thread, thread_t* from_thread);

int thread_fill_up_stack(thread_t* thread, int argc, char** argv, int envc, char** envp);

int thread_kstack_free(thread_t* thread);
int thread_free(thread_t* thread);
int thread_die(thread_t* thread);
int thread_zombie(thread_t* thread);
int thread_stop(thread_t* thread);
int thread_stop_and_resched(thread_t* thread);
int thread_continue(thread_t* thread);

static ALWAYS_INLINE int thread_is_freed(thread_t* thread) { return (thread->status == THREAD_STATUS_INVALID); }
static ALWAYS_INLINE int thread_is_alive(thread_t* thread) { return !thread_is_freed(thread); }

/**
 * BLOCKER FUNCTIONS
 */

int thread_init_blocker(thread_t* thread, const struct blocker* blocker);

int init_join_blocker(thread_t* thread, int wait_for_pid);
int init_read_blocker(thread_t* thread, file_descriptor_t* bfd);
int init_write_blocker(thread_t* thread, file_descriptor_t* bfd);
int init_sleep_blocker(thread_t* thread, timespec_t ts);
int init_select_blocker(thread_t* thread, int nfds, fd_set_t* readfds, fd_set_t* writefds, fd_set_t* exceptfds, timeval_t* timeout);

/**
 * DEBUG FUNCTIONS
 */

int thread_dump_frame(thread_t* thread);
int thread_print_backtrace();

#endif /* _KERNEL_TASKING_THREAD_H */