/* train.C
 */


#include <iostream.h>
#include "defs.h"
#include "train.h"
#include "controller.h"
#include "section.h"
#include "platform.h"
#include "passenger.h"
#include "station.h"
#include "events.h"
#include "scheduler.h"
#include "main.h"
#include "xy.h"

#ifdef GL
  #include <GL/gl.h>
#endif


int Train::nextIdent = 0;	// shared by all trains, for generating idents


// Return true if the train will be going to a particular station, s.

bool
Train::isStoppingAtStation( Station *s )

{
  for (int i=nextStationIndex; i<path.size(); i++)
    if (path[i].type == STATION  &&  path[i].station == s)
      return true;

  return false;
}


// Add a passenger to the train

void
Train::addPassenger( Passenger *p )

{
  if (!spaceLeft()) {
    cerr << "Error: tried to add passenger to full train " << *this << ".\n";
    abort();
  }

  passengers.add( p );
}


/* Signal the train to advance for a certain amount of time.  The
 * train calculates its new position and speed, updates the track
 * sections that it's occupying, and determines a new acceleration.
 */

void
Train::advance()

{
  float deltaTime = currentTime - lastUpdateTime;
  float positionChange;
  Platform *platform;

  // Calcuate change in position
  
  if (platformStatus == WAITING)
    positionChange = 0;
  else {
    positionChange = deltaTime * (speed + 0.5 * acceleration * deltaTime);
    if (positionChange < 0)
      positionChange = 0;
  }

  // Update the current track sections covered by the train, based
  // upon the positionChange.

  updatePositionInSections( positionChange );

  // Calculate new speed

  speed += acceleration * deltaTime;
  if (speed < 0)
    speed = 0;

  // Determine new acceleration.  

  acceleration = calcNewAcceleration();

  // If the train has arrived at a platform, disembark the passengers,
  // load passengers from the platform, inform the controller, and
  // then wait.

  platform = sections[ sections.size()-1 ]->platform();
  
  if (platform != NULL &&
      speed < .01 &&
      platformStatus == ARRIVING &&
      path[nextStationIndex].type == STATION &&
      platform->thisStation() == path[nextStationIndex].station) {

    int numPassengers;
    TrainDepartureEvent *e;

    if (outputEvents)
      cout << currentTime << ": " << *this << " stopped at platform " << *platform << endl;

    numPassengers = disembarkPassengers( platform->thisStation() );
    numPassengers += platform->countBoardingPassengers( this );

    platformStatus = WAITING;

    controller.trainIsWaitingOnPlatform( this, platform, numPassengers );

    // Schedule a departure event, which will occur after the passengers
    // have had time to disembark and enter the train.  There's a fixed
    // cost, LOADING_TIME, per stop, plus a small cost per passenger.
    // The passengers are actually loaded after this time has elapsed.

    e = new TrainDepartureEvent( currentTime + LOADING_TIME + numPassengers * LOADING_TIME_PER_PASSENGER,
				 this );
    scheduler.addEvent( e );

  } else

    // If the next path element is a switch, and the train is in the
    // section before the switch, signal to the controller that the
    // train wants to take the switch.  The controller will set the
    // track status appropriately.

    if (path[ nextStationIndex ].type == BRANCH  &&
	path[ nextStationIndex ].branch == currentSection()->nextBranchSection())

      if (controller.switchToBranch( this )) {

	nextStationIndex++;
	if (nextStationIndex >= path.size())
	  nextStationIndex = 0;
      }
  
  // remember the last time the train advanced

  lastUpdateTime = currentTime;
}


/* The controller calls this function to signal the train to depart
 * from the platform.  This is called when enough time has elapsed to
 * allow people to disembark and to board.
 */

void
Train::departFromPlatform()

{
  Station *nextStation;

  // update next destination

  nextStationIndex++;
  if (nextStationIndex >= path.size())
    nextStationIndex = 0;

  // find the next path element

  if (path[nextStationIndex].type == REVERSE) {

    TrainDepartureEvent *e;

    // Reverse direction

    reverseDirection();

    // The controller must be informed.

    controller.trainIsReversing( this );

    // Reschedule the departure.  Enough time has to elapse for the
    // driver to walk from one end of the train to the other

    e = new TrainDepartureEvent( currentTime + REVERSING_TIME, this );
    scheduler.addEvent( e );

  } else {

    // Load the passengers from the platform

    sections[0]->platform()->signalTrainArrival( this );

    // Say we're leaving

    if (outputEvents)
      cout << currentTime << ": " << *this << " is leaving "
	   << *sections[sections.size()-1]->platform() << "\n";
    
    // Update status and depart

    platformStatus = LEAVING;
  
    advance();
  }
}


