/**************************************************************************
 *
 * icecream.cpp - Priority queue example program.
 *
 * $Id$
 *
 ***************************************************************************
 *
 * Licensed to the Apache Software  Foundation (ASF) under one or more
 * contributor  license agreements.  See  the NOTICE  file distributed
 * with  this  work  for  additional information  regarding  copyright
 * ownership.   The ASF  licenses this  file to  you under  the Apache
 * License, Version  2.0 (the  "License"); you may  not use  this file
 * except in  compliance with the License.   You may obtain  a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the  License is distributed on an  "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY  KIND, either  express or
 * implied.   See  the License  for  the  specific language  governing
 * permissions and limitations under the License.
 *
 * Copyright 1994-2006 Rogue Wave Software.
 * 
 **************************************************************************/

#include <algorithm>   // for random_shuffle()
#include <iostream>    // for cout
#include <queue>       // for queue
#include <vector>      // for vector

#include <examples.h>


// Execution event in a descrete event driven simulation.
class event {
public:
    // Construct sets time of event.
    event (unsigned int t) : time (t)
        { }

    // Execute event by invoking this method.
    virtual void processEvent () = 0;

    const unsigned int time;
};

struct eventComparator {
    bool operator() (const event * left, const event * right) const {
        return left->time > right->time;
    }
};


// Framework for discrete event-driven simulations.
class simulation {
public:
    simulation () : time (0), eventQueue () 
        {}
    void run ();
    void  scheduleEvent (event * newEvent) {
        eventQueue.push (newEvent);
    }
    unsigned int time;
protected:
    std::priority_queue<event*,
                        std::vector<event *, std::allocator<event*> >,
                        eventComparator> eventQueue;
};


void simulation::run () {

    while (! eventQueue.empty ()) {

        event * nextEvent = eventQueue.top ();
        eventQueue.pop ();
        time = nextEvent->time;
        nextEvent->processEvent ();
        delete nextEvent;
    }
}


//  Ice cream store simulation.
class storeSimulation : public simulation {
public:
    storeSimulation () : simulation (), freeChairs (35), profit (0.0)
        { }
    bool canSeat (unsigned int numberOfPeople);
    void order   (unsigned int numberOfScoops);
    void leave   (unsigned int numberOfPeople);
    // Data fields.
    unsigned int freeChairs;
    double       profit;  
} theSimulation;


class arriveEvent : public event {
public:
    arriveEvent (unsigned int t, unsigned int groupSize)
        : event (t), size (groupSize)
        { }
    virtual void processEvent ();
private:
    unsigned int size;
};


class orderEvent : public event {
public:
    orderEvent (unsigned int t, unsigned int groupSize)
        : event (t), size (groupSize)
        { }
    virtual void processEvent ();
private:
    unsigned int size;
};


class leaveEvent : public event
{
public:
    leaveEvent (unsigned int t, unsigned int groupSize)
        : event (t), size (groupSize)
        { }
    virtual void processEvent ();
private:
    unsigned int size;
};


// returns a random integer between 0 and n with n <= 32767 <= INT_MAX
int irand (int n)
{
    static int seq[] = {
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf
    };

    std::random_shuffle (seq, seq + sizeof seq / sizeof *seq);

    const int rnd =
        ((seq [0] << 11) | (seq [1] << 8) | (seq [2] << 4)) + seq [3];

    return rnd % n;
}


void arriveEvent::processEvent () {

    if (theSimulation.canSeat (size))
        theSimulation.scheduleEvent
            (new orderEvent (time + 1 + irand (4), size));
}


void orderEvent::processEvent () {

    // Each person orders some number of scoops.
    for (unsigned int i = 0; i < size; i++)
        theSimulation.order (1 + irand (4));

    // Then we schedule the leave event.
    theSimulation.scheduleEvent
        (new leaveEvent (time + 1 + irand (10), size));
}


void leaveEvent::processEvent () {

    theSimulation.leave (size);
}


// If sufficient room then seat customers.
bool storeSimulation::canSeat (unsigned int numberOfPeople) {

    std::cout << "Time: " << time;
    std::cout << " group of " << numberOfPeople << " customers arrives";

    if (numberOfPeople < freeChairs) {
        std::cout << " is seated\n";
        freeChairs -= numberOfPeople;
        return true;
    }
    else {
        std::cout << " no room, they leave\n";
        return false;
    }
}


// Service icecream, compute profits.
void storeSimulation::order (unsigned int numberOfScoops) {

    std::cout << "Time: " << time << " serviced order for "
              << numberOfScoops << '\n';
    profit += 0.35 * numberOfScoops;
}


// People leave, free up chairs.
void storeSimulation::leave (unsigned int numberOfPeople) {

    std::cout << "Time: " << time << " group of size "
              << numberOfPeople << " leaves\n";
    freeChairs += numberOfPeople;
}


int main () {

    std::cout << "Ice Cream Store simulation from Chapter 9\n";

    // Load queue with some number of initial events.
    for (unsigned t = 0; t < 20; t += irand (6)) {

        std::cout << "pumping queue with event " << t << '\n';
        theSimulation.scheduleEvent (new arriveEvent (t, 1 + irand (4)));
    }

    // Run the simulation.
    theSimulation.run ();

    std::cout << "Total profits " << theSimulation.profit
              << "\nEnd of ice cream store simulation\n";

    return 0;
}
