// dynamics.cxx - Code to manage the higher order airport ground activities
// Written by Durk Talsma, started December 2004.
//
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
// $Id$

#include <config.h>

#include <algorithm>
#include <string>
#include <vector>

#include <simgear/compiler.h>
#include <simgear/structure/SGWeakPtr.hxx>
#include <simgear/structure/exception.hxx>

#include <Environment/environment_mgr.hxx>
#include <Environment/environment.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/debug/logstream.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Main/locale.hxx>
#include <Airports/runways.hxx>
#include <Airports/groundnetwork.hxx>
#include <Navaids/NavDataCache.hxx>
#include <AIModel/AIManager.hxx>
#include <AIModel/AIBase.hxx>

#include "airport.hxx"
#include "dynamics.hxx"

using std::string;
using std::vector;
using std::sort;
using std::random_shuffle;

// #define RUNWAY_FALLBACK_DEBUG

class ParkingAssignment::ParkingAssignmentPrivate
{
public:
  ParkingAssignmentPrivate(FGParking* pk, FGAirportDynamics* dyn) :
    refCount(0),
    parking(pk),
    dynamics(dyn)
  {
    assert(pk);
    assert(dyn);
    retain(); // initial count of 1
  }

  ~ParkingAssignmentPrivate()
  {
      FGAirportDynamicsRef strongRef = dynamics.lock();
      if (strongRef) {
          strongRef->releaseParking(parking);
      }
  }

  void release()
  {
    if ((--refCount) == 0) {
      delete this;
    }
  }

  void retain()
  {
    ++refCount;
  }

  unsigned int refCount;
  FGParkingRef parking;
    
// we don't want an owning ref here, otherwise we
// have a circular ownership from AirportDynamics -> us
  SGWeakPtr<FGAirportDynamics> dynamics;
};

ParkingAssignment::ParkingAssignment() :
  _sharedData(NULL)
{
}

ParkingAssignment::~ParkingAssignment()
{
  if (_sharedData) {
    _sharedData->release();
  }
}

ParkingAssignment::ParkingAssignment(FGParking* pk, FGAirportDynamics* dyn) :
  _sharedData(NULL)
{
  if (pk) {
    _sharedData = new ParkingAssignmentPrivate(pk, dyn);
  }
}

ParkingAssignment::ParkingAssignment(const ParkingAssignment& aOther) :
  _sharedData(aOther._sharedData)
{
  if (_sharedData) {
    _sharedData->retain();
  }
}

void ParkingAssignment::operator=(const ParkingAssignment& aOther)
{
  if (_sharedData == aOther._sharedData) {
    return; // self-assignment, special case
  }

  if (_sharedData) {
    _sharedData->release();
  }

  _sharedData = aOther._sharedData;
  if (_sharedData) {
    _sharedData->retain();
  }
}

void ParkingAssignment::release()
{
  if (_sharedData) {
    _sharedData->release();
    _sharedData = NULL;
  }
}

bool ParkingAssignment::isValid() const
{
  return (_sharedData != NULL);
}

FGParking* ParkingAssignment::parking() const
{
  return _sharedData ? _sharedData->parking.ptr() : NULL;
}

////////////////////////////////////////////////////////////////////////////////

/**
 * @brief Helper to cache all AIObject positions near the airport when
 * searching for available parkings. This allows us to reject parkings
 * which we might not have marked as occupied, but which an object is
 * neverthless close to; such as the primary user or MP aircraft.
 */
class NearbyAIObjectCache
{
public:
    NearbyAIObjectCache(FGAirportRef apt) :
        m_airport(apt)
    {}

    bool isAnythingNear(const SGVec3d& cart, double radiusM)
    {
        if (!m_populated) {
            populate();
        }

        for (auto c : m_cache) {
            const double d = dist(cart, c);
            if (d < radiusM) {
                return true;
            }
        }

        return false;
    }

private:
    void populate()
    {
        SGVec3d cartAirportPos = m_airport->cart();
        FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
        for (auto ai : aiManager->get_ai_list()) {
            const auto cart = ai->getCartPos();

            // 20km cutoff from airport centre
            if (dist(cartAirportPos, cart) > 20000) {
                continue;
            }

            m_cache.push_back(cart);
        }
        m_populated = true;
    }