/* For each passenger that wants to disembark at this station,
 * transfer them to the station.
 */


int
Train::disembarkPassengers( Station *s )

{
  int passengerCount = 0;
  Platform *p;

  if (outputEvents)
    cout << currentTime << ": " << *this << " unloads";

  for (int i=0; i<passengers.size(); i++)

    if (passengers[i]->wantsToLeaveTrain( s )) {

      if (outputEvents)
	cout << " " << *passengers[i];

      s->receivePassenger( passengers[i] );
      passengers.remove(i);
      i--;
      passengerCount++;
    }

  if (outputEvents)
    cout << endl;
  
  return passengerCount;
}


/* Reverse the train.  This involves updating the sections occupied by
 * the train and the distances remaining in the front and rear
 * sections.
 */


void
Train::reverseDirection()

{
  float newFrontDist;
  int size = sections.size();

  // Reverse the order of the sections covered by the train

  for (int i=0; i<size/2; i++) {
    Section *tempSection;

    tempSection = sections[i];
    sections[i] = sections[size-i-1];
    sections[size-i-1] = sections[i];
  }

  for (int i=0; i<size; i++)    
    sections[i]->reverseOccupiedSection();

  // Update the front and rear distance

  newFrontDist = sections[size-1]->length() - rearDistRemaining;
  rearDistRemaining = sections[0]->length() - frontDistRemaining;
  frontDistRemaining = newFrontDist;
}


/* Update the current track sections covered by the train, based upon
 * the positionChange.  The train rear covers sections[0], while the
 * train front covers sections[ sections.size()-1 ].  */


void
Train::updatePositionInSections( float positionChange )

{
  while (positionChange > 0) {

    float dist;

    dist = MIN( frontDistRemaining, rearDistRemaining ); // distance to next section

    if (dist > positionChange) {

      // --- train won't get to next section with positionChange ---

      frontDistRemaining -= positionChange;
      rearDistRemaining -= positionChange;
      positionChange = 0;

    } else if (dist == frontDistRemaining) {

      // --- train leaves front section first ---

      Section *next;		// next section entered by train

      rearDistRemaining -= dist;
      positionChange -= dist;

      next = nextSection( currentSection() );
      next->trainIsEnteringSection( this, currentSection() ); // inform section that train enters
      frontDistRemaining = next->length();

      if (next->platform() != NULL) // next section has a platform?
	platformStatus = ARRIVING;

      sections.add( next );

    } else {			

      // --- train leaves rear section first ---

      Section *prev = sections[0];

      frontDistRemaining -= dist;
      positionChange -= dist;

      sections.remove( 0 );	// remove last section

      // To avoid accumulation of error, calculate the position in the
      // rear section from scratch

      rearDistRemaining = length; // total train length
      for (int i=1; i<sections.size(); i++) // remove lengths of occupied track sections
	rearDistRemaining -= sections[i]->length();
      rearDistRemaining += frontDistRemaining; // removed too much of front section

      prev->trainIsLeavingSection( this ); // inform section that train leaves
    }
  }
}


/* Determine the next section the train enters after section s.
 * Exactly one of the next main section or next branch section must
 * not be RED.
 */


Section *
Train::nextSection( Section *s )

{
  Section *main   = s->nextMainSection();
  Section *branch = s->nextBranchSection();
  Section *next;

  // Find the next section

  if (main != NULL && main->status() != RED) { // check main section

    if (branch != NULL && branch->status() != RED) { // both main and branch sections allowed!
      cerr << "Error: Train " << *this << " in section " << *s
	   << " can enter EITHER main section " << *main
	   << " or branch section " << *branch << ".\n";
      abort();

    } else

      next = main;

  } else if (branch != NULL && branch->status() != RED) // check branch section

    next = branch;

  else {			// neither main nor branch section is allowed!

    cerr << "Error: Train " << *this << " in section " << *s
	 << " cannot enter either main section " << *main
	 << " or branch section " << *branch << ".\n";
    abort();
  }

  return next;
}


