// os45park.c - Jurassic Park   10/24/2013
// ***********************************************************************
// **   DISCLAMER ** DISCLAMER ** DISCLAMER ** DISCLAMER ** DISCLAMER   **
// **                                                                   **
// ** The code given here is the basis for the CS345 projects.          **
// ** It comes "as is" and "unwarranted."  As such, when you use part   **
// ** or all of the code, it becomes "yours" and you are responsible to **
// ** understand any algorithm or method presented.  Likewise, any      **
// ** errors or problems become your responsibility to fix.             **
// **                                                                   **
// ** NOTES:                                                            **
// ** -Comments beginning with "// ??" may require some implementation. **
// ** -Tab stops are set at every 3 spaces.                             **
// ** -The function API's in "OS345.h" should not be altered.           **
// **                                                                   **
// **   DISCLAMER ** DISCLAMER ** DISCLAMER ** DISCLAMER ** DISCLAMER   **
// ***********************************************************************
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include "park.h"

JPARK myPark;
pthread_mutex_t parkMutex; // mutex park variable access
pthread_mutex_t moveCars;
pthread_mutex_t fillSeat[NUM_CARS];   // (signal) seat ready to fill
pthread_mutex_t seatFilled[NUM_CARS]; // (wait) passenger seated
pthread_mutex_t rideOver[NUM_CARS];   // (signal) ride over

void *jurassicTask(void *args)
{
    int i, j, k;
    char buf[32];
    int c[NUM_CARS];
    // initial car locations
    for (i = 0; i < NUM_CARS; i++)
    {
        myPark.cars[i].location = 33 - i;
        myPark.cars[i].passengers = 0;
    }
    // drivers are all asleep
    for (i = 0; i < NUM_DRIVERS; i++)
    {
        myPark.drivers[i] = 0;
    }
    // initialize other park variables
    myPark.numOutsidePark = 0;
    // # trying to enter park
    myPark.numTicketsAvailable = MAX_TICKETS;
    // T=#
    myPark.numInPark = 0;
    // P=#
    myPark.numRidesTaken = 0;
    // S=#
    myPark.numInTicketLine = 0;
    // Ticket line
    myPark.numInMuseumLine = 0;
    // Museum line
    myPark.numInMuseum = 0;
    // # in museum
    myPark.numInCarLine = 0;
    // Ride line
    myPark.numInCars = 0;
    // # in cars
    myPark.numInGiftLine = 0;
    // Gift shop line
    myPark.numInGiftShop = 0;
    // # in gift shop
    myPark.numExitedPark = 0;
    // # exited park

    // create move car signal semaphore
    pthread_mutex_init(&moveCars, NULL);
    pthread_mutex_lock(&moveCars);

    // initialize communication semaphores
    for (i = 0; i < NUM_CARS; i++)
    {
        pthread_mutex_init(&fillSeat[i], NULL);
        pthread_mutex_lock(&fillSeat[i]);

        pthread_mutex_init(&seatFilled[i], NULL);
        pthread_mutex_lock(&seatFilled[i]);

        pthread_mutex_init(&rideOver[i], NULL);
        pthread_mutex_lock(&rideOver[i]);
    }
    // start display park task
    pthread_t displayTask;
    pthread_create(&displayTask, NULL, jurassicDisplayTask, NULL);

    // OK, let's get the park going...
    pthread_mutex_init(&parkMutex, NULL);
    begin = 1;

    // start lost visitor task
    pthread_t lostTask;
    pthread_create(&lostTask, NULL, lostVisitorTask, NULL);

    // wait to move cars
    do
    {
        // wait for signal to move cars
        ParkDebug("WAIT(moveCars)");
        pthread_mutex_lock(&moveCars);

        // order car locations
        for (i = 0; i < NUM_CARS; i++)
        {
            c[i] = i;
        }
        // sort car positions
        for (i = 0; i < (NUM_CARS - 1); i++)
        {
            for (j = i + 1; j < NUM_CARS; j++)
            {
                if (myPark.cars[c[i]].location < myPark.cars[c[j]].location)
                {
                    k = c[i];
                    c[i] = c[j];
                    c[j] = k;
                }
            }
        }
        // move each car if possible (or load or unload)
        for (i = 0; i < NUM_CARS; i++)
        {
            // move car (-1=location occupied, 0=moved, 1=no move)
            if (makeMove(c[i]))
            {
                // check if car is loading
                if ((myPark.cars[c[i]].location == 33) && (myPark.cars[c[i]].passengers < NUM_SEATS) &&
                    (myPark.numInCarLine))
                {
                    // need a passenger
                    sprintf(buf, "SIGNAL(fillSeat[%d])", c[i]);
                    ParkDebug(buf);
                    pthread_mutex_unlock(&fillSeat[c[i]]);
                    // seat open
                    sprintf(buf, "WAIT(seatFilled[%d])", c[i]);
                    ParkDebug(buf);
                    pthread_mutex_lock(&seatFilled[c[i]]);
                    // passenger in seat
                    myPark.cars[c[i]].passengers++;
                }
                // check if car is unloading
                if ((myPark.cars[c[i]].location == 30) && (myPark.cars[c[i]].passengers > 0))
                {
                    // empty each seat until car is empty
                    myPark.numRidesTaken++;

                    // if car empty, signal ride over
                    if (--myPark.cars[c[i]].passengers == 0)
                    {
                        sprintf(buf, "SIGNAL(rideOver[%d])", c[i]);
                        ParkDebug(buf);
                        pthread_mutex_unlock(&rideOver[c[i]]);
                    }
                }
                // sanity check
                if ((myPark.cars[0].location == myPark.cars[1].location) ||
                    (myPark.cars[0].location == myPark.cars[2].location) ||
                    (myPark.cars[0].location == myPark.cars[3].location) ||
                    (myPark.cars[1].location == myPark.cars[2].location) ||
                    (myPark.cars[1].location == myPark.cars[3].location) ||
                    (myPark.cars[2].location == myPark.cars[3].location))
                {
                    printf("\nProblem:");
                    for (k = 0; k < NUM_CARS; k++)
                        printf(" %d", myPark.cars[k].location);
                    k = getchar();
                }
            }
        }
    } while (myPark.numExitedPark < NUM_VISITORS);
    // park done
    printf("\nJurassic Park is shutting down for the evening!!");
    return NULL;
} // end

