#include <types.h>
#include <lib.h>
#include <synchprobs.h>
#include <synch.h>
//#include <wchan.h>
#include <opt-A1.h>

/*
 * This simple default synchronization mechanism allows only vehicle at a time
 * into the intersection.   The intersectionSem is used as a a lock.
 * We use a semaphore rather than a lock so that this code will work even
 * before locks are implemented.
 */

/*
 * Replace this default synchronization mechanism with your own (better) mechanism
 * needed for your solution.   Your mechanism may use any of the available synchronzation
 * primitives, e.g., semaphores, locks, condition variables.   You are also free to
 * declare other global variables if your solution requires them.
 */

/*
 * replace this with declarations of any synchronization and other variables you need here
 */
static struct lock * globlock;
typedef struct cv cv;

// This section contains global vars and useful functions to work with them. As well as the structs used for them
typedef struct car
{
  Direction origin;
  Direction dest;
  struct car * next;
  struct cv * cv;
} car;

typedef struct list
{
  car * front;
  car * back;
} list;

list * active = NULL;

// car initializer/allocator
static car * newcar(Direction origin, Direction dest)
{
  car * temp = kmalloc(sizeof(car));
  
  if(!(temp))
  {
    panic("Failed to create a car.\n");
  }
  
  temp->origin = origin;
  temp->dest = dest;
  temp->next = NULL;
  temp->cv = NULL;
  
  return temp;
}

// list initializer/allocator
static list * newlist()
{
  list * temp = kmalloc(sizeof(list));
  
  if(!(temp))
  {
    panic("Could not allocate list.\n");
  }
  
  temp->front = NULL;
  temp->back = NULL;
  
  return temp;
}

// push a car to the end of the active list
static void push(car * newcar)
{
  // empty list
  if (!(active->front))
  {
    active->front = newcar;
    active->back = newcar;
    return;
  }
  
  active->back->next = newcar;
  active->back = newcar;
}

// called when a car clears the intersection
static void clearint(car * done)
{
  car * temp = active->front;
  car * temp2 = NULL;
  
  // loop through until we match, storing the parent element
  while(temp != done)
  {
    temp2 = temp;
    temp = temp->next;
  }
  
  // first element of the list is being removed
  if (!(temp2))
  {
    // if this is the only element
    if (temp == active->back)
    {
      active->back = NULL;
    }
    
    active->front = active->front->next;
  }
  else
  {
    // we are removing the middle or end, so we can set the previous to point to the element after the removed
    temp2->next = temp->next;
  }
  
  if (temp->cv) // if this car was blocking something
  {
    cv_broadcast(temp->cv, globlock); // wake all/inform them you're all good
    cv_destroy(temp->cv);
    
    /*while (!(wchan_isempty(temp->cv->wc)))
    {
      cv_signal(temp->cv, globlock);
    }
    
    cv_destroy(temp->cv);*/
  }
  
  kfree(temp);
}

// cleans up a list
static void dellist(list * dead)
{
  car * temp = dead->front;

  while (temp)
  {
    car * temp2 = temp->next;
    if (temp->cv) cv_destroy(temp->cv);
    kfree(temp);
    temp = temp2;
  }
  
  kfree(dead);
}

// returns true if a car is turning right
static bool rightturn(car * car)
{
  int temp = car->origin - car->dest;
  return (temp == 1 || temp == -3);
}

/*
 * The simulation driver will call this function once before starting
 * the simulation
 *
 * You can use it to initialize synchronization and other variables.
 *
 */
void intersection_sync_init()
{
  globlock = lock_create("lightlock");
  
  if (!(globlock))
  {
    panic("Failed to create lock!\n");
  }
  
  active = newlist();
}

/*
 * The simulation driver will call this function once after
 * the simulation has finished
 *
 * You can use it to clean up any synchronization and other variables.
 *
 */
void intersection_sync_cleanup()
{
  KASSERT(active);
  
  dellist(active);
  lock_destroy(globlock);
}


/*
 * The simulation driver will call this function each time a vehicle
 * tries to enter the intersection, before it enters.
 * This function should cause the calling simulation thread
 * to block until it is OK for the vehicle to enter the intersection.
 *
 * parameters:
 *    * origin: the Direction from which the vehicle is arriving
 *    * destination: the Direction in which the vehicle is trying to go
 *
 * return value: none
 */

void intersection_before_entry(Direction origin, Direction destination)
{
  lock_acquire(globlock);
  car * new = newcar(origin, destination);
  
  RESTART:
  
  // Nothing in intersection, so proceed
  if (!(active->front))
  {
    push(new);
    lock_release(globlock);
    return;
  }
  else // things are in the intersection
  {
    car * temp = active->front;
    
    while (temp)
    {
      //kprintf("New o: %d, Comp o: %d, New d: %d, Comp d: %d\n", new->origin, temp->origin, new->dest, temp->dest);
      if ((temp->origin == new->origin && temp->dest != new->dest) || (temp->origin == new->dest && temp->dest == new->origin) || (temp->dest != new->dest && (rightturn(new) || rightturn(temp))))
      {
        //kprintf("Everything is fine, continue\n");
        temp = temp->next;
        continue;
      }
      else
      {
        // create cv for temp if it doesn't have one yet
        if(!(temp->cv))
        {
          temp->cv = cv_create("carcv");
        }
        
        //kprintf("actually sleeping now\n");
        cv_wait(temp->cv, globlock); // sleep and reacquire lock once woken
        goto RESTART; // now we have to make sure there's nothing else screwing us over
      }
    }
    
    push(new);
    lock_release(globlock);
  }
}


/*
 * The simulation driver will call this function each time a vehicle
 * leaves the intersection.
 *
 * parameters:
 *    * origin: the Direction from which the vehicle arrived
 *    * destination: the Direction in which the vehicle is going
 *
 * return value: none
 */

void intersection_after_exit(Direction origin, Direction destination)
{
  lock_acquire(globlock);
  car * temp = active->front;
  
  // since this is a list we append to, loop through and stop on first match
  while (temp)
  {
    if (temp->origin == origin && temp->dest == destination)
    {
      clearint(temp);
      break;
    }
    else
    {
      temp = temp->next;
    }
  }

  lock_release(globlock);
}
