#ifndef THREAD_TOOL_H
#define THREAD_TOOL_H

#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>
#include <threads.h>

// The maximum number of threads.
#define THREAD_MAX 100
#define HANDLER 1
#define SCHEDULER 2
#define READ_LOCK 3
#define WRITE_LOCK 4
#define THREAD_SLEEP 5
#define THREAD_EXIT 6

void sighandler(int signum);
void scheduler();

// The thread control block structure.
struct tcb {
    int id;
    int *args;
    // Reveals what resource the thread is waiting for. The values are:
    //  - 0: no resource.
    //  - 1: read lock.
    //  - 2: write lock.
    int waiting_for;
    int sleeping_time;
    jmp_buf env;  // Where the scheduler should jump to.
    int n, i, f_cur, f_prev;
    int p_n, p_i, p_cur;
    int dp, ds, s, b, cur_qp, cur_qs; // enroll vars
};

// The only one thread in the RUNNING state.
extern struct tcb *current_thread;
extern struct tcb *idle_thread;

struct tcb_queue {
    struct tcb *arr[THREAD_MAX];  // The circular array.
    int head;                     // The index of the head of the queue
    int size;
};

extern struct tcb_queue ready_queue, waiting_queue;


// The rwlock structure.
//
// When a thread acquires a type of lock, it should increment the corresponding count.
struct rwlock {
    int read_count;
    int write_count;
};

extern struct rwlock rwlock;

// The remaining spots in classes.
extern int q_p, q_s;

// The maximum running time for each thread.
extern int time_slice;

// The long jump buffer for the scheduler.
extern jmp_buf sched_buf;

// TODO::
// You should setup your own sleeping set as well as finish the marcos below

extern struct tcb *sleeping_set[THREAD_MAX];
extern void perror_exit(const char *msg);

extern void queue_enq(struct tcb_queue* self, struct tcb* element);
extern struct tcb* queue_deq(struct tcb_queue* self);

#ifdef DEBUG
#define debug_printf(...) fprintf(stderr, __VA_ARGS__)
#else // DEBUG
#define debug_printf(...) fprintf(stderr, NULL)
#endif

#define thread_create(func, t_id, t_args)                                              \
    ({                                                                                 \
     func(t_id, t_args); \
     })

#define thread_setup(t_id, t_args)                                                     \
    ({                                                                                 \
     printf("thread %d: set up routine %s\n", t_id, __func__);                          \
     struct tcb* new_tcb = (struct tcb*)malloc(sizeof(struct tcb));                     \
     new_tcb->id = t_id;                                                                \
     new_tcb->args = t_args;                                                            \
     if (setjmp(new_tcb->env) == 0) { /* TODO: setjmp or sigsetjmp? */                        \
        if (t_id == 0) {                                                                   \
           idle_thread = new_tcb;                                                          \
        } else {                                                                           \
           queue_enq(&ready_queue, new_tcb);\
        }                                                                                  \
        debug_printf("returning...\n"); \
        return;                                                                            \
     }                                                                                  \
     })

#define thread_yield()                                  \
    ({                                                  \
     if (setjmp(current_thread->env) == -1) { /* TODO: setjmp or sigsetjmp? */   \
        printf("setjmp error\n");               \
     }                                          \
     sigset_t tstp_mask; \
     if (sigemptyset(&tstp_mask) == -1) perror_exit("tstp_mask sigemptyset");                \
     if (sigaddset(&tstp_mask, SIGTSTP) == -1) perror_exit("tstp_mask sigaddset SIGTSTP");   \
                                                                                             \
     if (sigprocmask(SIG_UNBLOCK, &tstp_mask, NULL) == -1)                                   \
         perror_exit("sigprocmask SIG_UNBLOCK tstp");                                        \
     if (sigprocmask(SIG_BLOCK, &tstp_mask, NULL) == -1)                                     \
         perror_exit("sigprocmask SIG_BLOCK tstp");                                          \
     sigset_t alrm_mask;                                                                     \
     if (sigemptyset(&alrm_mask) == -1) perror_exit("alrm_mask sigemptyset");                \
     if (sigaddset(&alrm_mask, SIGALRM) == -1) perror_exit("alrm_mask sigaddset SIGALRM");   \
                                                                                             \
     if (sigprocmask(SIG_UNBLOCK, &alrm_mask, NULL) == -1)                                   \
         perror_exit("sigprocmask SIG_UNBLOCK alrm");                                        \
     if (sigprocmask(SIG_BLOCK, &alrm_mask, NULL) == -1)                                     \
         perror_exit("sigprocmask SIG_BLOCK alrm");                                          \
    })

#define read_lock()                                                      \
    ({                                                                   \
     if (setjmp(current_thread->env) == -1) perror_exit("setjmp read lock"); /* TODO: setjmp or sigsetjmp? */ \
     if (rwlock.write_count != 0) { \
        current_thread->waiting_for = 1; \
        siglongjmp(sched_buf, READ_LOCK); \
     } \
     current_thread->waiting_for = 0; \
     rwlock.read_count++; \
    })

#define write_lock()                                                     \
    ({                                                                   \
     if (setjmp(current_thread->env) == -1) perror_exit("setjmp write lock"); /* TODO: setjmp or sigsetjmp? */  \
     if (rwlock.write_count != 0 || rwlock.read_count != 0) { \
        current_thread->waiting_for = 2; \
        siglongjmp(sched_buf, WRITE_LOCK); \
     } \
     current_thread->waiting_for = 0; \
     rwlock.write_count++; \
    })

#define read_unlock()                                                                 \
    ({                                                                                \
     rwlock.read_count--; \
    })

#define write_unlock()                                                                \
    ({                                                                                \
     rwlock.write_count--; \
    })

#define thread_sleep(sec)                                            \
    ({                                                               \
     current_thread->sleeping_time = sec; \
     sleeping_set[current_thread->id] = current_thread; \
     if (setjmp(current_thread->env) == 0) \
         siglongjmp(sched_buf, THREAD_SLEEP); \
    })

#define thread_awake(t_id)                                                        \
    ({                                                                            \
     if (sleeping_set[t_id] != NULL) { \
        debug_printf("THREAD AWAKE: waking thread %d\n", t_id); \
        queue_enq(&ready_queue, sleeping_set[t_id]); \
        sleeping_set[t_id] = NULL; \
     } \
    })

#define thread_exit()                                    \
    ({                                                   \
     printf("thread %d: exit\n", current_thread->id); \
     siglongjmp(sched_buf, THREAD_EXIT); \
    })

#endif  // THREAD_TOOL_H
