//Autor: Roman Necas

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>

// Macros for memory mapping
#define MMAP(ptr) {(ptr) = mmap(NULL, sizeof(*(ptr)), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);}
#define UNMAP(ptr) {munmap((ptr), sizeof(*(ptr)));}

// Shared memory variables

int *order = NULL;         // Order of execution for print statements
int *bus_stop;             // Array to store bus stop IDs for each skier

// Semaphores
sem_t *sem_mutex = NULL;   // Mutex semaphore for critical sections
sem_t *sem_boarded = NULL; // Semaphore for skiers boarding the bus
sem_t *sem_order = NULL;   // Semaphore for print order
sem_t *sem_ubus = NULL;    // Semaphore for skiers unboarding the bus
sem_t *sem_unboard = NULL; // Semaphore for skiers unboarding the bus
sem_t **sem_busstops = NULL; // Array of semaphores for each bus stop

FILE *output;              // File pointer for output

// Function prototypes
void bus_loop(int Z, int K, int TB, int L);
void skier_loop(int idL, int idZ, int TL);

int main(int argc, char* argv[]) {
    // Check for correct number of arguments
    if (argc != 6) {
        fprintf(stderr, "Usage: %s L Z K TL TB\n", argv[0]);
        return 1;
    }

    // Parse command-line arguments
    int L = atoi(argv[1]);    // Number of skiers
    int Z = atoi(argv[2]);    // Number of bus stops
    int K = atoi(argv[3]);    // Maximum skiers on the bus
    int TL = atoi(argv[4]);   // Maximum time for a skier to reach the bus stop
    int TB = atoi(argv[5]);   // Maximum time for the bus to travel between stops

    // Validate argument values
    if (L < 0 || L >= 20000 || Z <= 0 || Z > 10 || K < 10 || K > 100 || TL < 0 || TL > 10000 || TB < 0 || TB > 1000) {
        fprintf(stderr, "Invalid arguments\n");
        return 1;
    }
    output = fopen("proj2.out", "w");

    // Memory mapping for shared variables
   
    MMAP(order);
    bus_stop = mmap(NULL, sizeof(*bus_stop) * L, PROT_READ | PROT_WRITE, 
                    MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    for (int i = 0; i < L; i++) {
        bus_stop[i] = 0; // Initialize bus stop array
    }
  
    *order = 1;   // Initialize order

    // Create semaphores
    if (( sem_mutex = sem_open("/xnecasr00_sem_mutex", O_CREAT | O_EXCL, 0666, 1)) == SEM_FAILED) {
        fprintf(stderr, "Error: Opening sem_mutex failed.\n"); 
        return 1;
    }
    if ((sem_boarded = sem_open("/xnecasr00_sem_boarded", O_CREAT | O_EXCL, 0666, 0)) == SEM_FAILED) {
        fprintf(stderr, "Error: Opening sem_boarded failed.\n");
        return 1;
    }
    if ((sem_order = sem_open("/xnecasr00_sem_order", O_CREAT | O_EXCL, 0666, 1)) == SEM_FAILED) {
        fprintf(stderr, "Error: Opening sem_order failed.\n");
        return 1;
    }
    if ((sem_ubus = sem_open("/xnecasr00_sem_ubus", O_CREAT | O_EXCL, 0666, 0)) == SEM_FAILED) {
        fprintf(stderr, "Error: Opening sem_ubus failed.\n");
        return 1;
    }
    if ((sem_unboard = sem_open("/xnecasr00_sem_unboard", O_CREAT | O_EXCL, 0666, 0)) == SEM_FAILED) {
        fprintf(stderr, "Error: Opening sem_unboard failed.\n");
        return 1;
    }

    // Create semaphores for each bus stop
    MMAP(sem_busstops);
    for (int i = 0; i < Z; i++) {
        char sem_name[35];
        snprintf(sem_name, sizeof(sem_name), "/xnecasr00_sem_busstop_%d", i + 1);
        if ((sem_busstops[i] = sem_open(sem_name, O_CREAT | O_EXCL, 0666, 0)) == SEM_FAILED) {
            fprintf(stderr, "Error: Opening semaphore %s failed.\n", sem_name);
            return 1;
        }
    }

    // Fork for bus process
    pid_t bus_pid = fork();
    if (bus_pid == -1) {
        perror("fork");
        return 1;
    } else if (bus_pid == 0) {
        bus_loop(Z, K, TB, L);
        exit(0);
    }

    // Fork for skier processes
    for (int i = 1; i <= L; i++) {
        pid_t skier_pid = fork();
        if (skier_pid == -1) {
            perror("fork");
            return 1;
        } else if (skier_pid == 0) {
            srand(time(0) * getpid());    
            int idZ = rand() % Z + 1; // Randomly assign bus stop ID
            skier_loop(i, idZ, TL);
            exit(0);
        }
    }

    // Wait for all child processes to finish
    for (int i = 0; i <= L; i++) {
        wait(NULL);
    }

    // Clean up shared memory and semaphores
    
    UNMAP(order);
    munmap(bus_stop, sizeof(*bus_stop)*L);
    sem_close(sem_mutex);
    sem_unlink("/xnecasr00_sem_mutex");
    sem_close(sem_boarded);
    sem_unlink("/xnecasr00_sem_boarded");
    sem_close(sem_order);
    sem_unlink("/xnecasr00_sem_order");
    sem_close(sem_ubus);
    sem_unlink("/xnecasr00_sem_ubus");
    sem_close(sem_unboard);
    sem_unlink("/xnecasr00_sem_unboard");

    // Close and unlink semaphores for each bus stop 
    for (int i = 0; i < Z; i++) {
        char sem_name[35];
        snprintf(sem_name, sizeof(sem_name), "/xnecasr00_sem_busstop_%d", i + 1);
        sem_close(sem_busstops[i]);
        sem_unlink(sem_name);
    }

    fclose(output);
    
    return 0;
}

void bus_loop(int Z, int K, int TB, int L) {
   srand(time(0) * getpid());   

   // Print bus started
   sem_wait(sem_order);
   setbuf(output, NULL);
   fprintf(output, "%d: BUS: started\n", *order);
   *order += 1;
   sem_post(sem_order);

   int boarded = 0;  // Number of skiers on the bus
   int idZ = 1;      // Current bus stop ID
   int skiing = 0;
   while (1) {
       // Sleep for a random time before arriving at the next stop
       usleep(rand() % (TB + 1));
       
       // Print bus arrival at the current stop
       sem_wait(sem_order);
       setbuf(output, NULL);
       fprintf(output, "%d: BUS: arrived to %d\n",*order, idZ);
       *order += 1;
       sem_post(sem_order);

       // Board skiers waiting at the current stop
       sem_wait(sem_mutex);
       int waiting_at_stop = 0;
       for (int i = 0; i < L; i++) {
           if ((bus_stop[i] == idZ) && waiting_at_stop < K) {
                    bus_stop[i] = 0; // Reset bus stop ID for skiers who boarded
                    waiting_at_stop++;
               	    sem_post(sem_busstops[idZ - 1]); // Signal skiers at the current stop to board
                    sem_wait(sem_boarded); // Wait for skiers to board
           }
       } 
      
       boarded += waiting_at_stop;  // Increment the number of skiers on the bus
       sem_post(sem_mutex);

       // Print bus leaving the current stop
       sem_wait(sem_order);
       setbuf(output, NULL);
       fprintf(output, "%d: BUS: leaving %d\n",*order, idZ);
       *order += 1;
       sem_post(sem_order);
   
       // If the bus has reached the final stop
       if (idZ == Z) {
           usleep(rand() % (TB + 1)); // Sleep for a random time before arriving at the final stop

           // Print bus arrival at the final stop
           sem_wait(sem_order);
           setbuf(output, NULL);
           fprintf(output, "%d: BUS: arrived to final\n", *order);
           *order += 1;
           sem_post(sem_order);

           // Unboard skiers at the final stop
           for (int i = 0; i < boarded; i++) {
               sem_post(sem_ubus); // Signal skiers to unboard
               sem_wait(sem_unboard); // Wait for skiers to unboard
		skiing++;
           }

           // Print bus leaving the final stop
           sem_wait(sem_order);
           setbuf(output, NULL);
           fprintf(output, "%d: BUS: leaving final\n", *order);
           *order += 1;
           sem_post(sem_order);
           
           idZ = 1; // Reset bus stop ID to the first stop
           boarded = 0; // Reset the number of skiers on the bus

            sem_wait(sem_mutex);
           // Check if there are any remaining skiers at the bus stops
            int remaining = 0;
            for (int i = 0; i < L; i++) {
                if (bus_stop[i] != 0) {
                    remaining++;
                }
            }
            sem_post(sem_mutex);
           
           // If there are no remaining skiers, print finish and exit the loop
           if (remaining == 0 && skiing == L) {

               sem_wait(sem_order);
               setbuf(output, NULL);
               fprintf(output, "%d: BUS: finish\n", *order);
               *order += 1;
               sem_post(sem_order);
               break;
           }
       } else {
           idZ++; // Move to the next bus stop
       }
   }
}

void skier_loop(int idL, int idZ, int TL) {
       srand(time(0) * getpid());

       // Print skier started
       sem_wait(sem_order);    
       setbuf(output, NULL);
       fprintf(output, "%d: L %d: started\n",*order, idL);
       *order += 1;
       sem_post(sem_order);

       // Sleep for a random time before arriving at the bus stop
       usleep(rand() % (TL + 1));

       // Print skier arrival at the bus stop
       sem_wait(sem_order);
       setbuf(output, NULL);
       fprintf(output, "%d: L %d: arrived to %d\n",*order, idL, idZ);
       *order += 1;
       sem_post(sem_order);

       // Store the skier's bus stop ID and increment the waiting count
       sem_wait(sem_mutex);
       bus_stop[idL - 1] = idZ;
      
       sem_post(sem_mutex);

       // Wait for the bus to arrive at the skier's bus stop
       sem_wait(sem_busstops[idZ - 1]);
       
       // Print skier boarding the bus
       sem_wait(sem_order);
       setbuf(output, NULL);
       fprintf(output, "%d: L %d: boarding\n",*order, idL);
       *order += 1;
       sem_post(sem_order);
       sem_post(sem_boarded); // Signal that the skier has boarded

       // Wait for the bus to reach the final stop
       sem_wait(sem_ubus);
       
       // Print skier going to ski
       sem_wait(sem_order);
       setbuf(output, NULL);
       fprintf(output, "%d: L %d: going to ski\n",*order, idL);
       *order += 1;
       sem_post(sem_order);
       sem_post(sem_unboard); // Signal that the skier has unboarded
       
}
