// ATCmgr.cxx - Implementation of FGATCMgr - a global Flightgear ATC manager.
//
// Written by David Luff, started February 2002.
//
// Copyright (C) 2002  David C Luff - david.luff@nottingham.ac.uk
//
// 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.

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <simgear/misc/sg_path.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/exception.hxx>

#include <Airports/simple.hxx>
#include <ATC/CommStation.hxx>
#include <Main/fg_props.hxx>

#include "ATCmgr.hxx"
#include "ATCDialogOld.hxx"
#include "ATCutils.hxx"
#include "atis.hxx"

using flightgear::CommStation;

FGATCMgr::FGATCMgr() :
    initDone(false),
    atc_list(new atc_list_type),
#ifdef ENABLE_AUDIO_SUPPORT
    voice(true),
    voiceOK(false),
    v1(0)
#else
    voice(false),
#endif
{
    globals->set_ATC_mgr(this);
}

FGATCMgr::~FGATCMgr() {
    globals->set_ATC_mgr(NULL);
    delete v1;
}

void FGATCMgr::bind() {
}

void FGATCMgr::unbind() {
}

void FGATCMgr::init() {
    //cout << "ATCMgr::init called..." << endl;
    
    lon_node = fgGetNode("/position/longitude-deg", true);
    lat_node = fgGetNode("/position/latitude-deg", true);
    elev_node = fgGetNode("/position/altitude-ft", true);
    atc_list_itr = atc_list->begin();
    
    // Search for connected ATC stations once per 0.8 seconds or so
    // globals->get_event_mgr()->add( "fgATCSearch()", fgATCSearch,
        //                                 FGEvent::FG_EVENT_READY, 800);
        //  
    // For some reason the above doesn't compile - including Time/event.hxx stops compilation.
        // Is this still true after the reorganization of the event managar??
        // -EMH-
    
    // Initialise the ATC Dialogs
    SG_LOG(SG_ATC, SG_INFO, "  ATC Dialog System");
    current_atcdialog = new FGATCDialog;
    current_atcdialog->Init();

    initDone = true;
    //cout << "ATCmgr::init done!" << endl;
}

void FGATCMgr::update(double dt) {
    if(!initDone) {
        init();
        SG_LOG(SG_ATC, SG_WARN, "Warning - ATCMgr::update(...) called before ATCMgr::init()");
    }
    
    current_atcdialog->Update(dt);
    
    //cout << "Entering update..." << endl;
    //Traverse the list of active stations.
    //Only update one class per update step to avoid the whole ATC system having to calculate between frames.
    //Eventually we should only update every so many steps.
    //cout << "In FGATCMgr::update - atc_list.size = " << atc_list->size() << endl;
    if(atc_list->size()) {
        if(atc_list_itr == atc_list->end()) {
            atc_list_itr = atc_list->begin();
        }
        //cout << "Updating " << (*atc_list_itr)->get_ident() << ' ' << (*atc_list_itr)->GetType() << '\n';
        //cout << "Freq = " << (*atc_list_itr)->get_freq() << '\n';
        //cout << "Updating...\n";
        (*atc_list_itr).second->Update(dt * atc_list->size());
        //cout << "Done ATC update..." << endl;
        ++atc_list_itr;
    }
    
#ifdef ATC_TEST
    //cout << "ATC_LIST: " << atc_list->size() << ' ';
    for(atc_list_iterator it = atc_list->begin(); it != atc_list->end(); it++) {
        cout << (*it)->get_ident() << ' ';
    }
    //cout << '\n';
#endif
    
    // Search the tuned frequencies every now and then - this should be done with the event scheduler
    static int i = 0;   // Very ugly - but there should only ever be one instance of FGATCMgr.
    if(i == 15) {
        //cout << "About to search navcomm1" << endl;
        FreqSearch("comm", 0);
        FreqSearch("nav", 0);
    }
    if(i == 30) {
        //cout << "About to search navcomm2" << endl;
        FreqSearch("comm", 1);
        FreqSearch("nav", 1);
        i = 0;
    }
    ++i;
    
    //cout << "comm1 type = " << comm_type[0] << '\n';
    //cout << "Leaving update..." << endl;
}

typedef map<string,int> MSI;

void FGATCMgr::ZapOtherService(const string ncunit, const string svc_name){
  for (atc_list_iterator svc = atc_list->begin(); svc != atc_list->end(); svc++) {
   
    if (svc->first != svc_name) {
      MSI &actv = svc->second->active_on;
      // OK, we have found some OTHER service;
      // see if it is (was) active on our unit:
      if (!actv.count(ncunit)) continue;
      //cout << "Eradicating '" << svc->first << "' from: " << ncunit << endl;
      actv.erase(ncunit);
      if (!actv.size()) {
        //cout << "Eradicating service: '" << svc->first << "'" << endl;
        svc->second->SetNoDisplay();
        svc->second->Update(0);     // one last update
        SG_LOG(SG_GENERAL, SG_INFO, "would have erased ATC service:" << svc->second->get_name()<< "/"
          << svc->second->get_ident());
        // delete svc->second;
        atc_list->erase(svc);
        // ALL pointers into the ATC list are now invalid,
        // so let's reset them:
        atc_list_itr = atc_list->begin();
      }
      break;        // cannot be duplicates in the active list
    }
  }
}