//***********************************************************************
// Move car in Jurassic Park (if possible)
//
//  return -1 if location occupied
//            0 if move ok
//         1 if no move
//
int makeMove(int car)
{
    int i, j;
    int moved;
    switch (j = myPark.cars[car].location)
    {
    // at cross roads 1
    case 7: {
        j = (rand() % 2) ? 24 : 8;
        break;
    }
    // at cross roads 2
    case 20: {
        // take shorter route if no one in line
        if (myPark.numInCarLine == 0)
        {
            j = 28;
        }
        else
        {
            j = (rand() % 3) ? 28 : 21;
        }
        break;
    }
    // bridge loop
    case 23: {
        j = 1;
        break;
    }
    // bridge short route
    case 27: {
        j = 20;
        break;
    }
    // exit car
    case 30: {
        if (myPark.cars[car].passengers == 0)
        {
            j++;
        }
        break;
    }
    // loading car
    case 33: {
        // if there is someone in car and noone is in line, proceed
        // if ((myPark.cars[car].passengers == NUM_SEATS) ||
        //   ((myPark.numInCarLine == 0) && myPark.cars[car].passengers))
        // if car is full, proceed into park
        if (myPark.cars[car].passengers == NUM_SEATS)
        {
            j = 0;
        }
        break;
    }
    // all other moves
    default: {
        j++;
        break;
    }
    }
    // check to see if position is taken
    for (i = 0; i < NUM_CARS; i++)
    {
        if (i != car)
        {

            if (myPark.cars[i].location == j)
            {
                return -1;
            }
        }
    }
    // return 0 if car moved
    moved = (myPark.cars[car].location == j);
    // make move
    myPark.cars[car].location = j;
    return moved;
}
// ***********************************************************************
// ***********************************************************************
// jurassic task
// ***********************************************************************
void *jurassicDisplayTask(void *args)
{
    // wait for park to get initialized...

    while (!begin)
    {
    }

    // display park every second
    do
    {
        JPARK currentPark;
        // update every second
        sleep(1);
        // take snapshot of park
        pthread_mutex_lock(&parkMutex);
        currentPark = myPark;
        pthread_mutex_unlock(&parkMutex);
        // draw current park
        drawPark(&currentPark);
        // signal for cars to move
        pthread_mutex_unlock(&moveCars);

    } while (myPark.numExitedPark < NUM_VISITORS);
    // park done
    pthread_mutex_unlock(&moveCars);
    printf("\nThank you for visiting Jurassic Park!!");
    return NULL;
} // end
//***********************************************************************
// Draw Jurassic Park
//          1         2         3         4         5         6         7
// 01234567890123456789012345678901234567890123456789012345678901234567890
//                _______________________________________________________   0
//   Entrance    /            ++++++++++++++++++++++++++++++++++++++++++|   1
//              -             +o0o-o1o-o2o-o3o-o4o-o5o-o6o             +|   2
//          ## -             /+   /                       \            +|   3
//            /             o +  o     ************        o           +|   4
//   |********         ##>>33 + 23     *Cntrl Room*        7           +|   5
//   |*Ticket*              o +  o     * A=# D1=# *        o           +|   6
//   |*Booth *             32 +  |     * B=# D2=# *       / \          +|   7
//   |* T=#  * ##           o +  o     * C=# D3=# *      o   o8o-o9o   +|   8
//   |* P=## *             31 + 22     * D=# D4=# *    24           \  +|   9
//   |* S=#  *              o +  o     ************    o             o +|   10
//   |********         ##<<30 +   \                   /             10 +|   11
//   |                      o +    o21-o20-o27-o26-o25               o +|   12
//   |                       \+       /   \                          | +|   13
//   |                        +o29-o28     o                         o +|   14
//   |                        +++++++++++ 19                        11 +|   15
//   |                                  +  o                         o +|   16
//   |              ##     ##           +   \                       /  +|   17
//   |        ******\ /****\ /********  +    o  O\                 o   +|   18
//   |        *         *            *  +   18    \/|||\___       12   +|   19
//    \       *  Gifts  *   Museum   *  +    o     x   x           o   +|   20
//     -      *    #    *     ##     *  +     \                   /    +|   21
//   ## -     *         *            *  +      o17-o16-o15-o14-o13     +|   22
//       \    ************************  ++++++++++++++++++++++++++++++++|   23
//
char *myTime(char *svtime)
{
    time_t cTime; // current time

    time(&cTime); // read current time
    strcpy(svtime, asctime(localtime(&cTime)));
    svtime[strlen(svtime) - 1] = 0; // eliminate nl at end
    return svtime;
} // end myTime

