/*
** COM S 414 -- System Programming and Operating Systems
** Assignment 2: Elevator Scheduling and Simulation
** 
** Name:
** NetID:
**/

#include <fstream>
#include <iostream>
#include <windows.h>
#include <cassert>

#include <vector>

using namespace std;

#define inputname  "input.data"
#define outputname "output.data"
#define MAX_FLOOR 1001

//
// prototypes
//
class Request;
void readinput(int& n, int&, vector<int>&, vector<int>&, vector<int>&, vector<int>&);
bool existPending(const vector<Request*>&);
DWORD WINAPI elevatorRun(LPVOID);

//
// GLOBAL VARIABLES -- for communication
//
int  clock = 0;      // incremented by control, then read when print output
vector<bool> tdone;  // set false by control to let elevators run, set true by elevators to let control runs.

//
// Each object is a request.
//
class Request
{
private:
    //
    // pass -- passenger
    // ploc -- passenger location
    // time -- request time
    // dest -- destination
    //
    int pass, ploc, time, dest;

    int status; // 0 is unassigned, 1 is waiting, 2 is in serve, 3 is done

public:
    Request(int ip, int il, int it, int id)
    {
        pass = ip;
        ploc = il;
        time = it;
        dest = id;
        status = 0;
    }

    bool valid()  
    { 
        return ::clock >= time; 
    }
    int getPass() { return (valid() ? pass : -1); }
    int getPLoc() { return (valid() ? ploc : -1); }
    int getTime() { return (valid() ? time : -1); }
    int getDest() { return (valid() ? dest : -1); }
    bool unassigned() { return valid() && status == 0; } // the request is not assigned to any elevator
    bool waiting()    { return valid() && status == 1; } // the request is assigned, but not served by the elevator
    bool inserve()    { return valid() && status == 2; } // the request is being served by the elevator
    bool isdone()     { return valid() && status == 3; } // the request has been served by the elevator (done)
    bool istopick(int loc, int dir) { return valid() && waiting() && (ploc == loc) && ((dest - ploc)*dir > 0); } // the request (passenger) is to be picked up, given the current location and direction
    bool istodrop(int loc)          { return inserve() && dest == loc; } // the request (passenger) is to be dropped off, given the location of the elevator serving it
    void assign() { assert(valid()); status = 1; } // change the status from unassigned to waiting
    void pick()   { assert(valid()); status = 2; } // change the status from waiting to in-serve
    void drop()   { assert(valid()); status = 3; } // change the status from in-serve to done
};

//
// Each object is the state of an elevator.
//
class EleState
{
private:
    //
    // id -- elevator id
    // loc -- current location (floor) of the elevator
    // dir -- direction the elevator is traveling. Convention: -1 for down, 1 for up, 0 for unknown
    //
    int id, loc, dir;
    vector<Request*> req;

public:

    EleState(int i, int l) : id(i), loc(l), dir(0), req()  {}
    
    //
    // Print out the state of the elevator, including the 
    // current location and the list of passengers it's carrying.
    // In the format: l_i { p_1 p_2 ... }
    //
    // This is used only by the control (main) thread.
    //
    void print(ostream& out)
    {
        size_t s = 0;
        s = req.size();
        out << loc << " { ";
        for (size_t i=0; i<s; i++) 
        {
            assert(req.at(i) != NULL);
            if (req.at(i)->inserve()) 
                out << req.at(i)->getPass() << " ";
        }
        out << "}";
    }

    int  getID()  { return id; }
    int  getLoc() { return loc; }
    int  getDir() { return dir; }
    const vector<Request*>& getReq() { return req; }

    void incLoc() { loc++; }
    void decLoc() { loc--; }
    void setDir(int d)      { dir = d; }           // set the direction of traveling.
    void addReq(Request* r) { req.push_back(r); }  // add a request to the elevator

    bool goingUp()   { return dir == 1; }  // is the elevator going up?
    bool goingDown() { return dir == -1; } // is the elevator going down?

    //
    // given the current location, drop all passengers that need to get off at this location
    // i.e., their destination is the current location
    //
    void drop(int curLoc)
    {
        size_t s = 0;

        s = req.size();
        for (size_t i=0; i<s; i++)
            if (req.at(i)->istodrop(curLoc))
                req.at(i)->drop();
    }

    //
    // given the current location and direction of traveling, 
    // pick up all passengers that are on the current location and need to go in this direction
    // i.e., their location is the current location, and their destination in the direction traveling.
    //
    void pick(int curLoc, int dir)
    {
        size_t s = 0;

        s = req.size();
        for (size_t i=0; i<s; i++)
            if (req.at(i)->istopick(curLoc, dir)) 
                req.at(i)->pick();
    }

    //
    // the elevator is serving no request.
    //
    bool idle()
    {
        size_t s = 0;

        s = req.size();
        for (size_t i=0; i<s; i++)
            if (req.at(i)->inserve()) return false;
        return true;
    }
};

