// atis.cxx - routines to generate the ATIS info string
// This is the implementation of the FGATIS class
//
// Written by David Luff, started October 2001.
//
// Copyright (C) 2001  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.

/////
///// TODO:  _Cumulative_ sky coverage.
///// TODO:  wind _gust_
///// TODO:  more-sensible encoding of voice samples
/////		u-law?  outright synthesis?
///// TODO:  AWOS and ASOS implementation.
/////

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

#include <simgear/compiler.h>

#include <stdlib.h>	// atoi()
#include <stdio.h>	// sprintf
#include <string>
SG_USING_STD(string);

#include STL_IOSTREAM
SG_USING_STD(cout);

#include <simgear/misc/sg_path.hxx>

#include <Environment/environment_mgr.hxx>
#include <Environment/environment.hxx>
#include <Environment/atmosphere.hxx>

#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Airports/runways.hxx>

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

FGATIS::FGATIS() :
  transmission(""),
  trans_ident(""),
  atis_failed(false),
  refname("atis"),
  _prev_display(0),
  old_volume(0),
  attention(0)
{
  _vPtr = globals->get_ATC_mgr()->GetVoicePointer(ATIS);
  _voiceOK = (_vPtr == NULL ? false : true);
  if (!(_type != ATIS || _type == AWOS)) {
       SG_LOG(SG_ATC, SG_ALERT, "ERROR - _type not ATIS or AWOS in atis.cxx");
  }
  fgTie("/environment/attention", this, (int_getter)0, &FGATIS::attend);
}

// Hint:
// http://localhost:5400/environment/attention?value=1&submit=update

FGATIS::~FGATIS() {
  fgUntie("/environment/attention");
}

void FGATIS::Init() {
// Nothing to see here.  Move along.  
}

void
FGATIS::attend (int attn)
{
  attention = attn;
}


// Main update function - checks whether we are displaying or not the correct message.
void FGATIS::Update(double dt) {
#if 0
Called from:
:#1  0x080ca378 in FGATIS::Update (this=0xa66d2a38, dt=0.050000000000000003) at atis.cxx:77
:#2  0x080b174e in FGATCMgr::update (this=0xb38f130, dt=0.025000000000000001) at ATCmgr.cxx:175
:#3  0x0805e92a in fgMainLoop () at main.cxx:458
:#4  0x0809bce1 in GLUTidle () at fg_os.cxx:122
:#5  0xb7b06d3a in glutMainLoop () from /usr/lib/libglut.so.3
:#6  0x0805bbad in fgMainInit (argc=1, argv=0xbffce9e4) at main.cxx:1029
#endif
  cur_time = globals->get_time_params()->get_cur_time();
  msg_OK = (msg_time < cur_time);
#if 0
  if (msg_OK || _display != _prev_display) {
    cout << "ATIS Update: " << _display << "  " << _prev_display 
	  << "  len: " << transmission.length() 
	  << "  oldvol: " << old_volume
	  << "  dt: " << dt << endl;
    msg_time = cur_time;
  }
#endif
  if(_display) {
    double volume(0);
    for (map<string,int>::iterator act = active_on.begin();
	act != active_on.end(); act++) {
      string prop = "/instrumentation/" + act->first + "/volume";
      volume += globals->get_props()->getDoubleValue(prop.c_str());
    }

// Check if we need to update the message
// - basically every hour and if the weather changes significantly at the station
// If !_prev_display, the radio had been detuned for a while and our 
// "transmission" variable was lost when we were de-instantiated.
    int rslt = GenTransmission(!_prev_display, attention);
    if (rslt || volume != old_volume) {
      //cout << "ATIS calling ATC::render  volume: " << volume << endl;
      Render(transmission, volume, refname, true);
      old_volume = volume;
    }
  } else {
// We shouldn't be displaying
    //cout << "ATIS.CXX - calling NoRender()..." << endl;
    NoRender(refname);
  }
  _prev_display = _display;
  attention = 0;
}

string replace(const string orig, const string ooo, const string nnn){
  string work = orig;
  size_t where(0);
  for ( ; (where = work.find(ooo, where)) != string::npos ; ) {
    work.replace(where, ooo.length(), nnn);
    where += nnn.length();
  }
  return work;
}

