// SPDX-FileCopyrightText: 2006 Durk Talsma
// SPDX-License-Identifier: GPL-2.0-or-later

#include <config.h>

#include <algorithm>
#include <cstdio>
#include <random>

#include <osg/Geode>
#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Shape>

#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/timing/sg_time.hxx>

#include <Scenery/scenery.hxx>

#include "atc_mgr.hxx"
#include "trafficcontrol.hxx"
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <AIModel/performancedata.hxx>
#include <Airports/airport.hxx>
#include <Airports/dynamics.hxx>
#include <Airports/groundnetwork.hxx>
#include <Radio/radio.hxx>
#include <Traffic/TrafficMgr.hxx>
#include <signal.h>

#include <ATC/ATCController.hxx>
#include <ATC/atc_mgr.hxx>
#include <ATC/trafficcontrol.hxx>

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

/***************************************************************************
 * FGATCController
 *
 **************************************************************************/

FGATCController::FGATCController()
{
    dt_count = 0;
    available = true;
    lastTransmission = 0;
    initialized = false;
    lastTransmissionDirection = ATC_AIR_TO_GROUND;
    group = NULL;
}

FGATCController::~FGATCController()
{
    if (initialized) {
        auto mgr = globals->get_subsystem<FGATCManager>();
        if (mgr) {
            mgr->removeController(this);
        }
    }
    _isDestroying = true;
    clearTrafficControllers();
}

void FGATCController::init()
{
    if (!initialized) {
        auto mgr = globals->get_subsystem<FGATCManager>();
        mgr->addController(this);
        initialized = true;
    }
}

void FGATCController::setAirportGroundRadar(SGSharedPtr<AirportGroundRadar> groundRadar)
{
    airportGroundRadar = groundRadar;
}

string FGATCController::getGateName(FGAIAircraft* ref)
{
    return ref->atGate();
}

bool FGATCController::isUserAircraft(FGAIAircraft* ac)
{
    return (ac->getCallSign() == fgGetString("/sim/multiplay/callsign")) ? true : false;
};

bool FGATCController::checkTransmissionState(int minState, int maxState, TrafficVectorIterator i, time_t now, AtcMsgId msgId,
                                             AtcMsgDir msgDir)
{
    int state = (*i)->getState();
    if ((state >= minState) && (state <= maxState) && available) {
        if ((msgDir == ATC_AIR_TO_GROUND) && isUserAircraft((*i)->getAircraft())) {
            SG_LOG(SG_ATC, SG_BULK, "Checking state " << state << " for " << (*i)->getAircraft()->getCallSign());
            SGPropertyNode_ptr trans_num = globals->get_props()->getNode("/sim/atc/transmission-num", true);
            int n = trans_num->getIntValue();
            if (n == 0) {
                trans_num->setIntValue(-1);
                // PopupCallback(n);
                SG_LOG(SG_ATC, SG_DEBUG, "Selected transmission message " << n);
                //auto atc = globals->get_subsystem<FGATCManager>();
                //FGATCDialogNew::instance()->removeEntry(1);
            } else {
                SG_LOG(SG_ATC, SG_BULK, "Sending message for " << (*i)->getAircraft()->getCallSign());
                transmit(*i, parent, msgId, msgDir, false);
                return false;
            }
        }
        transmit(*i, parent, msgId, msgDir, true);
        (*i)->updateState();
        lastTransmission = now;
        available = false;
        return true;
    }
    return false;
}

