/*------------------------------------------------------------------------------
Description: Controller classes for elevator project
Author: Chris Huyler
Language: C++
Name: controller.h
Date: April 30, 2000
------------------------------------------------------------------------------*/
#include "elevator.h"
#include "CLOCK.H"
#include "list.h"
#include "Sim.h"


class ElevatorController {
	public:
    	ElevatorController(int floors,int elevators,int rpm);
		void run(int time);
	private:
		bool ControlLoopCD(int e, int f,int i);    // loop to change direction if necessary
		void ControlLoopWait(int e, int f,bool eb,bool fb,int i); // waiting loop
		void ControlLoopIdle(int i);              // idle loop
		void ControlLoopMove(int i);              // move loop
		void Set_FloorButtons();             // initialize floor buttons
		void TickClock();                    // incriment floor
		void Get_FloorButtonPress();
		void Get_ElevatorButtonPress(int fbID,int i);
      int Schedule(int fbID);
      Sim sim;
		Clock timer;
		Log *ControlLog;
		Elevator E[20];
		int timeout[20];				// timer for idle movement
		int fnum;					// number of floors in simulation
      int evnum;					// number of elevators in simulation
		fbutton Buttons[200];
      list<int> junklist;
 		list<int> FloorList[20];   // ordered linked list of requested floors
		list<int> ElevatorList[20]; // ordered linked list of requested elevator stops
		bool isWaiting[20];

};


ElevatorController::ElevatorController(int floors,int elevators,int rpm)
{
   ControlLog = Log::Instance();
   sim.set_rpm(rpm);
   fnum = floors;
   evnum = elevators;
	srand(time(NULL));
   Set_FloorButtons();
	for(int i=0;i<elevators;i++)
   {
      E[i].set_ID(i);
		isWaiting[i] = false;
	}
}


void ElevatorController::Set_FloorButtons()
{
    // first floor has no down button special case
   	Buttons[0].set_floor(0);
   	Buttons[0].set_dir("up");

   	for(int i=1;i<((fnum-1)*2-2);i++)
   	{            							// middle floors
      	Buttons[i].set_floor((i+1)/2);
      	if((i%2)==1)
      		Buttons[i].set_dir("down");
      	else
      		Buttons[i].set_dir("up");
	}

	// last floor has no up button
   	Buttons[(fnum-1)*2-1].set_floor(fnum-1);
   	Buttons[(fnum-1)*2-1].set_dir("down");
}

void ElevatorController::run(int time)
{
	timer.Set_time();
   bool done = false;
   while(!done)
	{
   	TickClock();
      // requests are only made durring user set time
      // after that, elevators clear lists with no more inputs
      if(time > timer.current_time())
      	Get_FloorButtonPress();

		for(int i=0;i<evnum;i++)
     	{
      	if(!E[i].moving())
      		if(E[i].get_dir() != "idle")
         		ControlLoopMove(i);
        		else
         		ControlLoopIdle(i);
		}

      // program does not exit until all elevators are idle
      if(time <= timer.current_time())
      {
      	done = true;
         for(int i=0;i<evnum;i++)
         	if(E[i].get_dir() != "idle")
            	done = false;
      }
   }
}

void ElevatorController::ControlLoopMove(int i)
{

   int e = E[i].get_curr();   	// current floor for elevator button list checks
   int f =  e*2;   				// current floor id for floor button list checks
   if((E[i].get_dir() == "down") && (f !=0))
   		f--;

   bool eb = ElevatorList[i].remove(e);  // 'true' if the floor was removed
   bool fb = false;
   for(int j=0;j<evnum;j++)
   	if(FloorList[i].remove(f))     // 'true' if the floor was removed
      	fb = true;                       // from any list of floors

   if(!eb && !fb && !isWaiting[i])
	{
   /* Elevator will only move once it is done waiting, no buttons have recently been
      removed from the button list, and the direction was not recently changed.
      note: if the direction was changed, the loop must run again to check for the
      floor button in the new direction.              */

     	if(!ControlLoopCD(e,f,i))
   			E[i].move();
   	}
   	else
		ControlLoopWait(e,f,eb,fb,i);
}

bool ElevatorController::ControlLoopCD(int e, int f,int i)
{
   	bool CD = false;        // bool to return whether direction has been changed
	if(E[i].get_dir() == "up")
	{
   		if(ElevatorList[i].isBack(e) && FloorList[i].isBack(f))
		{
	   		if(!ElevatorList[i].isEmpty() || !FloorList[i].isEmpty())
	    		E[i].set_dir("down");
	    	else
	    	  	if(!isWaiting[i])     /*  when both lists are empty and the elevator
                                        is done waiting, direction becomes 'idle' */
	   	 E[i].set_dir("idle");
	   		CD = true;
		}
   	}
	else
   	if(E[i].get_dir() == "down")
      	if(ElevatorList[i].isFront(e) && FloorList[i].isFront(f))
      	{
      		if(!ElevatorList[i].isEmpty() || !FloorList[i].isEmpty())
	      	   	E[i].set_dir("up");
	      	else
	      		if(!isWaiting[i])
	      	   	E[i].set_dir("idle");
	   		CD = true;
		}
	return(CD);
}