    bool m_populated = false;
    FGAirportRef m_airport;
    std::vector<SGVec3d> m_cache;
};

////////////////////////////////////////////////////////////////////////////////

FGAirportDynamics::FGAirportDynamics(FGAirport * ap):
    _ap(ap), rwyPrefs(ap),
    startupController    (this),
    towerController      (this),
    approachController   (this),
    atisSequenceIndex(-1),
    atisSequenceTimeStamp(0.0)

{
    lastUpdate = 0;
}

// Destructor
FGAirportDynamics::~FGAirportDynamics()
{
}


// Initialization required after XMLRead
void FGAirportDynamics::init()
{
    groundController.setTowerController(&towerController);
    groundController.init(this);
}

FGParking* FGAirportDynamics::innerGetAvailableParking(double radius, const string & flType,
                                           const string & airline,
                                           bool skipEmptyAirlineCode)
{
    NearbyAIObjectCache nearCache(parent());
    const FGParkingList& parkings(parent()->groundNetwork()->allParkings());
    FGParkingList candidates;
    for (auto parking : parkings) {
        if (!isParkingAvailable(parking)) {
          continue;
        }

        if (parking->getRadius() < radius) {
            continue;
        }

        if (!flType.empty() && (parking->getType() != flType)) {
            continue;
        }

        if (skipEmptyAirlineCode && parking->getCodes().empty()) {
          continue;
        }

        if (!airline.empty() && !parking->getCodes().empty()) {
          if (parking->getCodes().find(airline, 0) == string::npos) {
            continue;
          }
        }

        if (nearCache.isAnythingNear(parking->cart(), parking->getRadius())) {
            continue;
        }

        candidates.push_back(parking);
    }

    if (candidates.empty()) {
        return nullptr;
    }

    // sort by increasing radius, so we return the smallest radius candidate
    // this avoids large spaces (A380 sized) being given to Fokkers/ATR-72s
    std::sort(candidates.begin(), candidates.end(),
              [](const FGParkingRef& a, const FGParkingRef& b) {
                  return a->getRadius() < b->getRadius();
              });

    setParkingAvailable(candidates.front(), false);
    return candidates.front();
}

bool FGAirportDynamics::hasParkings() const
{
    return !parent()->groundNetwork()->allParkings().empty();
}

ParkingAssignment FGAirportDynamics::getAvailableParking(double radius, const string & flType,
                                            const string & acType,
                                            const string & airline)
{
  SG_UNUSED(acType); // sadly not used at the moment

  // most exact seach - airline codes must be present and match
  FGParking* result = innerGetAvailableParking(radius, flType, airline, true);
  if (result) {
    return ParkingAssignment(result, this);
  }

  // more tolerant - gates with empty airline codes are permitted
  result = innerGetAvailableParking(radius, flType, airline, false);
  if (result) {
    return ParkingAssignment(result, this);
  }

  // fallback - ignore the airline code entirely
  result = innerGetAvailableParking(radius, flType, string(), false);
  return result ? ParkingAssignment(result, this) : ParkingAssignment();
}

ParkingAssignment FGAirportDynamics::getParkingByName(const std::string& name) const
{
    const FGParkingList& parkings(parent()->groundNetwork()->allParkings());
    FGParkingList::const_iterator it;
    for (it = parkings.begin(); it != parkings.end(); ++it) {
        if ((*it)->name() == name) {
            return ParkingAssignment(*it, const_cast<FGAirportDynamics*>(this));
        }
    }

    return ParkingAssignment();
}

ParkingAssignment FGAirportDynamics::getAvailableParkingByName(const std::string & name)
{
    const FGParkingList& parkings(parent()->groundNetwork()->allParkings());
    auto it = std::find_if(parkings.begin(), parkings.end(), [this, name] (FGParkingRef parking){
       if (parking->name() != name)
           return false;
        
        return this->isParkingAvailable(parking);
    });
    
    if (it == parkings.end())
        return {}; // no assignment possible
    
    return {*it, this};
}