void FGATCController::transmit(FGTrafficRecord* rec, FGAirportDynamics* parent, AtcMsgId msgId,
                               AtcMsgDir msgDir, bool audible)
{
    string sender, receiver;
    int stationFreq = 0;
    int taxiFreq = 0;
    int towerFreq = 0;
    string atisInformation;
    string text;
    string taxiFreqStr;
    string towerFreqStr;
    double heading = 0;
    string activeRunway;
    string fltType;
    string rwyClass;
    string SID;
    string transponderCode;
    FGAIFlightPlan* fp;
    string fltRules;
    string instructionText;
    int ground_to_air = 0;

    //double commFreqD;
    sender = rec->getCallsign();
    if (rec->getAircraft()->getTaxiClearanceRequest()) {
        instructionText = "push-back and taxi";
    } else {
        instructionText = "taxi";
    }

    SG_LOG(SG_ATC, SG_BULK, "transmitting for: " << sender << " at Leg " << rec->getLeg());

    auto depApt = rec->getDeparture();
    auto arrApt = rec->getArrival();

    stationFreq = getFrequency();
    taxiFreq = depApt->getDynamics()->getGroundFrequency(2);
    towerFreq = depApt->getDynamics()->getTowerFrequency(2);
    receiver = getName();
    atisInformation = depApt->getDynamics()->getAtisSequence();

    // Swap sender and receiver value in case of a ground to air transmission
    if (msgDir == ATC_GROUND_TO_AIR) {
        string tmp = sender;
        sender = receiver;
        receiver = tmp;
        ground_to_air = 1;
    }

    switch (msgId) {
    case MSG_ANNOUNCE_ENGINE_START:
        text = sender + ". Ready to Start up.";
        break;
    case MSG_REQUEST_ENGINE_START:
        if (!depApt) {
            SG_LOG(SG_ATC, SG_DEV_ALERT, "TrafficRec has empty departure airport, can't transmit msg " << msgId << " at Leg " << rec->getLeg() << " " << rec->getCallsign());
            return;
        }
        text =
            receiver + ", This is " + sender + ". Position " +
            getGateName(rec->getAircraft()) + ". Information " +
            atisInformation + ". " +
            rec->getAircraft()->getTrafficRef()->getFlightRules() +
            " to " +
            depApt->getName() + ". Request start-up.";
        break;
        // Acknowledge engine startup permission
        // Assign departure runway
        // Assign SID, if necessary (TODO)
    case MSG_PERMIT_ENGINE_START:
        taxiFreqStr = formatATCFrequency3_2(taxiFreq);

        heading = rec->getAircraft()->getTrafficRef()->getCourse();
        fltType = rec->getAircraft()->getTrafficRef()->getFlightType();
        rwyClass =
            rec->getAircraft()->GetFlightPlan()->getRunwayClassFromTrafficType(fltType);

        rec->getAircraft()->getTrafficRef()->getDepartureAirport()->getDynamics()->getActiveRunway(rwyClass, RunwayAction::TAKEOFF, activeRunway,
                                                                                                   heading);
        rec->getAircraft()->GetFlightPlan()->setRunway(activeRunway);
        fp = NULL;
        rec->getAircraft()->GetFlightPlan()->setSID(fp);
        if (fp) {
            SID = fp->getName() + " departure";
        } else {
            SID = "fly runway heading ";
        }
        //snprintf(buffer, 7, "%3.2f", heading);
        fltRules = rec->getAircraft()->getTrafficRef()->getFlightRules();
        transponderCode = genTransponderCode(fltRules);
        rec->getAircraft()->SetTransponderCode(transponderCode);
        if (stationFreq != taxiFreq) {
            text =
                receiver + ". Start-up approved. " + atisInformation +
                " correct, runway " + activeRunway + ", " + SID + ", squawk " +
                transponderCode + ". " +
                "For " + instructionText + " clearance call " + taxiFreqStr + ". " +
                sender + " control.";
        } else {
            text =
                receiver + ". Start-up approved. " + atisInformation +
                " correct, runway " + activeRunway + ", " + SID + ", squawk " +
                transponderCode + ". " +
                sender + " control.";
        }
        break;
    case MSG_DENY_ENGINE_START:
        text = receiver + ". Standby.";
        break;
    case MSG_ACKNOWLEDGE_ENGINE_START:
        fp = rec->getAircraft()->GetFlightPlan()->getSID();
        if (fp) {
            SID =
                rec->getAircraft()->GetFlightPlan()->getSID()->getName() +
                " departure";
        } else {
            SID = "fly runway heading ";
        }
        taxiFreqStr = formatATCFrequency3_2(taxiFreq);
        activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
        transponderCode = rec->getAircraft()->GetTransponderCode();

        if (stationFreq != taxiFreq) {
            text =
                receiver + ". Start-up approved. " + atisInformation +
                " correct, runway " + activeRunway + ", " + SID + ", squawk " +
                transponderCode + ". " +
                "For " + instructionText + " clearance call " + taxiFreqStr + ". " +
                sender + ".";
        } else {
            text =
                receiver + ". Start-up approved. " + atisInformation +
                " correct, runway " + activeRunway + ", " + SID + ", squawk " +
                transponderCode + ". " +
                sender + ".";
        }
        break;
    case MSG_ACKNOWLEDGE_SWITCH_GROUND_FREQUENCY:
        taxiFreqStr = formatATCFrequency3_2(taxiFreq);
        text = receiver + ". Switching to " + taxiFreqStr + ". " + sender + ".";
        break;
    case MSG_INITIATE_CONTACT:
        text = receiver + ". With you. " + sender + ".";
        break;
    case MSG_ACKNOWLEDGE_INITIATE_CONTACT:
        text = receiver + ". Roger. " + sender + ".";
        break;
    case MSG_REQUEST_PUSHBACK_CLEARANCE:
        if (rec->getAircraft()->getTaxiClearanceRequest()) {
            text = receiver + ". Request push-back. " + sender + ".";
        } else {
            text = receiver + ". Request Taxi clearance. " + sender + ".";
        }
        break;
    case MSG_PERMIT_PUSHBACK_CLEARANCE:
        if (rec->getAircraft()->getTaxiClearanceRequest()) {
            text = receiver + ". Push-back approved. " + sender + ".";
        } else {
            text = receiver + ". Cleared to Taxi. " + sender + ".";
        }
        break;
    case MSG_HOLD_PUSHBACK_CLEARANCE:
        text = receiver + ". Standby. " + sender + ".";
        break;
    case MSG_REQUEST_TAXI_CLEARANCE:
        text = receiver + ". Ready to Taxi. " + sender + ".";
        break;
    case MSG_ISSUE_TAXI_CLEARANCE:
        text = receiver + ". Cleared to taxi. " + sender + ".";
        break;
    case MSG_ACKNOWLEDGE_TAXI_CLEARANCE:
        text = receiver + ". Cleared to taxi. " + sender + ".";
        break;
    case MSG_HOLD_POSITION:
        text = receiver + ". Hold Position. " + sender + ".";
        break;
    case MSG_ACKNOWLEDGE_HOLD_POSITION:
        text = receiver + ". Holding Position. " + sender + ".";
        break;
    case MSG_RESUME_TAXI:
        text = receiver + ". Resume Taxiing. " + sender + ".";
        break;
    case MSG_ACKNOWLEDGE_RESUME_TAXI:
        text = receiver + ". Continuing Taxi. " + sender + ".";
        break;
    case MSG_REPORT_RUNWAY_HOLD_SHORT:
        activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
        text = receiver + ". Holding short runway " + activeRunway + ". " + sender + ".";
        break;
    case MSG_ACKNOWLEDGE_REPORT_RUNWAY_HOLD_SHORT:
        activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
        text = receiver + " Roger. Holding short runway " + activeRunway + ". " + sender + ".";
        break;
    case MSG_LINE_UP_RWY:
        activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
        text = receiver + ". Line up runway " + activeRunway + " and wait." + sender + ".";
        break;
    case MSG_ACKNOWLEDGE_LINE_UP_RWY:
        activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
        text = receiver + ". Line up runway " + activeRunway + " and wait." + sender + ".";
        break;
    case MSG_CLEARED_FOR_TAKEOFF:
        activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
        text = receiver + ". Cleared for takeoff runway " + activeRunway + ". " + sender + ".";
        break;
    case MSG_ACKNOWLEDGE_CLEARED_FOR_TAKEOFF:
        activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
        text = receiver + " Roger. Cleared for takeoff runway " + activeRunway + ". " + sender + ".";
        break;
    case MSG_SWITCH_TOWER_FREQUENCY:
        towerFreqStr = formatATCFrequency3_2(towerFreq);
        text = receiver + " Contact Tower at " + towerFreqStr + ". " + sender + ".";
        break;
    case MSG_ACKNOWLEDGE_SWITCH_TOWER_FREQUENCY:
        towerFreqStr = formatATCFrequency3_2(towerFreq);
        text = receiver + " Roger, switching to tower at " + towerFreqStr + ". " + sender + ".";
        break;
    case MSG_ARRIVAL:
        text = receiver + ". " + sender + " Information delta.";
        break;
    case MSG_ACKNOWLEDGE_ARRIVAL:
        activeRunway = rec->getRunway();
        text = receiver + " expect ILS approach " + activeRunway + ". " + sender;
        break;
    case MSG_CLEARED_TO_LAND:
        activeRunway = rec->getRunway();
        // TODO: Weather
        text = receiver + " runway " + activeRunway + " cleared to land. " + sender;
        break;
    case MSG_ACKNOWLEDGE_CLEARED_TO_LAND:
        activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
        text = receiver + " runway " + activeRunway + " cleared to land. " + sender;
        break;
    case MSG_HOLD:
        text = receiver + " hold as published . " + sender;
        break;
    case MSG_ACKNOWLEDGE_HOLD:
        text = receiver + " holding as published . " + sender;
        break;
    case MSG_TAXI_PARK:
        if (!rec->getAircraft()->GetFlightPlan()->getParkingGate()) {
            SG_LOG(SG_ATC, SG_WARN, "Flightplan without gate " << rec->getCallsign() << " (" << rec->getId() << ") ");
            break;
        }
        text = receiver + " taxi to " + rec->getAircraft()->GetFlightPlan()->getParkingGate()->getName() + " . " + sender;
        break;
    case MSG_ACKNOWLEDGE_TAXI_PARK:
        if (!rec->getAircraft()->GetFlightPlan()->getParkingGate()) {
            SG_LOG(SG_ATC, SG_WARN, "Flightplan without gate " << rec->getCallsign() << " (" << rec->getId() << ") ");
            break;
        }
        text = receiver + " taxi to " + rec->getAircraft()->GetFlightPlan()->getParkingGate()->getName() + " . " + sender;
        break;
    default:
        text = text + sender + ". Transmitting unknown Message. MsgId " + std::to_string(msgId);
        break;
    }

    const bool atcAudioEnabled = fgGetBool("/sim/sound/atc/enabled", false);
    if (audible && atcAudioEnabled) {
        double onBoardRadioFreq0 =
            fgGetDouble("/instrumentation/comm[0]/frequencies/selected-mhz");
        double onBoardRadioFreq1 =
            fgGetDouble("/instrumentation/comm[1]/frequencies/selected-mhz");
        int onBoardRadioFreqI0 = (int)floor(onBoardRadioFreq0 * 100 + 0.5);
        int onBoardRadioFreqI1 = (int)floor(onBoardRadioFreq1 * 100 + 0.5);
        SG_LOG(SG_ATC, SG_DEBUG, "COM1 : " << onBoardRadioFreq0 << " COM2 : " << onBoardRadioFreq1 << " Sending to " << formatATCFrequency3_2(stationFreq) << " Txt : " << text);
        if (stationFreq == 0) {
            SG_LOG(SG_ATC, SG_DEBUG, getName() << " stationFreq not found");
        }

        // Display ATC message only when one of the radios is tuned
        // the relevant frequency.
        // Note that distance attenuation is currently not yet implemented

        if ((stationFreq > 0) &&
            ((onBoardRadioFreqI0 == stationFreq) ||
             (onBoardRadioFreqI1 == stationFreq))) {
            if (rec->allowTransmissions()) {
                if (fgGetBool("/sim/radio/use-itm-attenuation", false)) {
                    SG_LOG(SG_ATC, SG_DEBUG, "Using ITM radio propagation");
                    FGRadioTransmission* radio = new FGRadioTransmission();
                    SGGeod sender_pos;
                    if (ground_to_air) {
                        sender_pos = parent->parent()->geod();
                    } else {
                        sender_pos = rec->getPos();
                    }
                    double frequency = ((double)stationFreq) / 100;
                    radio->receiveATC(sender_pos, frequency, text, ground_to_air);
                    delete radio;
                } else {
                    SG_LOG(SG_ATC, SG_BULK, "Transmitting " << text);
                    fgSetString("/sim/messages/atc", text.c_str());
                }
            }
        }
    } else {
        //FGATCDialogNew::instance()->addEntry(1, text);
    }
}