/* Update the train's acceleration.  This depends upon the status of
 * the next track section and whether it is arriving at or leaving a
 * platform.  These are several situations:
 *
 * Not in a platform section: Set acceleration so that, when the train
 *   arrives at the next section, its speed is equal to the maximum
 *   speed for that section.  Acceleration is clamped to some [min,max].
 *
 * In a platform section:
 *   If WAITING, do nothing.
 *   If SLOWING, set acceleration so that train has zero speed when it
 *     reaches the middle of the section.
 *   If LEAVING, set acceleration as though not in a platform section.
 */

float
Train::calcNewAcceleration()

{
  Section *thisSection, *main, *branch;
  SectionStatus mainStatus, branchStatus, nextStatus;
  float desiredSpeed, distanceRemaining;
  int i;

  main   = sections[ sections.size()-1 ]->nextMainSection();
  branch = sections[ sections.size()-1 ]->nextBranchSection();

  mainStatus   = (main == NULL) ? RED : main->status();
  branchStatus = (branch == NULL) ? RED : branch->status();

  // If we want to take the next switch, use its status only

  if (path[ nextStationIndex ].type == BRANCH  &&
      path[ nextStationIndex ].branch == currentSection()->nextBranchSection()) {

    nextStatus = branchStatus;

  } else {

    // Determine next section status.  Pick the least restrictive of the
    // statuses in the next main and branch sections

    if (mainStatus == GREEN || branchStatus == GREEN)
      nextStatus = GREEN;
    else if (mainStatus == YELLOW || branchStatus == YELLOW)
      nextStatus = YELLOW;
    else
      nextStatus = RED;
  }

  thisSection = sections[ sections.size()-1 ];

  // Update acceleration

  if (thisSection->platform() == NULL ||
      platformStatus == LEAVING ||
      path[ nextStationIndex ].type != STATION ||
      thisSection->platform()->thisStation() != path[ nextStationIndex ].station) {

    // If not at platform, or if at platform but LEAVING

    switch (nextStatus) {
    case RED:    desiredSpeed = RED_SPEED;    break;
    case YELLOW: desiredSpeed = YELLOW_SPEED; break;
    case GREEN:  desiredSpeed = GREEN_SPEED;  break;
    }    
    distanceRemaining = frontDistRemaining;

  } else if (platformStatus == ARRIVING) {

    // If arriving at platform, stop in centre of track section

    distanceRemaining = frontDistRemaining - (thisSection->length() - length) * 0.5;

    if (distanceRemaining < TRAIN_LENGTH / 2.0)
      desiredSpeed = 0;
    else
      desiredSpeed = YELLOW_SPEED;

  } else {

    // If waiting at platform, do nothing

    desiredSpeed = 0;
    distanceRemaining = 0;
  }

  return computeNecessaryAccel( speed, desiredSpeed, distanceRemaining );
}


/* Compute the constant acceleration necessary to change speed by
 * deltaSpeed within distance deltaDistance.
 *
 * Let v0, a = initial speed, acceleration
 * Let dp, dv, dt = change in position, change in speed, change in time
 *
 * Then   dv = a dt   and   dp = v0 dt + 1/2 a dt^2
 *
 * Solve for dt: dt = dp / (v0 + 1/2 dv)
 * Solve for a:  a = dv * (v0 + 1/2 dv) / dp
 */

float
Train::computeNecessaryAccel( float currentSpeed, float desiredSpeed, float distance )

{
  float accel;
  float dv = desiredSpeed - currentSpeed;

  if (dv == 0)
    return 0;

  // Ran out of space (usually at a platform)

  if (distance == 0)
    if (dv > 0)
      return MAX_TRAIN_ACCEL;
    else
      return MIN_TRAIN_ACCEL;

  accel = dv * (currentSpeed + 0.5 * dv) / distance;

  if (accel > MAX_TRAIN_ACCEL)
    accel = MAX_TRAIN_ACCEL;
  else if (accel < MIN_TRAIN_ACCEL)
    accel = MIN_TRAIN_ACCEL;

  return accel;
}


/* Draw the train
 */


void
Train::draw()