FGParkingRef FGAirportDynamics::getOccupiedParkingByName(const std::string& name) const
{
    auto it = std::find_if(occupiedParkings.begin(), occupiedParkings.end(),
                           [name](const FGParkingRef& pk) { return pk->name() == name; });
    if (it != occupiedParkings.end()) {
        return *it;
    }

    return {};
}

void FGAirportDynamics::setParkingAvailable(FGParking* park, bool available)
{
  if (available) {
    releaseParking(park);
  } else {
    occupiedParkings.insert(park);
  }
}

bool FGAirportDynamics::isParkingAvailable(FGParking* parking) const
{
  return (occupiedParkings.find(parking) == occupiedParkings.end());
}

void FGAirportDynamics::releaseParking(FGParking* id)
{
  ParkingSet::iterator it = occupiedParkings.find(id);
  if (it == occupiedParkings.end()) {
    return;
  }

  occupiedParkings.erase(it);
}

class GetParkingsPredicate
{
    bool mustBeAvailable;
    std::string type;
    const FGAirportDynamics* dynamics;
public:
    GetParkingsPredicate(bool b, const std::string& ty, const FGAirportDynamics* dyn) :
        mustBeAvailable(b),
        type(ty),
        dynamics(dyn)
    {}

    bool operator()(const FGParkingRef& park) const
    {
        if (!type.empty() && (park->getType() != type))
            return true;

        if (mustBeAvailable && !dynamics->isParkingAvailable(park)) {
            return true;
        }

        return false;
    }
};

FGParkingList FGAirportDynamics::getParkings(bool onlyAvailable, const std::string &type) const
{
    FGParkingList result(parent()->groundNetwork()->allParkings());

    GetParkingsPredicate pred(onlyAvailable, type, this);
    auto it = std::remove_if(result.begin(), result.end(), pred);
    result.erase(it, result.end());
    return result;
}

void FGAirportDynamics::setRwyUse(const FGRunwayPreference & ref)
{
    rwyPrefs = ref;
}

bool areRunwaysParallel(const FGRunwayRef& a, const FGRunwayRef& b)
{
    double hdgDiff = (b->headingDeg() - a->headingDeg());
    SG_NORMALIZE_RANGE(hdgDiff, -180.0, 180.0);
    return (fabs(hdgDiff) < 5.0);
}

double runwayScore(const FGRunwayRef& rwy)
{
    return rwy->lengthM() + rwy->widthM();
}

double runwayWindScore(const FGRunwayRef& runway, double windHeading,
                       double windSpeedKts)
{
    double hdgDiff = fabs(windHeading - runway->headingDeg()) * SG_DEGREES_TO_RADIANS;
    SGMiscd::normalizeAngle(hdgDiff);

    double crossWind = windSpeedKts * sin(hdgDiff);
    double tailWind = -windSpeedKts * cos(hdgDiff);

    return -(crossWind + tailWind);
}

typedef std::vector<FGRunwayRef> RunwayVec;


class FallbackRunwayGroup
{
public:
    FallbackRunwayGroup(const FGRunwayRef& rwy) :
        _groupScore(0.0)
    {
        runways.push_back(rwy);
        _leadRunwayScore = runwayScore(rwy);
        _groupScore += _leadRunwayScore;
    }

    bool canAccept(const FGRunwayRef& rwy) const
    {
        if (!areRunwaysParallel(runways.front(), rwy)) {
            return false;
        }

        return true;
    }

    void addRunway(const FGRunwayRef& rwy)
    {
        assert(areRunwaysParallel(runways.front(), rwy));
        double score = runwayScore(rwy);
        if (score < (0.5 * _leadRunwayScore)) {
            // drop the runway completely. this is to drop short parallel
            // runways from being active
            return;
        }

        runways.push_back(rwy);
        _groupScore += score;
    }

