/* controller.C
 */


#include "controller.h"
#include "train.h"
#include "section.h"
#include "events.h"


#define MIN_TRAIN_SEP   400	// trains need 400 metre separation for slow travel
#define SAFE_TRAIN_SEP  800	// with 800 meters, following train can go max speed


/* When a train enters a section, change the section status to RED so
 * that other trains don't go into it.
 */

void
Controller::trainIsEnteringSection( Train *t, Section *s )

{
  // Check that things are as we expect

  if (s->stat == RED) {
    cerr << "Error: Train " << *t << " is entering RED section " << *s << ".\n";
    abort();
  }

  // Block the section

  s->stat = RED;
}


/* When a train leaves a section:
 *
 * If it left a MAIN section, set to YELLOW the sections behind it
 * that are > MIN_TRAIN_SEPARATION and set to GREEN the sections that
 * are > SAFE_TRAIN_SEPARATION.
 *
 * If it left a SWITCH or SIDING section, that section should remain
 * RED, but the part of the main line at the rear of the switch should
 * now be changed to GREEN.
 */


void
Controller::trainIsLeavingSection( Train *t, Section *s )

{
  float dist;
  Section *prev, *next;

  switch (s->type()) {

  case SIDING:
  case SWITCH:

    // Change to GREEN the section preceding the switch on the
    // main line, as well as the other sections of the main line
    // preceding that one.

    prev = s;
    next = prev->prevMainSection();

    while (next != NULL && !next->occupied() && next->status() != GREEN) {
      next->setStatus( GREEN );
      next->orientSectionReversed( prev );
      prev = next;
      next = next->prevMainSection();
    }
    break;

  case MAIN:

    dist = 0;

    // follow sections back by at least MIN_TRAIN_SEP, setting to RED

    while (s != NULL && dist < MIN_TRAIN_SEP && !s->occupied()) {
      s->setStatus( RED );
      dist += s->length();
      s = s->prevMainSection();
    }

    if (s == NULL || s->occupied())
      break;

    // follow sections back by at least SAFE_TRAIN_SEP, setting to YELLOW

    while (s != NULL && dist < SAFE_TRAIN_SEP && !s->occupied()) {
      s->setStatus( YELLOW );
      dist += s->length();
      s = s->prevMainSection();
    }

    if (s == NULL || s->occupied())
      break;

    // follow sections back until we reach something occupied or GREEN, setting to GREEN

    while (s != NULL && !s->occupied() && s->status() != GREEN) {
      s->setStatus( GREEN );
      s = s->prevMainSection();
    }

    break;

  default:
    cerr << "Error in Controller::trainIsLeavingStation: missing a type in the `switch'.\n";
    abort();
  }
}


/* When the train reverses, this function is called.  The controller
 * attempts to clear the line ahead of this reversed train, and
 * blocks the line behind it.
 */


void
Controller::trainIsReversing( Train *t )

{
  Section *prev, *next;

  // Block the line behind

  trainIsLeavingSection( t, t->currentSection() );
  t->currentSection()->setStatus( RED );

  // Free the line as far ahead as possible

  prev = t->currentSection();

  while (true) {
    next = prev->nextMainSection();

    if (next == NULL)		// end of line
      break;

    if (next->status() == GREEN)
      break;

    if (next->occupied()) {	           // whoops!  Cleared line all the way to
      trainIsLeavingSection( NULL, prev ); // the next train.  Undo the damage!
      break;
    }

    next->setStatus( GREEN );
    next->orientSection( prev );
    prev = next;
  }
}


/* Attempt to set the signals so that train t can switch onto the
 * next branch section ahead of it.
 *
 * The controller makes a fairly restrictive assumption that the
 * switch occupies exactly one section of track and that there is a
 * main line on the other side of the switch:
 *
 *   main  - t --O- - - - -
 *                \
 *                 \ switch
 *                  \
 *   main  - - - - - O------
 */


bool
Controller::switchToBranch( Train *t )
  
{
  Section *branchSection, *nextMainSection;

  // Find the branch section that should be immediately in front of the train

  branchSection = t->currentSection()->nextBranchSection();

  if (branchSection == NULL) {
    cerr << "Error: Train " << *t << " in section " << t->currentSection()
	 << " is attempting to branch, but there's no branch ahead of it.\n";
    abort();
  }

  if (branchSection->occupied()) // can't proceed
    return false;

  branchSection->orientSection( t->currentSection() ); // orient in train's direction

  // Find the main section of the other line beyond the branch

  nextMainSection = branchSection->nextMainSection();

  if (nextMainSection == NULL) {
    cerr << "Error: Train " << *t << " in section " << t->currentSection()
	 << " is attempting to branch, but there's no main line after the branch ahead of it.\n";
    abort();
  }

  if (nextMainSection->occupied()) // can't proceed
    return false;

  nextMainSection->orientSection( branchSection ); // orient in train's direction

  // The train can only proceed if (1) the main section of the other
  // line beyond the branch is not RED and (2) that section must have
  // SAFE_TRAIN_SEP metres of unoccupied track in each direction

  if (nextMainSection->status() == RED  ||
      !TrackIsUnoccupied( nextMainSection, SAFE_TRAIN_SEP, AHEAD )  ||
      !TrackIsUnoccupied( nextMainSection, SAFE_TRAIN_SEP, BEHIND ))

    return false;
    
  else { // Can procede

    trainIsLeavingSection( t, nextMainSection ); // block preceding section on other main line
    branchSection->setStatus( YELLOW );
    nextMainSection->setStatus( YELLOW );

    return true;
  }    
}


/* When train starts waiting on a platform, this function is called.
 * Currently, nothing is done, but the function might be used by a
 * more intelligent controller.
 */

void
Controller::trainIsWaitingOnPlatform( Train *t, Platform *platform, int numPassengers )

{
}


/* When train leaves platform, this function is called.  Currently,
 * nothing is done, but the function might be used by a more
 * intelligent controller.
 */
     
void
Controller::trainIsLeavingPlatform( Train *t, Station *nextStation )

{
}


/* Return true iff the sections for a given distance ahead or behind
 * this section, s, are unoccupied.
 */


bool
Controller::TrackIsUnoccupied( Section *prev, float distance, Direction dir )

{
  Section *next;

  while (distance > 0) {

    // Find next track section in appropriate direction

    if (dir == AHEAD)
      next = prev->nextMainSection();
    else
      next = prev->prevMainSection();

    // Check status of next section

    if (next == NULL)		// if nonexistent, it's not occupied!
      return true;

    if (next->occupied())
      return false;

    // Reduce distance to check by length of next section

    distance -= next->length();

    // Orient next section appropriately

    if (dir == AHEAD)
      next->orientSection( prev );
    else
      next->orientSectionReversed( prev );

    prev = next;
  }

  return true;
}
