/* elevator.cpp
 *
 * An elevator maintains a pool of occupants
 */


#include <iostream.h>
#include <math.h>
#include "elevator.H"
#include "person.H"
#include "floor.H"
#include "control.H"
#include "main.H"
#include "opengl.H"
#include "graphics.H"
#include "list.H"


/* Return the index of this elevator.
 */

int
Elevator::index()
{
    return elev_num;
}


/* Return a float which is the current floor position of this
 * elevator.  This is an integer if the elevator is at a floor;
 * between floors, the position is not an integer.
 *
 * The elevator accelerates at a constant rate `a' until the
 * midway point; then it decellerates at rate -a.  Velocity looks
 * like:
 *
 *             vmax    +
 *                    / \
 *                   /   \
 *                0 /     \
 *                  
 *                  +-----+
 *                     t
 * The distance travelled, x, is the integral of the velocity, which
 * is 1/2 t vmax.  But vmax occurs after accelerating by a for 1/2 t,
 * so vmax = 1/2 t a and therefore x = 1/4 a t^2.  So the time of the
 * journey is sqrt( 4 x / a ).  Below is a plot of number of floors 
 * (horizontal axis) versus time in seconds (vertical axis) for a = 0.2.
 *
 * gnuplot> plot [x=0:10] sqrt( 4 * x / (0.2**2) ) 
 *
 *     35 ++------------+------------+-------------+------------+------------++
 *        +             +            +             +            +             +
 *        |                                                                ****
 *     30 ++                                                        ******** ++
 *        |                                                   *******         |
 *        |                                             ******                |
 *     25 ++                                      ******                     ++
 *        |                                  *****                            |
 *        |                             *****                                 |
 *     20 ++                       *****                                     ++
 *        |                    *****                                          |
 *        |                *****                                              |
 *     15 ++            ***                                                  ++
 *        |          ***                                                      |
 *        |       ***                                                         |
 *     10 ++    ***                                                          ++
 *        |   **                                                              |
 *        |  *                                                                |
 *      5 +**                                                                ++
 *        |*                                                                  |
 *        +*            +            +             +            +             +
 *      0 *+------------+------------+-------------+------------+------------++
 *        0             2            4             6            8            10
 */


float
Elevator::floor_position()
{
    float total_dist, total_time, time_so_far, dist_so_far;
  
    total_dist  = next_floor->index() - last_floor->index();

    if (total_dist == 0)
	return last_floor->index();

    total_time  = sqrt( 4 * fabs(total_dist) / accel_rate );
    time_so_far = current_time - departure_time;

    if (time_so_far <= 0.5 * total_time)
	dist_so_far = 0.5 * time_so_far * (accel_rate * time_so_far);
    else
	dist_so_far = fabs(total_dist) - 0.5 * (total_time-time_so_far) *
	    (accel_rate * (total_time-time_so_far));

    if (total_dist >= 0)
	return last_floor->index() + dist_so_far;
    else
	return last_floor->index() - dist_so_far;
}


/* Return the array of current destinations.  This is known by the
 * controller, *not* by the elevator itself.
 */

int    
Elevator::is_a_destination( Floor *floor )
{
    return controller->is_a_destination( this, floor );
}


/* Return the current capacity of the elevator.
 */

int
Elevator::space_left()
{
    return capacity;
}


/* Add one person to the pool of occupants.
 */

void 
Elevator::add_person( Person *pers )
{
    if (capacity <= 0)
	cerr << "Elevator::add_person - capacity exceeded\n";
    else {
	occupants.add_data( pers );
	capacity--;
    }
}


/* Remove a person from the pool of occupants.
 */

void 
Elevator::remove_person( Person *pers )
{
    occupants.remove_data( pers );
    capacity++;
}


/* Signal all the occupants that the elevator has arrived at
 * a floor.
 */

void 
Elevator::signal_arrival_to_occupants()
{
    Lnode *n, *nn;

    /* Loop through the travelling persons */

    n = occupants.get_next(NULL); 
    while (n != NULL) {
	nn = occupants.get_next( n );
	(n->data)->signal_arrival( this, last_floor );
	n = nn;
    }
}


/* Handle the elevator arrival.  This notifies the occupants of the
 * arrival.
 */

void
Elevator::handle_arrival_at_floor( Floor *floor )
{
    controller->update_destinations( this, floor );
    last_floor = floor;
    signal_arrival_to_occupants();
}


/* Handle the departure.  This notifies the floor of the arrival so that
 * new people can enter the elevator.
 */