    void adjustScoreForWind(double windHeading, double windSpeedKts)
    {
        _basicScore = _groupScore;
        RunwayVec::iterator it;
        for (it = runways.begin(); it != runways.end(); ++it) {
            _groupScore += runwayWindScore(*it, windHeading, windSpeedKts);
        }
    }

    double groupScore() const
    {
        return _groupScore;
    }

    void getRunways(FGRunwayList& arrivals, FGRunwayList& departures)
    {
    // make the common cases very obvious
        if (runways.size() == 1) {
            arrivals.push_back(runways.front());
            departures.push_back(runways.front());
            return;
        }


    // becuase runways were sorted by score when building, they were added
    // by score also, so we can use a simple algorithim to assign
        for (unsigned int r=0; r < runways.size(); ++r) {
            if ((r % 2) == 0) {
                arrivals.push_back(runways[r]);
            } else {
                departures.push_back(runways[r]);
            }
        }
    }

    std::string dump()
    {
        ostringstream os;
        os << runways.front()->ident();
        for (unsigned int r=1; r <runways.size(); ++r) {
            os << ", " << runways[r]->ident();
        }

        os << " (score=" << _basicScore << ", wind score=" << _groupScore << ")";
        return os.str();
    }

private:
    RunwayVec runways;
    double _groupScore,
        _leadRunwayScore;
    double _basicScore;

};

class WindExclusionCheck
{
public:
    WindExclusionCheck(double windHeading, double windSpeedKts) :
        _windSpeedKts(windSpeedKts),
        _windHeading(windHeading)
    {}

    bool operator()(const FGRunwayRef& rwy) const
    {
        return (runwayWindScore(rwy, _windHeading, _windSpeedKts) > 30);
    }
private:
    double _windSpeedKts,
        _windHeading;
};

class SortByScore
{
public:
    bool operator()(const FGRunwayRef& a, const FGRunwayRef& b) const
    {
        return runwayScore(a) > runwayScore(b);
    }
};

class GroupSortByScore
{
public:
    bool operator()(const FallbackRunwayGroup& a, const FallbackRunwayGroup& b) const
    {
        return a.groupScore() > b.groupScore();
    }
};