void ElevatorController::ControlLoopWait(int e, int f,bool eb,bool fb,int i)
{
	if(!isWaiting[i])
   	{
   		isWaiting[i] = true;
      	E[i].stop();
   	}
   	else
   		if(!E[i].waiting())  isWaiting[i] = false;
   	if(eb)
   		E[i].unload();
   	if(fb)
   	{
   		E[i].load();
    	Buttons[f].dim();
      	Get_ElevatorButtonPress(f,i);
   	}
}


void ElevatorController::ControlLoopIdle(int i)
{
   	int currentFloor = E[i].get_curr();
   	int next, closest = 100;
	if(!FloorList[i].isEmpty())
   	{
      	timeout[i] = 0;
   		FloorList[i].setIterator();
      	while(FloorList[i].more())
      	{
      		next = FloorList[i].next();      // find closest floor
      		if(abs(next-currentFloor) < abs(closest-currentFloor))
         		closest = next;
      	}
      	if(closest < currentFloor)   // set direction to go to closest floor
      	{
      	E[i].set_dir("down");
      	E[i].move();
      	}
      	else 	if (closest > currentFloor)
      			{
            		E[i].set_dir("up");
      				E[i].move();
            	}
      			else
            		E[i].set_dir("up");
   	}
   	else
   	{      // idle timeout movement to first floor
   		timeout[i]++;
      	if(E[i].get_curr() > 0 && timeout[i] > 30)
   		{//  cout << "\n(IDLE)" << i;
      		E[i].set_dir("down");
         	E[i].move();
         	E[i].set_dir("idle");
      	}
	}
}

void ElevatorController::TickClock()
{
	timer.tick();
   ControlLog->Log_Time(timer.current_time());
}


void ElevatorController::Get_FloorButtonPress()
{
   int fbID = sim.Generate_FloorButtonPress(fnum);
   if(fbID != -1)
   {
   	  int i = Schedule(fbID);    // floors are scheduled once button is pressed
      Buttons[fbID].press();
      FloorList[i].remove(fbID);		   // makes sure that only one instance
      FloorList[i].insert(fbID);          // of a floor is in the list
   }
}

void ElevatorController::Get_ElevatorButtonPress(int fbID,int i)
{
	int ebID;
  	bool PressAnother = true;
	while(PressAnother)
	{   // generate button press from Sim
  		ebID = sim.Generate_ElevatorButtonPress(fnum,fbID);
		if(!E[i].get_lit(ebID))
      	{
      		E[i].press(ebID);
         	ElevatorList[i].remove(ebID);		// makes sure that only one instance
         	ElevatorList[i].insert(ebID);    // of a floor is in the list
      	}
      	if(rand() % 100 < 80)        // if multiple people get on and press different buttons
      		PressAnother = false;
	}
}

int ElevatorController::Schedule(int fbID)
{
    int e = 0;
    bool found;
    string dir;
    if(fbID %2 == 1)
    	dir = "down";
    else
    	dir = "up";
    fbID = (fbID+1)/2;

    // first check for elevator that is moving in same direction
	for(int i=0;i<evnum;i++)
    	// same direction
    	if(E[i].get_dir() == dir)
        	// will pass the floor without changing direction
        	if((dir == "down" && E[i].get_curr() <= fbID) ||
               (dir == "up"   && E[i].get_curr() >= fbID))
            // closer than the previous elevator chosen
        	if(abs(fbID - E[i].get_curr()) <= abs(fbID - E[e].get_curr()))
            {
            	e = i;
                found = true;
            }

    // otherwise look for an idle elevator
	if(!found)
    	for(int i=0;i<evnum;i++)
        	if(E[i].get_dir() == "idle")
            	if(abs(fbID - E[i].get_curr()) <= abs(fbID - E[e].get_curr()))
                {       // choose closest elevator
                	e = i;
                    found = true;
                }

    // otherwise look for the closest elevator moving in wrong direction,
    // but will pass by the floor
	if(!found)
    	for(int i=0;i<evnum;i++)
         if((dir == "down" && E[i].get_curr() <= fbID) ||
            (dir == "up"   && E[i].get_curr() >= fbID))
				if(abs(fbID - E[i].get_curr()) <= abs(fbID - E[e].get_curr()))
            {
            	e = i;
               found = true;
            }

    // last resort assign to closest elevator
   if(!found){
      getch();
    	for(int i=0;i<evnum;i++)
				if(abs(fbID - E[i].get_curr()) <= abs(fbID - E[e].get_curr()))
            {
            	e = i;
               found = true;
            }
   }
	return(e);
}