// Find in list - return a currently active ATC pointer given ICAO code and type
// Return NULL if the given service is not in the list
// - *** THE CALLING FUNCTION MUST CHECK FOR THIS ***
FGATC* FGATCMgr::FindInList(const string& id, const atc_type& tp) {
  string ndx = id + decimalNumeral(tp);
  if (!atc_list->count(ndx)) return 0;
  return (*atc_list)[ndx];
}

// Return a pointer to an appropriate voice for a given type of ATC
// creating the voice if necessary - ie. make sure exactly one copy
// of every voice in use exists in memory.
//
// TODO - in the future this will get more complex and dole out country/airport
// specific voices, and possible make sure that the same voice doesn't get used
// at different airports in quick succession if a large enough selection are available.
FGATCVoice* FGATCMgr::GetVoicePointer(const atc_type& type) {
    // TODO - implement me better - maintain a list of loaded voices and other voices!!
    if(voice) {
        switch(type) {
        case ATIS: case AWOS:
#ifdef ENABLE_AUDIO_SUPPORT
            // Delayed loading fo all available voices, needed because the
            // soundmanager might not be initialized (at all) at this point.
            // For now we'll do one hardwired one

            /* I've loaded the voice even if /sim/sound/pause is true
             *  since I know no way of forcing load of the voice if the user
             *  subsequently switches /sim/sound/audible to true.
             *  (which is the right thing to do -- CLO) :-)
             */
            if (!voiceOK && fgGetBool("/sim/sound/working")) {
                v1 = new FGATCVoice;
                try {
                    voiceOK = v1->LoadVoice("default");
                    voice = voiceOK;
                } catch ( sg_io_exception & e) {
                    voiceOK  = false;
                    SG_LOG(SG_ATC, SG_ALERT, "Unable to load default voice : "
                                            << e.getFormattedMessage().c_str());
                    voice = false;
                    delete v1;
                    v1 = 0;
                }
            }
#endif
            if(voiceOK) {
                return(v1);
            }
        case TOWER:
            return(NULL);
        case APPROACH:
            return(NULL);
        case GROUND:
            return(NULL);
        default:
            return(NULL);
        }
        return(NULL);
    } else {
        return(NULL);
    }
}

// Search for ATC stations by frequency
void FGATCMgr::FreqSearch(const string navcomm, const int unit) {


    string ncunit = navcomm + "[" + decimalNumeral(unit) + "]";
    string commbase = "/instrumentation/" + ncunit;
    string commfreq = commbase + "/frequencies/selected-mhz";
    SGPropertyNode_ptr comm_node = fgGetNode(commfreq.c_str(), false);

    //cout << "FreqSearch: " << ncunit
    //  << "  node: " << comm_node << endl;
    if (!comm_node) return; // no such radio unit

    ATCData data;   
    // Note:  122.375 must be rounded DOWN to 12237 
    // in order to be consistent with apt.dat et cetera.
    int freqKhz = static_cast<int>(comm_node->getDoubleValue() * 100.0 + 0.25);
    
    _aircraftPos = SGGeod::fromDegFt(lon_node->getDoubleValue(),
        lat_node->getDoubleValue(), elev_node->getDoubleValue());

    class RangeFilter : public CommStation::Filter {
    public:
        RangeFilter( const SGGeod & pos ) : 
          CommStation::Filter(), 
          _cart(SGVec3d::fromGeod(pos)),
          _pos(pos)
        {}
      
        virtual bool pass(FGPositioned* aPos) const
        {
            flightgear::CommStation * stn = dynamic_cast<flightgear::CommStation*>(aPos);
            if( NULL == stn ) return false;
          // do the range check in cartesian space, since the distances are potentially
          // large enough that the geodetic functions become unstable
          // (eg, station on opposite side of the planet)
            double rangeM = SGMiscd::max( stn->rangeNm(), 10.0 ) * SG_NM_TO_METER;
            double d2 = distSqr( aPos->cart(), _cart);
          
            return d2 <= (rangeM * rangeM);
        }
    private:
        SGVec3d _cart;
        SGGeod _pos;
    };

    RangeFilter rangeFilter(_aircraftPos );
    
    CommStation* sta = CommStation::findByFreq(freqKhz, _aircraftPos, &rangeFilter );
    if (!sta) {
        ZapOtherService(ncunit, "x x x");
        return;
    }
    
    // Get rid of any *other* service that was on this radio unit:
    FGPositioned::Type ty = sta->type();
    string svc_name = sta->ident() + FGPositioned::nameForType(ty);
    ZapOtherService(ncunit, svc_name);
    // See if the service already exists, possibly connected to
    // some other radio unit:
    if (atc_list->count(svc_name)) {
        // make sure the service knows it's tuned on this radio:
        FGATC* svc = (*atc_list)[svc_name];
        svc->active_on[ncunit] = 1;
        svc->SetDisplay();
        return;
    }

    // This was a switch-case statement but the compiler didn't like
    // the new variable creation with it.
    if(ty == FGPositioned::FREQ_ATIS || ty == FGPositioned::FREQ_AWOS) {
        (*atc_list)[svc_name] = new FGATIS;
        FGATC* svc = (*atc_list)[svc_name];
        if(svc != NULL) {
            svc->SetStation(sta);
            svc->active_on[ncunit] = 1;
            svc->SetDisplay();
            svc->Init();
        }
    }

}