{
#ifdef GL
  xyCoord front, rear, centre, forward, right, v1, v2, v3, v4, temp;
  Section *s;
  float dx, dy, distRemaining, fractionFull, peoplePerCar, peopleToDraw;
  int sectionIndex;

  peopleToDraw = passengers.size();
  peoplePerCar = TRAIN_CAPACITY / (float) CARS_PER_TRAIN;

  // Find rear of train

  distRemaining = rearDistRemaining;
  sectionIndex = 0;

  s = sections[ sectionIndex ];
  rear.x = s->x0 + (1 - (distRemaining / s->length())) * (s->x1 - s->x0);
  rear.y = s->y0 + (1 - (distRemaining / s->length())) * (s->y1 - s->y0);

  front = rear;			// for first iteration of loop

  // Treat each car

  for (int i=0; i<CARS_PER_TRAIN; i++) {

    rear = front;		// front previous iteration

    // Find track section at front of this car
    
    distRemaining -= length / CARS_PER_TRAIN;
    while (distRemaining < 0) {
      sectionIndex++;
      distRemaining += sections[ sectionIndex ]->length();
    }

    // Find front of this car

    s = sections[ sectionIndex ];
    front.x = s->x0 + (1 - (distRemaining / s->length())) * (s->x1 - s->x0);
    front.y = s->y0 + (1 - (distRemaining / s->length())) * (s->y1 - s->y0);

    // Draw one car with 'front' and 'rear'

    centre = (front + rear) * 0.5;
    forward = (front - rear);
    forward.normalize();
    right.x = forward.y;
    right.y = -forward.x;
    right.normalize();

    // Draw black background underneath car

    dx = 4;			// width of train (4 metres!) for drawing
    dy = length / 2.0 / (CARS_PER_TRAIN * 1.2); // 1.2 = small space between cars

    v1 = centre + forward * dy - right * dx;
    v2 = centre - forward * dy - right * dx;
    v3 = centre - forward * dy + right * dx;
    v4 = centre + forward * dy + right * dx;

    glColor3f( 0, 0, 0 );
    glBegin( GL_POLYGON );  
    glVertex2f( v1.x, v1.y );
    glVertex2f( v2.x, v2.y );
    glVertex2f( v3.x, v3.y );
    glVertex2f( v4.x, v4.y );
    glEnd();

    // Draw car filled in to indicate occupancy

    peopleToDraw += peoplePerCar;
    fractionFull = (peopleToDraw - TRAIN_CAPACITY) / peoplePerCar;
    if (fractionFull < 0)
      fractionFull = 0;
    else if (fractionFull > 1)
      fractionFull = 1;

    dx = 4;			// width of train (4 metres!) for drawing
    dy = length / 2.0 / (CARS_PER_TRAIN * 1.2); // 1.2 = small space between cars

    v1 = centre + forward * dy - right * dx;
    v2 = centre + forward * (1 - 2*fractionFull) * dy - right * dx;
    v3 = centre + forward * (1 - 2*fractionFull) * dy + right * dx;
    v4 = centre + forward * dy + right * dx;

    glColor3f( 0.1, 0.9, 0.9 );
    glBegin( GL_POLYGON );  
    glVertex2f( v1.x, v1.y );
    glVertex2f( v2.x, v2.y );
    glVertex2f( v3.x, v3.y );
    glVertex2f( v4.x, v4.y );
    glEnd();

    // Draw car outline

    dx = 4;			// width of train (4 metres!) for drawing
    dy = length / 2.0 / (CARS_PER_TRAIN * 1.2); // 1.2 = small space between cars

    v1 = centre + forward * dy - right * dx;
    v2 = centre - forward * dy - right * dx;
    v3 = centre - forward * dy + right * dx;
    v4 = centre + forward * dy + right * dx;

    glColor3f( 0.1, 0.9, 0.9 );
    glBegin( GL_LINE_LOOP );  
    glVertex2f( v1.x, v1.y );
    glVertex2f( v2.x, v2.y );
    glVertex2f( v3.x, v3.y );
    glVertex2f( v4.x, v4.y );
    glEnd();
  }
#endif
}


void
Train::printPassengers()

{
  for (int i=0; i<passengers.size(); i++)
    cout << *passengers[i]
	 << ": source " << *passengers[i]->sourceStation()
	 << ", dest " << *passengers[i]->destStation()
	 << ", next " << *passengers[i]->nextStation << "\n";
}
