/**
 * Implements a simulation where cars cross a one-lane bridge.  Each car is
 * represented by a thread, going either westbound or eastbound.  This implementation
 * uses the monitor pattern, as it relies on the Bridge class for implementing logic.
 */

#include <thread>
#include "bridge-soln.hh"
#include "random-generator.h"
#include "thread-utils.h"
using namespace std;

// The number of cars to spawn to go across the bridge
static const unsigned int NUM_CARS = 24;

// The bounds for how long it could take for a car to get to the bridge
static const unsigned int LOW_APPROACH_TIME_MS = 0;
static const unsigned int HIGH_APPROACH_TIME_MS = 3000;

// The bounds for how long it could take for a car to cross the bridge
static const unsigned int LOW_CROSS_TIME_MS = 100;
static const unsigned int HIGH_CROSS_TIME_MS = 500;

static mutex rgen_lock;
static RandomGenerator rgen;

/* Locks around the shared random generator and returns a random value
 * between low and high to sleep for (in milliseconds).
 */
static unsigned int get_sleep_duration(unsigned int low, unsigned int high) {
    unique_lock<mutex> ul(rgen_lock);
    return rgen.getNextInt(low, high);
}

// Simulates "crossing the bridge" by sleeping for a random amount of time.
static void drive_across() {
    sleep_for(get_sleep_duration(LOW_CROSS_TIME_MS, HIGH_CROSS_TIME_MS));
}

// Simulates "driving up to the bridge" by sleeping for a random amount of time.
static void approach_bridge() {
    sleep_for(get_sleep_duration(LOW_APPROACH_TIME_MS, HIGH_APPROACH_TIME_MS));
}

// Simulates flipping a fair coin, returning true or false with equal probability
static bool flip_coin() {
    unique_lock<mutex> ul(rgen_lock);
    return rgen.getNextBool(0.5);
}

// Arrive at, cross, and depart from the bridge going east.
static void cross_bridge_east(size_t id, Bridge& bridge) {
    approach_bridge();   // sleep
    bridge.arrive_eastbound(id);
    drive_across();      // sleep
    bridge.leave_eastbound(id);
}

// Arrive at, cross, and depart from the bridge going west.
static void cross_bridge_west(size_t id, Bridge& bridge) {
    approach_bridge();   // sleep
    bridge.arrive_westbound(id);
    drive_across();      // sleep
    bridge.leave_westbound(id);
}


int main(int argc, const char *argv[]) {
    Bridge bridge;
    thread cars[NUM_CARS];

    for (size_t i = 0; i < NUM_CARS; i++) {
        if (flip_coin()) {
            cars[i] = thread(cross_bridge_east, i, ref(bridge));
        } else {
            cars[i] = thread(cross_bridge_west, i, ref(bridge));
        }
    }

    for (thread& car : cars) {
        car.join();
    }
    return 0;
}
