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

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 cv * cv;
} car;

// array of all 12 possible cars in the intersection
car * active[12] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};

// all of our offset arrays
int dirs[4] = {0, 3, 6, 9};
int northa[4] = {0, 0, 1, 2};
int easta[4] = {2, 0, 0, 1};
int southa[4] = {1, 2, 0, 0};
int westa[4] = {0, 1, 2, 0};
int * compass[4] = {northa, easta, southa, westa};

// 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->cv = NULL;
  
  return temp;
}

// called when a car clears the intersection
static void clearint(car * done, int pos)
{
  active[pos] = NULL;
  
  if (done->cv) // if this car was blocking something
  {
    cv_broadcast(done->cv, globlock); // wake all/inform them you're all good
    cv_destroy(done->cv);
  }
  
  kfree(done);
}

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

// inits all arrays we need, lock.
void intersection_sync_init()
{
  globlock = lock_create("lightlock");
  
  if (!(globlock))
  {
    panic("Failed to create lock!\n");
  }
}

/*
 * 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);
  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:
  for (int i = 0; i < 12; ++i)
  {
    car * temp = active[i];
    
    if (temp)
    {
      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))))
      {
        continue;
      }
      else
      {
        // create cv for temp if it doesn't have one yet
        if(!(temp->cv))
        {
          temp->cv = cv_create("carcv");
        }
        
        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
      }
    }
    else
    {
      continue;
    }
  }
  
  active[(dirs[new->origin] + *(compass[new->origin] + new->dest))] = 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);
  int position = dirs[origin] + *(compass[origin] + destination);
  clearint(active[position], position);
  lock_release(globlock);
}