SGSharedPtr<FGTrafficRecord> FGATCController::getRecord(int id) const
{
    TrafficVectorIterator i = searchActiveTraffic(id);
    if (i == activeTraffic.end()) {
        // Dead traffic should never reach here
        SG_LOG(SG_ATC, SG_WARN,
               "AI error: Aircraft without traffic record is signing off from " << getName() << " list " << activeTraffic.empty());
        return nullptr;
    }
    return *i;
}

/**
 * We share the traffic record much like real life. It gets handed
 * from one controller to the next.
 * @param aiObject
 * @param leg
*/
void FGATCController::handover(SGSharedPtr<FGTrafficRecord> aiObject, int leg)
{
    if (aiObject) {
        aiObject->clearResolveCircularWait();
        activeTraffic.push_back(aiObject);
        if (leg == AILeg::PARKING_TAXI) {
            // The first contact
            if (airportGroundRadar->add(aiObject)) {
                SG_LOG(SG_ATC, SG_DEBUG,
                       "Added " << (aiObject)->getCallsign() << "(" << (aiObject)->getId() << ") " << aiObject);

            } else {
                SG_LOG(SG_ATC, SG_DEV_WARN,
                       "Not Added " << (aiObject)->getCallsign() << "(" << (aiObject)->getId() << ") " << getName());
            }
        }
    }
}