#define D1Upper 13
#define D1Left 45
#define D1Lower 18
#define D1Right 53
#define D2Upper 4
#define D2Left 53
#define D2Lower 13
#define D2Right 57
void drawPark(JPARK *park)
{
    static int direction1 = D1Left;
    static int dy1 = D1Lower;
    static int direction2 = D2Left;
    static int dy2 = D1Lower - 9;
    int i, j;
    char svtime[64]; // ascii current time
    char driver[] = {'T', 'z', 'A', 'B', 'C', 'D'};
    char buf[32];
    char pk[25][80];
    static int cp[34][3] = {
        {2, 29, 0},  {2, 33, 0},  {2, 37, 0},  {2, 41, 0},  // 0-6
        {2, 45, 0},  {2, 49, 0},  {2, 53, 0},  {4, 57, 1},  // 7
        {8, 59, 0},  {8, 63, 0},                            // 8-9
        {10, 67, 1}, {14, 67, 1}, {18, 65, 1},              // 10-12
        {22, 61, 0}, {22, 57, 0}, {22, 53, 0},              // 13-17
        {22, 49, 0}, {22, 45, 0}, {18, 43, 1}, {14, 41, 1}, // 18-19
        {12, 37, 0}, {12, 33, 0}, {8, 31, 1},  {4, 31, 1},  // 20-23
        {8, 55, 3},                                         // 24
        {12, 49, 0}, {12, 45, 0}, {12, 41, 0},              // 25-27
        {14, 33, 0}, {14, 29, 0},                           // 28-29
        {10, 26, 1}, {8, 26, 1},  {6, 26, 1},  {4, 26, 1},  // 30-33
    };
    strcpy(&pk[0][0], "                ___Jurassic Park_______________________________________");
    strcpy(&pk[1][0], "   Entrance    /            ++++++++++++++++++++++++++++++++++++++++++|");
    strcpy(&pk[2][0], "              -             +---------------------------             +|");
    strcpy(&pk[3][0], "           # -             /+   /                       \\            +|");
    strcpy(&pk[4][0], "            /             | +  |     ************        |           +|");
    strcpy(&pk[5][0], "   |********          # >>| +  |     *Cntrl Room*        |           +|");
    strcpy(&pk[6][0], "   |*Ticket*              | +  |     * A=# D1=# *        |           +|");
    strcpy(&pk[7][0], "   |*Booth *              | +  |     * B=# D2=# *       / \\          +|");
    strcpy(&pk[8][0], "   |* T=#  * #            | +  |     * C=# D3=# *      /   -------   +|");
    strcpy(&pk[9][0], "   |* P=#  *              | +  |     * D=# D4=# *     /           \\  +|");
    strcpy(&pk[10][0], "   |* S=#  *              | +  |     ************    /             | +|");
    strcpy(&pk[11][0], "   |********            <<| +   \\                   /              | +|");
    strcpy(&pk[12][0], "   |                      | +    -------------------               | +|");
    strcpy(&pk[13][0], "   |                       \\+       /   \\                          | +|");
    strcpy(&pk[14][0], "   |                        +-------     |                         | +|");
    strcpy(&pk[15][0], "   |                        +++++++++++  |                         | +|");
    strcpy(&pk[16][0], "   |                                  +  |                         | +|");
    strcpy(&pk[17][0], "   |                #      #          +   \\                       /  +|");
    strcpy(&pk[18][0], "   |        ******\\ /****\\ /********  +    |                     |   +|");
    strcpy(&pk[19][0], "   |        *         *            *  +    |                     |   +|");
    strcpy(&pk[20][0], "    \\       *  Gifts  *   Museum   *  +    |                     |   +|");
    strcpy(&pk[21][0], "     -      *         *            *  +     \\                   /    +|");
    strcpy(&pk[22][0], "    # -     *         *            *  +      -------------------     +|");
    strcpy(&pk[23][0], "       \\    ************************  ++++++++++++++++++++++++++++++++|");
    strcpy(&pk[24][0], "   Exit \\_____________________________________________________________|");
    // output time
    sprintf(buf, "%s", myTime(svtime));
    memcpy(&pk[0][strlen(pk[0]) - strlen(buf)], buf, strlen(buf));

    // out number waiting to get into park
    sprintf(buf, "%d", park->numOutsidePark);
    memcpy(&pk[3][12 - strlen(buf)], buf, strlen(buf));

    // T=#, out number of tickets available
    sprintf(buf, "%d ", park->numTicketsAvailable);
    memcpy(&pk[8][8], buf, strlen(buf));

    // P=#, out number in park (not to exceed OSHA requirements)
    sprintf(buf, "%d ", park->numInPark);
    memcpy(&pk[9][8], buf, strlen(buf));

    // S=#, output guests completing ride
    sprintf(buf, "%d", park->numRidesTaken);
    memcpy(&pk[10][8], buf, strlen(buf));

    // out number in ticket line
    sprintf(buf, "%d ", park->numInTicketLine);
    memcpy(&pk[8][13], buf, strlen(buf));

    // out number in gift shop line
    sprintf(buf, "%d", park->numInGiftLine);
    memcpy(&pk[17][21 - strlen(buf)], buf, strlen(buf));

    // out number in museum line
    sprintf(buf, "%d", park->numInMuseumLine);
    memcpy(&pk[17][28 - strlen(buf)], buf, strlen(buf));

    // out number in car line
    sprintf(buf, "%d", park->numInCarLine);
    memcpy(&pk[5][23 - strlen(buf)], buf, strlen(buf));

    // out number in gift shop
    sprintf(buf, "%d ", park->numInGiftShop);
    memcpy(&pk[21][17], buf, strlen(buf));

    // out number in museum
    sprintf(buf, "%d ", park->numInMuseum);
    memcpy(&pk[21][29], buf, strlen(buf));

    // out number exited park
    sprintf(buf, "%d", park->numExitedPark);
    memcpy(&pk[22][5 - strlen(buf)], buf, strlen(buf));

    // cars
    for (i = 0; i < NUM_CARS; i++)
    {
        sprintf(buf, "%d", park->cars[i].passengers);
        memcpy(&pk[6 + i][42 - strlen(buf)], buf, strlen(buf));
    }
    // drivers
    for (i = 0; i < NUM_DRIVERS; i++)
    {
        pk[6 + i][46] = driver[park->drivers[i] + 1];
    }
    // output cars
    for (i = 0; i < NUM_CARS; i++)
    {
        // draw car
        j = park->cars[i].location;
        switch (cp[j][2])
        {
        // horizontal
        case 0: {
            pk[cp[j][0]][cp[j][1] + 0] = 'o';
            pk[cp[j][0]][cp[j][1] + 1] = 'A' + i;
            pk[cp[j][0]][cp[j][1] + 2] = 'o';
            break;
        }
        // vertical
        case 1: {
            pk[cp[j][0] + 0][cp[j][1]] = 'o';
            // pk[cp[j][0]+1][cp[j][1]] = 'A'+i;
            // if ((park->cars[i].passengers > 0) && (park->cars[i].passengers < NUM_SEATS))
            if ((park->cars[i].passengers > 0) && ((j == 30) || (j == 33)))
            {
                pk[cp[j][0] + 1][cp[j][1]] = '0' + park->cars[i].passengers;
            }
            else
                pk[cp[j][0] + 1][cp[j][1]] = 'A' + i;

            pk[cp[j][0] + 2][cp[j][1]] = 'o';
            break;
        }
        case 2: {
            pk[cp[j][0] + 0][cp[j][1] + 0] = 'o';
            pk[cp[j][0] + 1][cp[j][1] + 1] = 'A' + i;
            pk[cp[j][0] + 2][cp[j][1] + 2] = 'o';
            break;
        }
        case 3: {
            pk[cp[j][0] + 0][cp[j][1] - 0] = 'o';
            pk[cp[j][0] + 1][cp[j][1] - 1] = 'A' + i;
            pk[cp[j][0] + 2][cp[j][1] - 2] = 'o';
            break;
        }
        }
    }
    // move dinosaur #1
    dy1 = dy1 + (rand() % 3) - 1;

    if (dy1 < D1Upper)
        dy1 = D1Upper;

    if (dy1 > D1Lower)
        dy1 = D1Lower;

    if (direction1 > 0)
    {
        memcpy(&pk[dy1 + 0][direction1 + 4], "...  /O", 7);
        memcpy(&pk[dy1 + 1][direction1 + 0], "___/|||\\/", 9);
        memcpy(&pk[dy1 + 2][direction1 + 3], "x   x", 5);
        if (++direction1 > D1Right)
            direction1 = -direction1;
    }
    else
    {
        if ((rand() % 3) == 1)
        {
            memcpy(&pk[dy1 + 0][-direction1 + 4], "...", 3);
            memcpy(&pk[dy1 + 1][-direction1 + 1], "__/|||\\___", 10);
            memcpy(&pk[dy1 + 2][-direction1 + 0], "O  x   x", 8);
        }
        else
        {
            memcpy(&pk[dy1 + 0][-direction1 + 0], "O\\  ...", 7);
            memcpy(&pk[dy1 + 1][-direction1 + 2], "\\/|||\\___", 9);
            memcpy(&pk[dy1 + 2][-direction1 + 3], "x   x", 5);
        }
        if (++direction1 > -D1Left)
            direction1 = -direction1;
    }
    // move dinosaur #2
    dy2 = dy2 + (rand() % 3) - 1;
    if (dy2 < D2Upper)
        dy2 = D2Upper;

    if (dy2 > D2Lower)
        dy2 = D2Lower;

    dy2 = (dy2 + 9) >= dy1 ? dy1 - 9 : dy2;

    if (direction2 > 0)
    {
        memcpy(&pk[dy2 + 0][direction2 + 7], "_", 1);
        memcpy(&pk[dy2 + 1][direction2 + 6], "/o\\", 3);
        memcpy(&pk[dy2 + 2][direction2 + 4], "</ _<", 5);
        memcpy(&pk[dy2 + 3][direction2 + 3], "</ /", 4);
        memcpy(&pk[dy2 + 4][direction2 + 2], "</ ==x", 6);
        memcpy(&pk[dy2 + 5][direction2 + 3], "/  \\", 4);
        memcpy(&pk[dy2 + 6][direction2 + 2], "//)__)", 6);
        memcpy(&pk[dy2 + 7][direction2 + 0], "<<< \\_ \\_", 9);
        if (++direction2 > D2Right)
            direction2 = -direction2;
    }
    else
    {
        memcpy(&pk[dy2 + 0][-direction2 + 1], "_", 1);
        memcpy(&pk[dy2 + 1][-direction2 + 0], "/o\\", 3);
        memcpy(&pk[dy2 + 2][-direction2 + 0], ">_ \\>", 5);
        memcpy(&pk[dy2 + 3][-direction2 + 2], "\\ \\>", 4);
        memcpy(&pk[dy2 + 4][-direction2 + 1], "x== \\>", 6);
        memcpy(&pk[dy2 + 5][-direction2 + 2], "/  \\", 4);
        memcpy(&pk[dy2 + 6][-direction2 + 1], "(__(\\\\", 6);
        memcpy(&pk[dy2 + 7][-direction2 + 0], "_/ _/ >>>", 9);
        if (++direction2 > -D2Left)
            direction2 = -direction2;
    }
    // draw park
    system("clear");

    printf("\n");
    for (i = 0; i < 25; i++)
        printf("\n%s", &pk[i][0]);
    printf("\n");

    // driver in only one place at a time
    for (i = 0; i < (NUM_DRIVERS - 1); i++)
    {
        if (park->drivers[i] != 0)
        {
            for (j = i + 1; j < NUM_DRIVERS; j++)
            {
                assert("Driver Error" && (park->drivers[i] != park->drivers[j]));
            }
        }
    }
    return;
} // end drawPark
// ***********************************************************************
// ***********************************************************************
// lostVisitor task
// ***********************************************************************
void *lostVisitorTask(void *args)
{
    int inPark;
    while (myPark.numExitedPark < NUM_VISITORS)
    {
        // number in park
        pthread_mutex_lock(&parkMutex);
        inPark = myPark.numInTicketLine + myPark.numInMuseumLine + myPark.numInMuseum + myPark.numInCarLine +
                 myPark.numInCars + myPark.numInGiftLine + myPark.numInGiftShop;

        if (inPark != myPark.numInPark)
        {
            printf("\nSomeone is lost!!!");
            printf("\nThere are %d visitors in the park,", myPark.numInPark);
            printf("\nbut I can only find %d of them!\n", inPark);
            printf("\n      numInPark=%d", myPark.numInPark);
            printf("\n         inPark=%d", inPark);
            printf("\nnumInTicketLine=%d", myPark.numInTicketLine);
            printf("\nnumInMuseumLine=%d", myPark.numInMuseumLine);
            printf("\n    numInMuseum=%d", myPark.numInMuseum);
            printf("\n   numInCarLine=%d", myPark.numInCarLine);
            printf("\n      numInCars=%d", myPark.numInCars);
            printf("\n  numInGiftLine=%d", myPark.numInGiftLine);
            printf("\n  numInGiftShop=%d", myPark.numInGiftShop);
            printf("\n");
            assert("Too few in Park!" && (inPark == myPark.numInPark));
        }
        pthread_mutex_unlock(&parkMutex);
    }
    return NULL;
} // end lostVisitorTask
