/**
 * Implements a simulation where cars cross a one-lane bridge.  Each car is
 * represented by a thread, going either westbound or eastbound.
 *
 * This version uses an incomplete version of the Bridge class that
 * bundles together all state as public instance variables, and does not
 * have any methods that implement any of the logic - all the logic is
 * still in this file.
 */

#include <thread>
#include <mutex>
#include <condition_variable>
#include <algorithm>
#include <iomanip>
#include <string>
#include <iostream>
#include "bridge.hh"
#include "random-generator.h"
#include "ostreamlock.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);
}

static void print(size_t id, const string& message, bool isEastbound,
    size_t& n_crossing_eastbound, size_t& n_crossing_westbound) {
        // Make a single string to contain e.g. "car #XX crossing going eastbound".
        // This must be a single string to right-align the BRIDGE graphic correctly.
        ostringstream ss;
        ss << "car #" << id << " " << message << " going " 
             << (isEastbound ? "eastbound" : "westbound");
        string statusMessage = ss.str();

        // Print the update, spaced equally so that the BRIDGE prints at the same
        // position on the line, no matter the length of the message.
        cout << oslock << setw(60) << left << statusMessage;

        // Now print the text representation of the bridge
        if (n_crossing_eastbound + n_crossing_westbound == 0) {
                cout << "BRIDGE: [none crossing]" << endl << osunlock;
        } else if (n_crossing_eastbound > 0 && n_crossing_westbound == 0) {
                cout << "BRIDGE: [";
                for (size_t i = 0; i < n_crossing_eastbound; i++) cout << "🚗";
                cout << " -->>]" << endl << osunlock;
        } else if (n_crossing_westbound > 0 && n_crossing_eastbound == 0) {
                cout << "BRIDGE: [<<-- ";
                for (size_t i = 0; i < n_crossing_westbound; i++) cout << "🚙";
                cout << "]" << endl << osunlock;
        } else {
                cout << "ERROR!!  cars stuck, trying to go both directions" << endl << osunlock;
        }
}

// Arrive at, cross, and depart from the bridge going east.
static void cross_bridge_east(size_t id, Bridge& bridge) {

    approach_bridge();   // sleep
    
    // Arrive eastbound
    bridge.bridge_lock.lock();
    print(id, "arrived", true, bridge.n_crossing_eastbound, bridge.n_crossing_westbound);
    while (bridge.n_crossing_westbound > 0) {
            bridge.none_crossing_westbound.wait(bridge.bridge_lock);
    }
    bridge.n_crossing_eastbound++;
    print(id, "crossing", true, bridge.n_crossing_eastbound, bridge.n_crossing_westbound);
    bridge.bridge_lock.unlock();

    drive_across();      // sleep

    // Leave eastbound
    bridge.bridge_lock.lock();
    bridge.n_crossing_eastbound--;
    if (bridge.n_crossing_eastbound == 0) {
            bridge.none_crossing_eastbound.notify_all();
    }
    print(id, "crossed", true, bridge.n_crossing_eastbound, bridge.n_crossing_westbound);
    bridge.bridge_lock.unlock();
}

// Arrive at, cross, and depart from the bridge going west.
static void cross_bridge_west(size_t id, Bridge& bridge) {

    approach_bridge();   // sleep

    // Arrive westbound
    bridge.bridge_lock.lock();
    print(id, "arrived", false, bridge.n_crossing_eastbound, bridge.n_crossing_westbound);
    while (bridge.n_crossing_eastbound > 0) {
            bridge.none_crossing_eastbound.wait(bridge.bridge_lock);
    }
    bridge.n_crossing_westbound++;
    print(id, "crossing", false, bridge.n_crossing_eastbound, bridge.n_crossing_westbound);
    bridge.bridge_lock.unlock();

    drive_across();      // sleep

    // Leave westbound
    bridge.bridge_lock.lock();
    bridge.n_crossing_westbound--;
    if (bridge.n_crossing_westbound == 0) {
            bridge.none_crossing_westbound.notify_all();
    }
    print(id, "crossed", false, bridge.n_crossing_eastbound, bridge.n_crossing_westbound);
    bridge.bridge_lock.unlock();
}


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;
}