/**
 * Sign off the aircraft with the id from this controller.
 * @param id the id of the aircraft
*/

void FGATCController::signOff(int id)
{
    TrafficVectorIterator i = searchActiveTraffic(id);
    if (i == activeTraffic.end()) {
        // Dead traffic should never reach here
        SG_LOG(SG_ATC, SG_DEV_WARN,
               "AI error: Aircraft without traffic record is signing off from " << getName() << " list " << activeTraffic.empty());
        return;
    }
    const auto leg = (*i)->getLeg();
    // if taken off or parked
    if (((leg > AILeg::TAKEOFF && leg < AILeg::APPROACH) ||
         (leg >= AILeg::PARKING_TAXI)) &&
        airportGroundRadar != nullptr) {
        bool result = airportGroundRadar->remove(*i);
        if (!result) {
            SG_LOG(SG_ATC, SG_DEV_WARN, "Couldn't remove from index " << (*i));
        }

        SG_LOG(SG_ATC, SG_DEBUG, (*i)->getCallsign() << "(" << (*i)->getId() << ") signing off from " << getName() << "(" << getFrequency() << ") and removed from AirportGroundradar Leg " << (*i)->getLeg() << " at " << (*i)->getPos());
    } else {
        SG_LOG(SG_ATC, SG_DEBUG, (*i)->getCallsign() << "(" << (*i)->getId() << ") signing off from " << getName() << "(" << getFrequency() << ") Leg " << (*i)->getLeg() << " at " << (*i)->getPos());
    }

    activeTraffic.erase(i);
}

