#ifndef VIETER_JOB_QUEUE
#define VIETER_JOB_QUEUE

#include "vieter_cron.h"
#include <stdbool.h>
#include <stdint.h>

/*
 * The order of these do not imply that they happen in this order. New states
 * will just get added as consecutive numbers. Their values should be
 * monotonically increasing values, as these will be used to index arrays, among
 * other things.
 */
typedef enum vieter_job_state {
  vieter_job_state_queued = 0,
  vieter_job_state_ready = 1,
  vieter_job_state_build_finished = 2,
  vieter_job_state_failed = 3
} vieter_job_state;

// This macro should be kept in sync with the above enum
#define VIETER_JOB_STATES 4
// Neither of these states are allowed to be categorized
#define VIETER_JOB_INITIAL_STATE vieter_job_state_queued
#define VIETER_JOB_FAILURE_STATE vieter_job_state_failed
// Bitmap describing what states should be divided into multiple architectures
#define VIETER_JOB_STATES_ARCH 0b0010
#define VIETER_JOB_STATE_IS_ARCH(i) (VIETER_JOB_STATES_ARCH & (1 << i))

/*
 * Struct storing a report for why a certain job failed to be processed in the
 * given state.
 */
typedef struct vieter_job_failure_report {
  vieter_job_state failed_state;
  char *msg;
} vieter_job_failure_report;

vieter_job_failure_report *vieter_job_failure_report_init();

void vieter_job_failure_report_free(vieter_job_failure_report **ptp);

/*
 * Represents a job currently being processed in the system. A job migrates
 * between different states before finally being removed from the queue.
 */
typedef struct vieter_job {
  uint64_t id;
  uint64_t next_scheduled_time;
  vieter_cron_expression *schedule;
  void *build_config;
  char *arch;
  vieter_job_failure_report *failure_report;
  uint64_t state_transition_times[VIETER_JOB_STATES];
  vieter_job_state current_state;
  bool single;
  bool dispatched;
} vieter_job;

/*
 * Allocate a new vieter_job object.
 */
vieter_job *vieter_job_init();

void vieter_job_free(vieter_job **ptp);

/*
 * Represents the actual queue managing the list of jobs.
 */
typedef struct vieter_job_queue vieter_job_queue;

typedef enum vieter_job_queue_error {
  vieter_job_queue_ok = 0,
  vieter_job_queue_not_present = 1,
  vieter_job_queue_already_present = 2,
  vieter_job_queue_state_empty = 3,
  vieter_job_queue_not_dispatched = 4,
  vieter_job_queue_state_is_arch = 5,
  vieter_job_queue_state_is_not_arch = 6,
} vieter_job_queue_error;

/*
 * Allocate and initialize a new job queue.
 */
vieter_job_queue *vieter_job_queue_init();

/*
 * Free a job queue.
 */
void vieter_job_queue_free(vieter_job_queue **ptp);

/*
 * Insert the given job into the system.
 */
vieter_job_queue_error vieter_job_queue_insert(vieter_job_queue *queue,
                                               vieter_job *job);

/*
 * Pop a job from the given non-categorized state's queue. The job will then be
 * marked as dispatched.
 */
vieter_job_queue_error vieter_job_queue_pop(vieter_job **out,
                                            vieter_job_queue *queue,
                                            vieter_job_state state);

/*
 * Pop a job from the given categorized state's queue. The job will then be
 * marked as dispatched.
 */
vieter_job_queue_error vieter_job_queue_pop_arch(vieter_job **out,
                                                 vieter_job_queue *queue,
                                                 vieter_job_state state,
                                                 char *arch);

/*
 * Transition the job with the given id to the new state. This sets the
 * job's dispatch flag to false, and adds it to the new state's queue.
 *
 * NOTE: this can only be done with dispatched jobs.
 */
vieter_job_queue_error vieter_job_queue_transition(vieter_job_queue *queue,
                                                   uint64_t id,
                                                   vieter_job_state new_state);

/*
 * Remove the given job from the job queue, returning its pointer to the caller.
 *
 * NOTE: this can only be done with dispatched jobs.
 */
vieter_job_queue_error
vieter_job_queue_remove(vieter_job **out, vieter_job_queue *queue, uint64_t id);

/*
 * Transition a job into the failure state, and attach a failure report with the
 * provided message. The message is copied, so the caller is responsible for
 * freeing the provided string.
 *
 * NOTE: this can only be done with dispatched jobs.
 */
vieter_job_queue_error vieter_job_queue_fail(vieter_job_queue *queue,
                                             uint64_t id, char *report_message);
/*
 * Acquire a read lock on the job queue. Return value is the result of
 * pthread_rwlock_rdlock.
 */
int vieter_job_queue_rlock(vieter_job_queue *job_queue);

/*
 * Acquire a write lock on the job queue. Return value is the result of
 * pthread_rwlock_wrlock.
 */
int vieter_job_queue_wlock(vieter_job_queue *job_queue);

/*
 * Unlock the lock after having acquired it. Return value is the result of
 * pthread_rwlock_unlock.
 */
int vieter_job_queue_unlock(vieter_job_queue *job_queue);

#endif