#include "Starbucks.h"

Starbucks::Starbucks(int N, int M) {
    // Define the necessary variables.
    this->num_of_cashier = N;
    this->num_of_barista = N/3;
    this->num_of_order = M;

    //These are the employees.
    this->cashier = new Cashier[num_of_cashier];
    this->barista = new Barista[num_of_barista];
    this->baristaQueue = new priority_queue<Customer, vector<Customer>, CompareCustomerPrice>[num_of_barista];


    //These are the information to be printed later.
    this->turnoverTimes = new double[num_of_order];
    this->systemRunTime = 0;
    this->maxCashierQueue = 0;
    this->maxBaristaQueue = new int[num_of_barista];

    for(int i=0; i<num_of_barista; i++) {
        maxBaristaQueue[i] = 0;
    }

    //This holds how many customers visited the shop at that very day.
    this->registeredCustomer = 0;
}

// Method is used to welcome a customer to the shop and puts it into eventQueue
void Starbucks::addCustomer(Customer& c) {
    c.registrationNumber = this->registeredCustomer;
    this->registeredCustomer++;
    this->eventQueue.push(c);
}

//Runs the second model.
void Starbucks::runStarbucksModelTwo() {
    this->systemRunTime = eventQueue.top().arrivalTime;

    while(!eventQueue.empty()) {
        Customer c = eventQueue.top();
        eventQueue.pop();

        if (c.status == 1) {
            bool isPlaced = false;

            for (int i = 0; i < num_of_cashier; i++) {
                if (cashier[i].isAvailable) {
                    cashier[i].isAvailable = false;
                    cashier[i].runtime += c.orderTime;

                    isPlaced = true;
                    c.status = 2;
                    c.workerNumber = i;
                    c.queueTime += c.orderTime;
                    eventQueue.push(c);
                    break;
                }
            }
            // If customer can't find a cashier available, will go to queue.
            if (!isPlaced) {
                cashierQueue.push(c);
                if (cashierQueue.size() > this->maxCashierQueue)
                    this->maxCashierQueue = cashierQueue.size();
            }
        }
        // When a customer leaves cashier another one comes in from the queue.
        else if (c.status == 2) {
            cashier[c.workerNumber].isAvailable = true;
            c.status = 3;
            eventQueue.push(c);

            if (!cashierQueue.empty()) {
                Customer c2(cashierQueue.top());
                cashierQueue.pop();
                cashier[c.workerNumber].isAvailable = false;
                cashier[c.workerNumber].runtime += c2.orderTime;
                c2.status = 2;
                c2.workerNumber = c.workerNumber;
                c2.queueTime = c.queueTime + c2.orderTime;
                eventQueue.push(c2);
            }
        }
        // Customer goes to barista.
        else if (c.status == 3) {
            bool isPlaced = false;

            if (barista[c.workerNumber/3].isAvailable) {
                barista[c.workerNumber/3].isAvailable = false;
                barista[c.workerNumber/3].runtime += c.brewTime;

                isPlaced = true;
                c.status = 4;
                c.queueTime += c.brewTime;
                eventQueue.push(c);
            }
            // If not found a place, go to the baristaQueue.
            if (!isPlaced) {
                baristaQueue[c.workerNumber/3].push(c);
                if (baristaQueue[c.workerNumber/3].size() > maxBaristaQueue[c.workerNumber/3])
                    maxBaristaQueue[c.workerNumber/3] = baristaQueue[c.workerNumber/3].size();
            }
        }
        // Leave the shop.
        else if(c.status == 4) {
            barista[c.workerNumber/3].isAvailable = true;
            turnoverTimes[c.registrationNumber] = c.queueTime-c.arrivalTime;

            // When a customer leaves the barista, another one comes in from the queue.
            if (!baristaQueue[c.workerNumber/3].empty()) {
                Customer c2(baristaQueue[c.workerNumber/3].top());
                baristaQueue[c.workerNumber/3].pop();
                barista[c.workerNumber/3].isAvailable = false;
                barista[c.workerNumber/3].runtime += c2.brewTime;
                c2.status = 4;
                c2.workerNumber = c.workerNumber;
                c2.queueTime = c.queueTime + c2.brewTime;
                eventQueue.push(c2);
            }

            if (eventQueue.size() == 0) {
                systemRunTime = c.queueTime - systemRunTime;
            }
        }
    }
}


//Runs the model one.
void Starbucks::runStarbucksModelOne() {
    this->systemRunTime = eventQueue.top().arrivalTime;

    while(!eventQueue.empty()) {
        Customer c = eventQueue.top();
        eventQueue.pop();

        // Assigning to a cashier or cashierQueue
        if(c.status == 1) {
            bool isPlaced = false;

            for(int i=0; i<num_of_cashier; i++) {
                if(cashier[i].isAvailable) {
                    cashier[i].isAvailable = false;
                    cashier[i].runtime += c.orderTime;

                    isPlaced = true;
                    c.status = 2;
                    c.workerNumber = i;
                    c.queueTime += c.orderTime;
                    eventQueue.push(c);
                    break;
                }
            }
            if(!isPlaced) {
                cashierQueue.push(c);
                if(cashierQueue.size() > this->maxCashierQueue)
                    this->maxCashierQueue = cashierQueue.size();
            }
        }

        else if (c.status == 2) {
            cashier[c.workerNumber].isAvailable = true;
            c.status = 3;
            eventQueue.push(c);

            if(!cashierQueue.empty()) {
                Customer c2(cashierQueue.top());
                cashierQueue.pop();
                cashier[c.workerNumber].isAvailable = false;
                cashier[c.workerNumber].runtime += c2.orderTime;
                c2.status = 2;
                c2.workerNumber = c.workerNumber;
                c2.queueTime = c.queueTime + c2.orderTime;
                eventQueue.push(c2);
                c.workerNumber = 0;
            }
        }

        else if (c.status == 3) {
            bool isPlaced = false;

            for(int i=0; i<num_of_barista; i++) {
                if(barista[i].isAvailable) {
                    barista[i].isAvailable = false;
                    barista[i].runtime += c.brewTime;

                    isPlaced = true;
                    c.status = 4;
                    c.workerNumber = i;
                    c.queueTime += c.brewTime;
                    eventQueue.push(c);
                    break;
                }
            }

            if(!isPlaced) {
                baristaQueue[0].push(c);
                if (baristaQueue[0].size() > maxBaristaQueue[0])
                    maxBaristaQueue[0] = baristaQueue[0].size();
            }
        }

        else if(c.status == 4) {
            barista[c.workerNumber].isAvailable = true;
            turnoverTimes[c.registrationNumber] = c.queueTime-c.arrivalTime;

            if (!baristaQueue[0].empty()) {
                Customer c2(baristaQueue[0].top());
                baristaQueue[0].pop();
                barista[c.workerNumber].isAvailable = false;
                barista[c.workerNumber].runtime += c2.brewTime;
                c2.status = 4;
                c2.workerNumber = c.workerNumber;
                c2.queueTime = c.queueTime + c2.brewTime;
                eventQueue.push(c2);
                c.workerNumber = 0;
            }

            if (eventQueue.size() == 0) {
                systemRunTime = c.queueTime - systemRunTime;
            }
        }
    }
}