void
Elevator::handle_departure_from_floor()
{
    last_floor->signal_arrival_to_attendees( this );
}


/* Draw the elevator.  The front-lower-centre of the elevator appears
 * at (x,y,z).
 */

#define LFB  glVertex3f( ox, oy, oz )
#define LFT  glVertex3f( ox, oy+graphics.elev_height, oz )
#define RFT  glVertex3f( ox+graphics.elev_width, oy+graphics.elev_height, oz )
#define RFB  glVertex3f( ox+graphics.elev_width, oy, oz )
#define LBB  glVertex3f( ox, oy, oz-graphics.elev_depth )
#define LBT  glVertex3f( ox, oy+graphics.elev_height, oz-graphics.elev_depth )
#define RBT  glVertex3f( ox+graphics.elev_width, oy+graphics.elev_height, oz-graphics.elev_depth )
#define RBB  glVertex3f( ox+graphics.elev_width, oy, oz-graphics.elev_depth )

void
Elevator::draw( float x, float y, float z )
{
#ifdef GL

    float ox, oy, oz;
    float xoff, zoff;
    Lnode *n;

    ox = x - graphics.elev_width/2.0;
    oy = y; 
    oz = z;

    // Some elevator faces

    graphics.grey();
    glBegin( GL_POLYGON );	// back
    LBB; LBT; RBT; RBB;
    glEnd();

    graphics.black();
    glBegin( GL_LINE_LOOP );
    LBB; LBT; RBT; RBB;
    glEnd();

    graphics.grey();
    glBegin( GL_POLYGON );	// right
    RBB; RBT; RFT; RFB;
    glEnd();
    graphics.black();
    glBegin( GL_LINE_LOOP );
    RBB; RBT; RFT; RFB;
    glEnd();

    graphics.grey();
    glBegin( GL_POLYGON );	// bottom
    RFB; LFB; LBB; RBB;
    glEnd();
    graphics.black();
    glBegin( GL_LINE_LOOP );
    RFB; LFB; LBB; RBB;
    glEnd();

    // Draw persons

    /* Draw the occupants.  They're laid out in rows in the -z
     * direction, with the rows being laid out in the -x direction.
     */

    n = occupants.get_next(NULL);

    for (xoff = graphics.person_sep;
	 xoff < graphics.elev_width - graphics.person_sep * 0.5;
	 xoff += graphics.person_sep) {

	for (zoff = graphics.person_sep;
	     zoff < graphics.elev_depth - graphics.person_sep * 0.5;
	     zoff += graphics.person_sep) {

	    if (n == NULL)
		break;

	    (n->data)->draw(x + graphics.elev_width/2.0 - xoff, oy, z - zoff);

	    n = occupants.get_next(n);
	}

	if (n == NULL)
	    break;
    }
  
    /* Rest of elevator.  Don't display the left wall (to allow the user
     * to see inside).
     */

#if 0
    graphics.grey();
    glBegin( GL_POLYGON );	// left
    LBB; LFB; LFT; LBT;
    glEnd();
    graphics.black();
    glBegin( GL_LINE_LOOP );
    LBB; LFB; LFT; LBT;
    glEnd();
#endif

    graphics.grey();
    glBegin( GL_POLYGON );	// top
    LBT; LFT; RFT; RBT;
    glEnd();
    graphics.black();
    glBegin( GL_LINE_LOOP );
    LBT; LFT; RFT; RBT;
    glEnd();

    graphics.grey();
    glBegin( GL_TRIANGLE_STRIP );	// front
    RFB; 
    glVertex3f( x+graphics.elev_width/2.4, oy, z );
    RFT;
    glVertex3f( x+graphics.elev_width/2.4, oy+graphics.elev_height*0.9, z );
    LFT;
    glVertex3f( x-graphics.elev_width/2.4, oy+graphics.elev_height*0.9, z );
    LFB;
    glVertex3f( x-graphics.elev_width/2.4, oy, z );
    glEnd();
    graphics.black();
    glBegin( GL_LINE_LOOP );
    RFB; RFT; LFT; LFB;
    glVertex3f( x-graphics.elev_width/2.4, oy, z );
    glVertex3f( x-graphics.elev_width/2.4, oy+graphics.elev_height*0.9, z );
    glVertex3f( x+graphics.elev_width/2.4, oy+graphics.elev_height*0.9, z );
    glVertex3f( x+graphics.elev_width/2.4, oy, z );
    glEnd();

#endif
}