string FGAirportDynamics::fallbackGetActiveRunway(int action, double heading)
{
    bool updateNeeded = false;
    if (_lastFallbackUpdate == SGTimeStamp()) {
        updateNeeded = true;
    } else {
        updateNeeded = (_lastFallbackUpdate.elapsedMSec() > (1000 * 60 * 15));
    }

    if (updateNeeded) {
        double windSpeed   = fgGetInt("/environment/metar/base-wind-speed-kt");
        double windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");

        // discount runways based on cross / tail-wind
        WindExclusionCheck windCheck(windHeading, windSpeed);
        RunwayVec runways(parent()->getRunways());
        auto it = std::remove_if(runways.begin(), runways.end(), windCheck);
        runways.erase(it, runways.end());

        // sort highest scored to lowest scored
        std::sort(runways.begin(), runways.end(), SortByScore());

        std::vector<FallbackRunwayGroup> groups;
        std::vector<FallbackRunwayGroup>::iterator git;

        for (it = runways.begin(); it != runways.end(); ++it) {
            bool existingGroupDidAccept = false;
            for (git = groups.begin(); git != groups.end(); ++git) {
                if (git->canAccept(*it)) {
                    existingGroupDidAccept = true;
                    git->addRunway(*it);
                    break;
                }
            } // of existing groups iteration

            if (!existingGroupDidAccept) {
                // create a new group
                groups.push_back(FallbackRunwayGroup(*it));
            }
        } // of group building phase

        if (groups.empty()) {
            SG_LOG(SG_AI, SG_DEV_WARN, "fallbackGetActiveRunway: airport " << parent()->ident() << " has no runways");
            return {};
        }

        // select highest scored group based on cross/tail wind
        for (git = groups.begin(); git != groups.end(); ++git) {
            git->adjustScoreForWind(windHeading, windSpeed);
        }

        std::sort(groups.begin(), groups.end(), GroupSortByScore());

        // debugging fallback runway assignments
#if defined(RUNWAY_FALLBACK_DEBUG)
        {
            ostringstream os;
            os << parent()->ident() << " groups:";

            for (git = groups.begin(); git != groups.end(); ++git) {
                os << "\n\t" << git->dump();
            }

            std::string s = os.str();
            SG_LOG(SG_AI, SG_INFO, s);
        }
#endif

        // assign takeoff and landing runways
        FallbackRunwayGroup bestGroup = groups.front();

        _lastFallbackUpdate.stamp();
        _fallbackRunwayCounter = 0;
        _fallbackDepartureRunways.clear();
        _fallbackArrivalRunways.clear();
        bestGroup.getRunways(_fallbackArrivalRunways, _fallbackDepartureRunways);

#if defined(RUNWAY_FALLBACK_DEBUG)
        ostringstream os;
        os << "\tArrival:" << _fallbackArrivalRunways.front()->ident();
        for (unsigned int r=1; r <_fallbackArrivalRunways.size(); ++r) {
            os << ", " << _fallbackArrivalRunways[r]->ident();
        }
        os << "\n\tDeparture:" << _fallbackDepartureRunways.front()->ident();
        for (unsigned int r=1; r <_fallbackDepartureRunways.size(); ++r) {
            os << ", " << _fallbackDepartureRunways[r]->ident();
        }

        std::string s = os.str();
        SG_LOG(SG_AI, SG_INFO, parent()->ident() << " fallback runways assignments for "
               << static_cast<int>(windHeading) << "@" << static_cast<int>(windSpeed) << "\n" << s);
#endif
    }

    _fallbackRunwayCounter++;
    FGRunwayRef r;
    // ensure we cycle through possible runways where they exist
    if (action == 1) {
        r = _fallbackDepartureRunways[_fallbackRunwayCounter % _fallbackDepartureRunways.size()];
    } else {
        r = _fallbackArrivalRunways[_fallbackRunwayCounter % _fallbackArrivalRunways.size()];
    }

    return r->ident();
}

