/*
 * SPDX-FileName: dynamics.cxx
 * SPDX-FileComment: Code to manage the higher order airport ground activities
 * SPDX-FileCopyrightText: Written by Durk Talsma, started December 2004
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <config.h>

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

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

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

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

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

// #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:
    explicit 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();
        auto 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),
                                                      groundController(this),
                                                      lastUpdate{0},
                                                      atisSequenceIndex(-1),
                                                      atisSequenceTimeStamp(0.0)
{
}

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


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

FGParking* FGAirportDynamics::innerGetAvailableParking(double radius, const std::string& flType,
                                                       const std::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) == std::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::hasParking(FGParking* parking) const
{
    return std::find(parent()->groundNetwork()->allParkings().begin(),
                     parent()->groundNetwork()->allParkings().end(),
                     parking) != parent()->groundNetwork()->allParkings().end();
}

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

ParkingAssignment FGAirportDynamics::getAvailableParking(double radius,
                                                         const std::string& flType,
                                                         const std::string& acType,
                                                         const std::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, std::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:
    explicit 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;
        }


        // because runways were sorted by score when building, they were added
        // by score also, so we can use a simple algorithm 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()
    {
        std::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{0.0};
};

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();
    }
};

std::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 std::string& trafficType,
                                             int action, std::string& runway,
                                             double heading)
{
    if (!rwyPrefs.available()) {
        runway = fallbackGetActiveRunway(action, heading);
        return !runway.empty();
    }

    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 =
            globals->get_subsystem<FGEnvironmentMgr>()
            ->getEnvironment(getLatitude(), getLongitude(),
                             getElevation());
        */
        double windSpeed = fgGetInt("/environment/metar/base-wind-speed-kt"); //stationweather.get_wind_speed_kt();
        double windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
        //stationweather.get_wind_from_heading_deg();
        std::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);
        double maxTail = currSched->getTailWind();
        double maxCross = currSched->getCrossWind();
        //cerr << "Current Schedule =        : " << scheduleName << endl;
        if (scheduleName.empty())
            return false;
        //cerr << "C"<< endl;
        RunwayGroup* currRunwayGroup = rwyPrefs.getGroup(scheduleName);
        //cerr << "D"<< endl;
        if (!(currRunwayGroup))
            return false;

        // 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 << "Currently 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();
        int nrActiveRunways = currRunwayGroup->getNrActiveRunways();
        //cerr << "Choosing runway for " << trafficType << endl;
        for (int i = 0; i < nrActiveRunways; i++) {
            std::string name;
            std::string 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 true;
}

std::string FGAirportDynamics::chooseRwyByHeading(stringVec rwys,
                                                  double heading)
{
    double bestError = 360.0;
    double rwyHeading, headingError;
    std::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 std::string& trafficType,
                                        int action, std::string& runway,
                                        double heading)
{
    bool ok = innerGetActiveRunway(trafficType, action, runway, heading);
    if (!ok || runway.empty()) {
        runway = chooseRunwayFallback();
    }
}

std::string FGAirportDynamics::chooseRunwayFallback()
{
    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 std::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::getApproachFrequency(unsigned nr)
{
    int approachFreq = 0;
    if (nr < 2) {
        SG_LOG(SG_ATC, SG_ALERT,
               "Leg value is smaller than two at " << SG_ORIGIN);
    }

    const intVec& freqApproach(parent()->groundNetwork()->getApproachFrequencies());

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

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));
}