bool FGATCController::hasInstruction(int id)
{
    // Search activeTraffic for a record matching our id
    TrafficVectorIterator i = searchActiveTraffic(id);

    if (i == activeTraffic.end()) {
        SG_LOG(SG_ATC, SG_DEV_WARN,
               "AI error: checking ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
    } else {
        return (*i)->hasInstruction();
    }
    return false;
}


FGATCInstruction FGATCController::getInstruction(int id)
{
    if (activeTraffic.size() != 0) {
        // Search activeTraffic for a record matching our id
        TrafficVectorIterator i = searchActiveTraffic(id);

        if (i != activeTraffic.end())
            return (*i)->getInstruction();
    }

    SG_LOG(SG_ATC, SG_DEV_WARN, "AI error: requesting ATC instruction for aircraft without traffic record from " << getName());
    return FGATCInstruction();
}


/*
* Format integer frequency xxxyy as xxx.yy
* @param freq - integer value
* @return the formatted string
*/
string FGATCController::formatATCFrequency3_2(int freq)
{
    char buffer[8]; // does this ever need to be freed?
    if (freq > 99999) {
        snprintf(buffer, 8, "%3.3f", ((float)freq / 1000.0));
    } else {
        snprintf(buffer, 8, "%3.3f", ((float)freq / 100.0));
    }
    return string(buffer);
}

// TODO: Set transponder codes according to real-world routes.
// The current version just returns a random string of four octal numbers.
string FGATCController::genTransponderCode(const string& fltRules)
{
    if (fltRules == "VFR")
        return string("1200");

    std::uniform_int_distribution<unsigned> distribution(0, 7);

    unsigned val = (distribution(generator) * 1000 +
                    distribution(generator) * 100 +
                    distribution(generator) * 10 +
                    distribution(generator));

    return std::to_string(val);
}

void FGATCController::eraseDeadTraffic()
{
    // Find the dead ones
    auto it = std::stable_partition(activeTraffic.begin(), activeTraffic.end(), [](const FGTrafficRecord* traffic) {
        return !traffic->isDead();
    });
    TrafficVector deadTraffic(std::make_move_iterator(it), std::make_move_iterator(activeTraffic.end()));
    for (auto r : deadTraffic) {
        airportGroundRadar->remove(r);
    }
    activeTraffic.erase(it, activeTraffic.end());
}

/**
* Search activeTraffic vector to find matching id
* @param id integer to search for in the vector
* @return the matching item OR activeTraffic.end()
*/
TrafficVectorIterator FGATCController::searchActiveTraffic(int id) const
{
    if (activeTraffic.empty()) {
        SG_LOG(SG_ATC, SG_DEBUG, "searchActiveTraffic empty list");
        return activeTraffic.cend();
    }
    return std::find_if(activeTraffic.cbegin(), activeTraffic.cend(),
                        [id](const FGTrafficRecord* rec) { return rec->getId() == id; });
}

void FGATCController::clearTrafficControllers()
{
    for (const auto& traffic : activeTraffic) {
        traffic->clearATCController();
    }
}