bool FGAirportDynamics::innerGetActiveRunway(const string & trafficType,
                                             int action, string & runway,
                                             double heading)
{
    double windSpeed;
    double windHeading;
    double maxTail;
    double maxCross;
    string name;
    string type;

    if (!rwyPrefs.available()) {
        runway = fallbackGetActiveRunway(action, heading);
        return !runway.empty();
    }

    RunwayGroup *currRunwayGroup = 0;
    int nrActiveRunways = 0;
    time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
    if ((std::abs((long) (dayStart - lastUpdate)) > 600)
        || trafficType != prevTrafficType) {
        landing.clear();
        takeoff.clear();
        lastUpdate = dayStart;
        prevTrafficType = trafficType;
        /*
        FGEnvironment
            stationweather =
            ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
            ->getEnvironment(getLatitude(), getLongitude(),
                             getElevation());
        */
        windSpeed   = fgGetInt("/environment/metar/base-wind-speed-kt"); //stationweather.get_wind_speed_kt();
        windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
        //stationweather.get_wind_from_heading_deg();
        string scheduleName;
        //cerr << "finding active Runway for : " << _ap->getId() << endl;
        //cerr << "Wind Heading              : " << windHeading << endl;
        //cerr << "Wind Speed                : " << windSpeed << endl;

        //cerr << "Nr of seconds since day start << " << dayStart << endl;

        ScheduleTime *currSched;
        //cerr << "A"<< endl;
        currSched = rwyPrefs.getSchedule(trafficType.c_str());
        if (!(currSched))
            return false;
        //cerr << "B"<< endl;
        scheduleName = currSched->getName(dayStart);
        maxTail = currSched->getTailWind();
        maxCross = currSched->getCrossWind();
        //cerr << "Current Schedule =        : " << scheduleName << endl;
        if (scheduleName.empty())
            return false;
        //cerr << "C"<< endl;
        currRunwayGroup = rwyPrefs.getGroup(scheduleName);
        //cerr << "D"<< endl;
        if (!(currRunwayGroup))
            return false;
        nrActiveRunways = currRunwayGroup->getNrActiveRunways();

        // Keep a history of the currently active runways, to ensure
        // that an already established selection of runways will not
        // be overridden once a more preferred selection becomes
        // available as that can lead to random runway swapping.
        if (trafficType == "com") {
            currentlyActive = &comActive;
        } else if (trafficType == "gen") {
            currentlyActive = &genActive;
        } else if (trafficType == "mil") {
            currentlyActive = &milActive;
        } else if (trafficType == "ul") {
            currentlyActive = &ulActive;
        }

        //cerr << "Durrently active selection for " << trafficType << ": ";
        for (stringVecIterator it = currentlyActive->begin();
             it != currentlyActive->end(); it++) {
             //cerr << (*it) << " ";
         }
         //cerr << endl;

        currRunwayGroup->setActive(_ap,
                                   windSpeed,
                                   windHeading,
                                   maxTail, maxCross, currentlyActive);

        // Note that I SHOULD keep multiple lists in memory, one for
        // general aviation, one for commercial and one for military
        // traffic.
        currentlyActive->clear();
        nrActiveRunways = currRunwayGroup->getNrActiveRunways();
        //cerr << "Choosing runway for " << trafficType << endl;
        for (int i = 0; i < nrActiveRunways; i++) {
            type = "unknown";   // initialize to something other than landing or takeoff
            currRunwayGroup->getActive(i, name, type);
            if (type == "landing") {
                landing.push_back(name);
                currentlyActive->push_back(name);
                //cerr << "Landing " << name << endl;
            }
            if (type == "takeoff") {
                takeoff.push_back(name);
                currentlyActive->push_back(name);
                //cerr << "takeoff " << name << endl;
            }
        }
        //cerr << endl;
    }

    if (action == 1)            // takeoff
    {
        int nr = takeoff.size();
        if (nr) {
            // Note that the randomization below, is just a placeholder to choose between
            // multiple active runways for this action. This should be
            // under ATC control.
            runway = chooseRwyByHeading(takeoff, heading);
        } else {                // Fallback
            runway = chooseRunwayFallback();
        }
    }

    if (action == 2)            // landing
    {
        if (!landing.empty()) {
            runway = chooseRwyByHeading(landing, heading);
        }
        
        if (runway.empty()) { //fallback
            runway = chooseRunwayFallback();
        }
    }

    return !runway.empty();
}

string FGAirportDynamics::chooseRwyByHeading(stringVec rwys,
                                             double heading)
{
    double bestError = 360.0;
    double rwyHeading, headingError;
    string runway;
    for (stringVecIterator i = rwys.begin(); i != rwys.end(); i++) {
        if (!_ap->hasRunwayWithIdent(*i)) {
          SG_LOG(SG_ATC, SG_WARN, "chooseRwyByHeading: runway " << *i <<
            " not found at " << _ap->ident());
          continue;
        }

        FGRunway *rwy = _ap->getRunwayByIdent((*i));
        rwyHeading = rwy->headingDeg();
        headingError = fabs(heading - rwyHeading);
        if (headingError > 180)
            headingError = fabs(headingError - 360);
        if (headingError < bestError) {
            runway = (*i);
            bestError = headingError;
        }
    }
    //cerr << "Using active runway " << runway << " for heading " << heading << endl;
    return runway;
}

void FGAirportDynamics::getActiveRunway(const string & trafficType,
                                        int action, string & runway,
                                        double heading)
{
    bool ok = innerGetActiveRunway(trafficType, action, runway, heading);
    if (!ok || runway.empty()) {
        runway = chooseRunwayFallback();
    }
}