//
// All scheduling functions should be here.
//
// C++ specific: a static method is called with a double colon (::) instead of
//               a dot in Java. See the use of dispatch in main() for instance.
//
class Scheduler
{
public:

    //
    // Given the list of requests, and the list of elevator,
    // dispatch all valid requests to the elevators.
    // 
    // Dispatching a request of index i in vector req to 
    // elevator j in vector elevator can be written as:
    //     elevator.at(j)->addReq(req.at(i));
    //     req.at(i)->assign();
    // where the second statement is to mark the request has been assigned.
    //
    // C++ specific: 
    //     - the first param is similar to this in java: Vector<Request> req
    //       except that it is read-only.
    //     - the second param is the same as this in java: Vector<EleState> elevator
    //
    static void dispatch(const vector<Request*>& req, vector<EleState*>& elevator)
    {
        //
        // FILL IN HERE
        //
    }
};

//
// read input from "input.data".
// return n,m,e,p,t,d as specified in the assignment.
//
void readinput(int& n, int& m, vector<int>& e, vector<int>& p, vector<int>& t, vector<int>& d)
{
    ifstream infile(inputname);

    if (infile.is_open())
    {
        int i = 0;
        
        infile >> n >> m;
        e.resize(n);
        p.resize(m);
        t.resize(m);
        d.resize(m);
        for (i=0; i<n; i++)
        {
            infile >> e[i];
        }
        for (i=0; i<m; i++)
        {
            infile >> p[i] >> t[i] >> d[i];
        }
        infile.close();
    }
    else cout << "Unable to open file '" << inputname << "'"; 
}

//
// Exist a request that is in serve or waiting.
//
bool existPending(const vector<Request*>& r)
{
    size_t s = 0;

    s = r.size();
    for (size_t i=0; i<s; i++)
    {
        if (!r[i]->isdone()) return true;
    }
    return false;
}

//
// This will be run by the threads.
// You don't need to worry about the output.
// Just make sure that the state is updated correctly,
// including: the location of the elevator, and the list of passengers.
// The list of passengers is computed as all the requests (that has been assigned to the elevator)
// which is being served.
//
DWORD WINAPI elevatorRun(LPVOID param)
{
    EleState& state = *((EleState*)param); // 'state' links to the appropriate element of vector estate in main.

    // cout << "Thread " << state.getID() << " started..." << endl; // this print out could crash --- not safe for threads!

    while (true)
    {
        while (tdone[state.getID()]) Sleep(1); // spinlock waiting: prevent this thread to run, until signaled by the control (main) thread

        //
        // FILL IN HERE: calculate the new state of the elevator and update variable 'state'.
        //

        tdone[state.getID()] = true; // signal the control (main) thread that it may run.
    }

    return 0;
}

int main()
{
    const EleState* nullPtr = NULL;
    int n, m;
    vector<int> e, p, t, d;
    vector<Request*> req;
    
    int i = 0;
    
    readinput(n, m, e, p, t, d); // read raw input
    for (i=0; i<m; i++)      // put input into request vector
    {
        req.push_back(new Request(i, p[i], t[i], d[i]));
    }
    tdone.resize(n, true); // at first, n elevators are ready.

    vector<DWORD>  ThreadID(n);
    vector<HANDLE> ThreadHandle(n);
    vector<EleState* > estate(n);

    //
    // Start threads
    //
    for (i=0; i<n; i++)
    {
        estate[i] = new EleState(i, e[i]);
        ThreadHandle[i] = CreateThread( NULL,
                                        0,
                                        elevatorRun,
                                        estate[i],
                                        0,
                                        &ThreadID[i]);
        assert(ThreadHandle[i] != NULL);
    }

    ofstream outfile(outputname);
    assert(outfile.is_open());

    do {
        for (i=0; i<n; i++)
        {
            while (!tdone[i]) Sleep(1); // wait for all elevators to be ready
        }

        outfile << ::clock << " ";
        for (i=0; i<n-1; i++)
        {
            estate[i]->print(outfile);
            outfile << " ";
        }
        estate[n-1]->print(outfile);
        outfile << endl;

        ::clock++;

        Scheduler::dispatch(req, estate);

        for (i=0; i<n; i++) tdone[i] = false; // signal the elevators that they may run.

    } while (existPending(req));

    outfile.close();

    //
    // Terminate threads, close handles,
    // and free memory of the states of the elevators.
    //
    for (i=0; i<n; i++)
    {
        TerminateThread(ThreadHandle[i], 0); // TerminateThread is dangerous to use, unless you know what the thread is doing.
        CloseHandle(ThreadHandle[i]);

        delete estate[i];
    }

    for (i=0; i<m; i++)
        delete req[i]; // free memory of the request vector

    return 0;
}
