#include "devices/intq.h"
#include <debug.h>
#include "threads/thread.h"

static int next (int pos);
static void wait (struct intq *q, struct thread **waiter);
static void signal (struct intq *q, struct thread **waiter);

/* Initializes interrupt queue Q. */
void
intq_init (struct intq *q) 
{
  lock_init (&q->lock);
  q->not_full = q->not_empty = NULL;
  q->head = q->tail = 0;
}

/* Returns true if Q is empty, false otherwise. */
bool
intq_empty (const struct intq *q) 
{
  ASSERT (intr_get_level () == INTR_OFF);
  return q->head == q->tail;
}

/* Returns true if Q is full, false otherwise. */
bool
intq_full (const struct intq *q) 
{
  ASSERT (intr_get_level () == INTR_OFF);
  return next (q->head) == q->tail;
}

/* Removes a byte from Q and returns it.
   If Q is empty, sleeps until a byte is added.
   When called from an interrupt handler, Q must not be empty. */
uint8_t
intq_getc (struct intq *q) 
{
  uint8_t byte;
  
  ASSERT (intr_get_level () == INTR_OFF);
  while (intq_empty (q)) 
    {
      ASSERT (!intr_context ());
      lock_acquire (&q->lock);
      wait (q, &q->not_empty);
      lock_release (&q->lock);
    }
  
  byte = q->buf[q->tail];
  q->tail = next (q->tail);
  signal (q, &q->not_full);
  return byte;
}

/* Adds BYTE to the end of Q.
   If Q is full, sleeps until a byte is removed.
   When called from an interrupt handler, Q must not be full. */
void
intq_putc (struct intq *q, uint8_t byte) 
{
  ASSERT (intr_get_level () == INTR_OFF);
  while (intq_full (q))
    {
      ASSERT (!intr_context ());
      lock_acquire (&q->lock);
      wait (q, &q->not_full);
      lock_release (&q->lock);
    }

  q->buf[q->head] = byte;
  q->head = next (q->head);
  signal (q, &q->not_empty);
}

/* Returns the position after POS within an intq. */
static int
next (int pos) 
{
  return (pos + 1) % INTQ_BUFSIZE;
}

/* WAITER must be the address of Q's not_empty or not_full
   member.  Waits until the given condition is true. */
static void
wait (struct intq *q UNUSED, struct thread **waiter) 
{
  ASSERT (!intr_context ());
  ASSERT (intr_get_level () == INTR_OFF);
  ASSERT ((waiter == &q->not_empty && intq_empty (q))
          || (waiter == &q->not_full && intq_full (q)));

  *waiter = thread_current ();
  thread_block ();
}

/* WAITER must be the address of Q's not_empty or not_full
   member, and the associated condition must be true.  If a
   thread is waiting for the condition, wakes it up and resets
   the waiting thread. */
static void
signal (struct intq *q UNUSED, struct thread **waiter) 
{
  ASSERT (intr_get_level () == INTR_OFF);
  ASSERT ((waiter == &q->not_empty && !intq_empty (q))
          || (waiter == &q->not_full && !intq_full (q)));

  if (*waiter != NULL) 
    {
      thread_unblock (*waiter);
      *waiter = NULL;
    }
}