string FGAirportDynamics::chooseRunwayFallback()
{
    if (_ap->numRunways() == 0) {
        SG_LOG(SG_AI, SG_WARN, "FGAirportDynamics: asked for runway at probable heliport, check for a bogus schedule referencing:" << _ap->ident());

        // by throwing, we should cause the AIManager to shut down this flight, which
        // is what need; the calling code assumes AITraffic has a valid runway, and
        // we just don't
        throw sg_exception("No runways available at airport", _ap->ident());
    }

    FGRunway *rwy = _ap->getActiveRunwayForUsage();
    if (!rwy) {
        SG_LOG(SG_AI, SG_WARN, "FGAirportDynamics::chooseRunwayFallback failed at " << _ap->ident()); 
        // let's use runway 0
        return _ap->getRunwayByIndex(0)->ident();
    }
    return rwy->ident();
}

double FGAirportDynamics::getElevation() const
{
    return _ap->getElevation();
}

const string FGAirportDynamics::getId() const
{
    return _ap->getId();
}

// Experimental: Return a different ground frequency depending on the leg of the
// Flight. Leg should always have a minimum value of two when this function is called.
// Note that in this scheme, the assignment of various frequencies to various ground
// operations is completely arbitrary. As such, is a short cut I need to take now,
// so that at least I can start working on assigning different frequencies to different
// operations.

int FGAirportDynamics::getGroundFrequency(unsigned leg)
{
    //return freqGround.size() ? freqGround[0] : 0; };
    //cerr << "Getting frequency for : " << leg << endl;
    int groundFreq = 0;
    if (leg < 1) {
        SG_LOG(SG_ATC, SG_ALERT,
               "Leg value is smaller than one at " << SG_ORIGIN);
    }

    const intVec& freqGround(parent()->groundNetwork()->getGroundFrequencies());

    if (freqGround.size() == 0) {
        return 0;
    }

    if ((freqGround.size() < leg) && (leg > 0)) {
        groundFreq =
            (freqGround.size() <=
             (leg - 1)) ? freqGround[freqGround.size() -
                                     1] : freqGround[leg - 1];
    }
    if ((freqGround.size() >= leg) && (leg > 0)) {
        groundFreq = freqGround[leg - 1];
    }
    return groundFreq;
}

int FGAirportDynamics::getTowerFrequency(unsigned nr)
{
    int towerFreq = 0;
    if (nr < 2) {
        SG_LOG(SG_ATC, SG_ALERT,
               "Leg value is smaller than two at " << SG_ORIGIN);
    }

    const intVec& freqTower(parent()->groundNetwork()->getTowerFrequencies());

    if (freqTower.size() == 0) {
        return 0;
    }
    if ((freqTower.size() > nr - 1) && (nr > 1)) {
        towerFreq = freqTower[nr - 1];
    }
    if ((freqTower.size() < nr - 1) && (nr > 1)) {
        towerFreq =
            (freqTower.size() <
             (nr - 1)) ? freqTower[freqTower.size() -
                                     1] : freqTower[nr - 2];
    }
    if ((freqTower.size() >= nr - 1) && (nr > 1)) {
        towerFreq = freqTower[nr - 2];
    }
    return towerFreq;
}

const std::string FGAirportDynamics::getAtisSequence()
{
   if (atisSequenceIndex == -1) {
       updateAtisSequence(1, false);
   }

   char atisSequenceString[2];
   atisSequenceString[0] = 'a' + atisSequenceIndex;
   atisSequenceString[1] = 0;

   return globals->get_locale()->getLocalizedString(atisSequenceString, "atc", "unknown");
}

int FGAirportDynamics::updateAtisSequence(int interval, bool forceUpdate)
{
    double now = globals->get_sim_time_sec();
    if (atisSequenceIndex == -1) {
        // first computation
        atisSequenceTimeStamp = now;
        atisSequenceIndex = rand() % 26; // random initial sequence letters
        return atisSequenceIndex;
    }

    int steps = static_cast<int>((now - atisSequenceTimeStamp) / interval);
    atisSequenceTimeStamp += (interval * steps);
    if (forceUpdate && (steps == 0)) {
        ++steps; // a "special" ATIS update is required
    }

    atisSequenceIndex = (atisSequenceIndex + steps) % 26;
    // return a huge value if no update occurred
    return (atisSequenceIndex + (steps ? 0 : 26*1000));
}