// Generate the actual broadcast ATIS transmission.
// Regen means regenerate the /current/ transmission.
// Special means generate a new transmission, with a new sequence.
// Returns 1 if we actually generated something.
int FGATIS::GenTransmission(const int regen, const int special) {

  double tstamp = atof(fgGetString("sim/time/elapsed-sec"));
  string BRK = ".\n";
  int sequence = current_commlist->GetAtisSequence(ident, tstamp, special);
  if (!regen && sequence > LTRS) {
//xx	  if (msg_OK) cout << "ATIS:  no change: " << sequence << endl;
//xx	  msg_time = cur_time;
    return 0;	// no change since last time
  }

  const int bs(100);
  char buf[bs];
  string time_str = fgGetString("sim/time/gmt-string");
  string hours, mins;
  string phonetic_seq_string;

  FGEnvironment stationweather =
      ((FGEnvironmentMgr *)globals->get_subsystem("environment"))
	->getEnvironment(lat, lon, 0.0);

  transmission = "";

  // UK CAA radiotelephony manual indicated ATIS transmissions start with "This is ..."
  // In the US they just start with the airport name.
  /// transmission += "This_is ";
  // transmitted station name.
  //cout << "In atis.cxx, airport name = " << name << endl;

///////////////
// FIXME:  This would be more flexible and more extensible
// if the mappings were taken from an XML file, not hard-coded.
///////////////

// Remap some abbreviations that occur in apt.dat, to 
// make things nicer for the text-to-speech system:
  string name2 = name + " ";
  name2 = replace(name2, "Intl ", 	"International ");
  name2 = replace(name2, "Rgnl ",	"Field ");
  name2 = replace(name2, "Muni ", 	"Municipal ");
  name2 = replace(name2, "Mem ", 	"Memorial ");
  name2 = replace(name2, "Fld ", 	"Field ");
  name2 = replace(name2, "AFB ", 	"Air Force Base ");
  name2 = replace(name2, "AAF ", 	"Army Air Field ");
  name2 = replace(name2, "MCAS ", 	"Marine Corps Air Station ");
  transmission += name2;
  if (_type == ATIS /* as opposed to AWOS */) {
    transmission += "airport information ";
    // Add the sequence letter
    phonetic_seq_string = GetPhoneticLetter(sequence);
    transmission += phonetic_seq_string + BRK;
  }
  transmission += "Automated weather observation ";
// Warning - this is fragile if the time string format changes
  hours = time_str.substr(0,2).c_str();
  mins  = time_str.substr(3,2).c_str();
// speak each digit separately:
  transmission += ConvertNumToSpokenDigits(hours + mins);
  transmission += " zulu weather" + BRK;

  transmission += "Wind: ";
  double speed = stationweather.get_wind_speed_kt();
  double wind_dir = stationweather.get_wind_from_heading_deg();
  while (wind_dir <= 0) wind_dir += 360;
// The following isn't as bad a kludge as it might seem.
// It combines the magvar at the /aircraft/ location with
// the wind direction in the stationweather environment.
// But if the aircraft is close enough to the station to
// be receiving the ATIS signal, this should be a good-enough
// approximation.  For more-distant aircraft, the wind_dir
// shouldn't be corrected anyway.
// The less-kludgy approach would be to use the magvar associated
// with the station, but that is not tabulated in the stationweather
// structure as it stands, and computing it would be expensive.
// Also note that as it stands, there is only one environment in
// the entire FG universe, so the aircraft environment is the same
// as the station environment anyway.
  wind_dir -= fgGetDouble("/environment/magnetic-variation-deg");		// wind_dir now magnetic
  if (speed == 0) {
// Force west-facing rwys to be used in no-wind situations
// which is consistent with Flightgear's initial setup:
	  wind_dir = 270;	
	  transmission += " light_and_variable";
  } else {
	  // FIXME: get gust factor in somehow
	  snprintf(buf, bs, "%03.0f", 5*round(wind_dir/5));
	  transmission += ConvertNumToSpokenDigits(buf);

	  snprintf(buf, bs, "%1.0f", speed);
	  transmission += " at " + ConvertNumToSpokenDigits(buf) + BRK;
  }

  int did_some(0);
  int did_ceiling(0);
  double ceiling;
  for (int layer = 0; layer <= 4; layer++) {
    snprintf(buf, bs, "/environment/clouds/layer[%i]/coverage", layer);
    string coverage = fgGetString(buf);
    if (coverage == "clear") continue;
    snprintf(buf, bs, "/environment/clouds/layer[%i]/thickness-ft", layer);
    if (fgGetDouble(buf) == 0) continue;
    snprintf(buf, bs, "/environment/clouds/layer[%i]/elevation-ft", layer);
    double ceiling = int(fgGetDouble(buf) - elev);
    if (ceiling > 12000) continue;
    if (coverage == "scattered") {
      if (!did_some) transmission += "   Sky condition: ";
      did_some++;
    } else /* must be a ceiling */  if (!did_ceiling) {
      transmission += "   Ceiling: ";
      did_ceiling++;
      did_some++;
    } else {
      transmission += "   ";
    }
    int cig00  = int(round(ceiling/100));  // hundreds of feet
    if (cig00) {
      int cig000 = cig00/10;
      cig00 -= cig000*10;		// just the hundreds digit
      if (cig000) {
	snprintf(buf, bs, "%i", cig000);
	transmission += ConvertNumToSpokenDigits(buf);
	transmission += " thousand ";
      }
      if (cig00) {
	snprintf(buf, bs, "%i", cig00);
	transmission += ConvertNumToSpokenDigits(buf);
	transmission += " hundred ";
      }
    } else {
      // Should this be "sky obscured?"
      transmission += " zero ";		// not "zero hundred"
    }
    transmission += coverage + BRK;
  }

  transmission += "Temperature: ";
  int temp = int(round(FGAtmo().fake_t_vs_a_us(elev)));
  if(temp < 0) {
	  transmission += "minus ";
  }
  snprintf(buf, bs, "%i", abs(temp));
  transmission += ConvertNumToSpokenDigits(buf);
  transmission += " dewpoint ";
  double dpsl = fgGetDouble("/environment/dewpoint-sea-level-degc");
  temp = int(round(FGAtmo().fake_dp_vs_a_us(dpsl, elev)));
  if(temp < 0) {
	  transmission += "minus ";
  }
  snprintf(buf, bs, "%i", abs(temp));
  transmission += ConvertNumToSpokenDigits(buf) + BRK;


  transmission += "Visibility: ";
  double visibility = stationweather.get_visibility_m();
  visibility /= atmodel::sm;	// convert to statute miles
  if (visibility < 0.25) {
    transmission += "less than one quarter";
  } else if (visibility < 0.5) {
    transmission += "one quarter";
  } else if (visibility < 0.75) {
    transmission += "one half";
  } else if (visibility < 1.0) {
    transmission += "three quarters";
  } else if (visibility >= 1.5 && visibility < 2.0) {
    transmission += "one and one half";
  } else {
    // integer miles
    if (visibility > 10) visibility = 10;
    sprintf(buf, "%i", int(.5 + visibility));
    transmission += ConvertNumToSpokenDigits(buf);
  }
  transmission += BRK;

  transmission += "Altimeter: ";
  double QNH;
  double Psl = fgGetDouble("/environment/pressure-sea-level-inhg");
  {
    using namespace atmodel;
    QNH = FGAtmo().qnh(elev, FGAtmo().p_vs_a(elev*foot, Psl*inHg) / inHg);
  }
  if(ident.substr(0,2) == "EG" && fgGetBool("/sim/atc/use-millibars")) {
	  // Convert to millibars for the UK!
	  QNH *= 33.864;
	  if  (QNH > 1000) QNH -= 1000;
  } else {
	  QNH *= 100;
  }		
  snprintf(buf, bs, "%.0f", QNH);
  transmission += ConvertNumToSpokenDigits(buf) + BRK;

  if (_type == ATIS /* as opposed to AWOS */) {
    string rwy_no = globals->get_runways()->search(ident, int(wind_dir));
    if(rwy_no != "NN") {
	    transmission += "Landing and departing runway ";
	    transmission += ConvertRwyNumToSpokenString(rwy_no) + BRK;
	    if (msg_OK) {
	      msg_time = cur_time;
	      //cout << "In atis.cxx, r.rwy_no: " << rwy_no 
	      //   << " wind_dir: " << wind_dir << endl;
	    }
    }
    transmission += "On initial contact advise you have information ";
    transmission += phonetic_seq_string;
    transmission += "... " + BRK;
  }
#ifdef ATIS_TEST  
  cout << "**** ATIS active on:";
#endif  
  for (map<string,int>::iterator act = active_on.begin(); act != active_on.end(); act++){
    string prop = "/instrumentation/" + act->first + "/atis";
    globals->get_props()->setStringValue(prop.c_str(), 
      ("<pre>\n" + transmission + "</pre>\n").c_str());
#ifdef ATIS_TEST  
    cout << "  " << prop;
#endif
  }
#ifdef ATIS_TEST  
  cout << " ****" << endl;
  cout << transmission << endl;
// Note that even if we aren't outputting the transmission
// on stdout, you can still see it by pointing a web browser
// at the property tree.  The second comm radio is:
// http://localhost:5400/instrumentation/comm%5B1%5D
#endif

// Take the previous English-looking string and munge it to
// be relatively-more acceptable to the primitive tts system.
// Note that : ; and . are among the token-delimeters recognized
// by the tts system.
  for (unsigned int where;;) {
    where = transmission.find_first_of(":.");
    if (where == string::npos) break;
    transmission.replace(where, 1, " /_ ");
  }
  return 1;
}
