// 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 <string>
#include <cstdlib>
#include <cstring> // for strcmp

#include "dynamicloader.hxx"

#include <Navaids/NavDataCache.hxx>
#include <Airports/airport.hxx>
#include <Airports/dynamics.hxx>
#include <Airports/groundnetwork.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/exception.hxx>

using std::string;

/*****************************************************************************
 * Helper function for parsing position string
 ****************************************************************************/
static double processPosition(const string &pos)
{
    string subs{pos};
    int sign = 1;
    const auto prefix = subs.substr(0, 1);
    if (prefix == "S" || (prefix == "W"))
        sign = -1;
    subs = subs.substr(1, subs.length()); // drop first character
    const auto spacePos = subs.find(" ", 0);
    const auto degree = subs.substr(0, spacePos);
    const auto decimal = subs.substr(spacePos, subs.length());

    return sign * (stoi(degree) + (stof(decimal) / 60.0));
}

FGGroundNetXMLLoader::FGGroundNetXMLLoader(FGGroundNetwork* net):
    XMLVisitor(),
    _groundNetwork(net)
{}

void  FGGroundNetXMLLoader::startXML () {
  //cout << "FGAirportDynamicsLoader::Start XML" << endl;
}

void  FGGroundNetXMLLoader::endXML ()
{
  ParkingPushbackIndex::const_iterator it;

  for (it = _parkingPushbacks.begin(); it != _parkingPushbacks.end(); ++it) {
    NodeIndexMap::const_iterator j = _indexMap.find(it->second);
    if (j == _indexMap.end()) {
        _hasErrors = true;
        SG_LOG(SG_NAVAID, SG_DEV_WARN,  "bad groundnet " << _groundNetwork->airport()->getId() << " , no node for index:" << it->first.get()->getIndex());
        continue;
    }

    it->first->setPushBackPoint(j->second);

  }

  if (!_unreferencedNodes.empty()) {
      _hasErrors = true;
  }

  for (const FGTaxiNodeRef& node: _unreferencedNodes) {
    SG_LOG(SG_NAVAID, SG_DEV_WARN,
           "unreferenced groundnet node: " << node->getIndex());
  }

}

void FGGroundNetXMLLoader::startParking(const XMLAttributes &atts)
{
  string type;
  int index = 0;
  string gateName, gateNumber;
  string lat, lon;
  double heading = 0.0;
  double radius = 1.0;
  string airlineCodes;
  int pushBackRoute = -1;       // signals unseen attribute

  savePosition();               // allows to retrieve the line number

  for (int i = 0; i < atts.size(); i++)
  {
    string attname(atts.getName(i));
	  if (attname == "index") {
      index = std::atoi(atts.getValue(i));
    } else if (attname == "type")
	    type = atts.getValue(i);
    else if (attname == "name")
      gateName = atts.getValue(i);
	  else if (attname == "number")
	    gateNumber = atts.getValue(i);
	  else if (attname == "lat")
      lat = atts.getValue(i);
	  else if (attname == "lon")
	    lon = atts.getValue(i);
	  else if (attname == "heading")
	    heading = std::atof(atts.getValue(i));
	  else if (attname == "radius") {
	    string radiusStr = atts.getValue(i);
	    if (radiusStr.find("M") != string::npos)
	      radiusStr = radiusStr.substr(0, radiusStr.find("M",0));
	    radius = std::atof(radiusStr.c_str());
	  }
	  else if (attname == "airlineCodes")
      airlineCodes = atts.getValue(i);
    else if (attname == "pushBackRoute") {
      const string attrVal = atts.getValue(i);
      try {
          if (attrVal.empty() || (attrVal == "None")) {
              pushBackRoute = -2;
          } else {
              pushBackRoute = simgear::strutils::readNonNegativeInt<int>(attrVal);
          }
      } catch (const sg_exception& e) {
        SG_LOG(SG_NAVAID, SG_DEV_WARN,
               getPath() << ":" << getLine() << ": " <<
               "invalid value for 'pushBackRoute': " << e.what());
        _hasErrors = true;
        pushBackRoute = -2;
      }
    }
  }

  SGGeod pos(SGGeod::fromDeg(processPosition(lon), processPosition(lat)));

  FGParkingRef parking(new FGParking(index,
                                     pos, heading, radius,
                                     gateName + gateNumber,
                                     type, airlineCodes));
  if (pushBackRoute >= 0) {
    _parkingPushbacks[parking] = pushBackRoute;
  }

  _indexMap[index] = parking;
  _groundNetwork->addParking(parking);
}

void FGGroundNetXMLLoader::startNode(const XMLAttributes &atts)
{
  int index = 0;
  string lat, lon;
  bool onRunway = false;
  int holdPointType = 0;

  for (int i = 0; i < atts.size() ; i++)
	{
	  string attname(atts.getName(i));
	  if (attname == "index")
	    index = std::atoi(atts.getValue(i));
	  else if (attname == "lat")
      lat = atts.getValue(i);
	  else if (attname == "lon")
	    lon = atts.getValue(i);
    else if (attname == "isOnRunway")
      onRunway = std::atoi(atts.getValue(i)) != 0;
	  else if (attname == "holdPointType") {
      string attval = atts.getValue(i);
      if (attval=="none") {
        holdPointType=0;
      } else if (attval=="normal") {
        holdPointType=1;
      } else if (attval=="CAT II/III") {
        holdPointType=3;
      } else if (attval=="PushBack") {
        holdPointType=3;
      } else {
        holdPointType=0;
      }
    }
	}

  if (_indexMap.find(index) != _indexMap.end()) {
    SG_LOG(SG_NAVAID, SG_DEV_WARN, "duplicate ground-net index:" << index);
    _hasErrors = true;
  }

  SGGeod pos(SGGeod::fromDeg(processPosition(lon), processPosition(lat)));
  FGTaxiNodeRef node(new FGTaxiNode(FGPositioned::TAXI_NODE, index, pos, onRunway, holdPointType));
  _indexMap[index] = node;
  _unreferencedNodes.insert(node);
}

void FGGroundNetXMLLoader::startArc(const XMLAttributes &atts)
{
  int begin = 0, end = 0;
  bool isPushBackRoute = false;

  for (int i = 0; i < atts.size() ; i++)
	{
	  string attname = atts.getName(i);
	  if (attname == "begin")
	    begin = std::atoi(atts.getValue(i));
	  else if (attname == "end")
	    end = std::atoi(atts.getValue(i));
    else if (attname == "isPushBackRoute")
	    isPushBackRoute = std::atoi(atts.getValue(i)) != 0;
	}

  IntPair e(begin, end);
  if (_arcSet.find(e) != _arcSet.end()) {
    SG_LOG(SG_NAVAID, SG_DEV_WARN, _groundNetwork->airport()->ident() << " ground-net: skipping duplicate edge:" << begin << "->" << end);
    _hasErrors = true;
    return;
  }

  NodeIndexMap::const_iterator it;
  FGTaxiNodeRef fromNode, toNode;
  it = _indexMap.find(begin);
  if (it == _indexMap.end()) {
      SG_LOG(SG_NAVAID, SG_DEV_WARN, "ground-net: bad edge:" << begin << "->" << end << ", begin index unknown");
      _hasErrors = true;
      return;
  } else {
      _unreferencedNodes.erase(it->second);
      fromNode = it->second;
  }

  it = _indexMap.find(end);
  if (it == _indexMap.end()) {
      SG_LOG(SG_NAVAID, SG_DEV_WARN, "ground-net: bad edge:" << begin << "->" << end << ", end index unknown");
      _hasErrors = true;
      return;
  } else {
      _unreferencedNodes.erase(it->second);
      toNode = it->second;
  }

  _arcSet.insert(e);
  _groundNetwork->addSegment(fromNode, toNode);
  if (isPushBackRoute) {
//    toNode->setIsPushback();
  }
}

void FGGroundNetXMLLoader::startElement (const char * name, const XMLAttributes &atts)
{
  if (!strcmp("Parking", name)) {
    startParking(atts);
  } else if (!strcmp("node", name)) {
    startNode(atts);
  } else if (!strcmp("arc", name)) {
    startArc(atts);
  }
}

void  FGGroundNetXMLLoader::endElement (const char * name)
{
  int valueAsInt = atoi(value.c_str());
  if (!strcmp("version", name)) {
    _groundNetwork->addVersion(valueAsInt);
  } else if (!strcmp("AWOS", name)) {
    _groundNetwork->addAwosFreq(valueAsInt);
  } else if (!strcmp("UNICOM", name)) {
    _groundNetwork->addUnicomFreq(valueAsInt);
  } else if (!strcmp("CLEARANCE", name)) {
    _groundNetwork->addClearanceFreq(valueAsInt);
  } else if (!strcmp("GROUND", name)) {
    _groundNetwork->addGroundFreq(valueAsInt);
  } else if (!strcmp("TOWER", name)) {
    _groundNetwork->addTowerFreq(valueAsInt);
  } else if (!strcmp("APPROACH", name)) {
    _groundNetwork->addApproachFreq(valueAsInt);
  }
}

void  FGGroundNetXMLLoader::data (const char * s, int len) {
  string token = string(s,len);
  //cout << "Character data " << string(s,len) << endl;
  if ((token.find(" ") == string::npos && (token.find('\n')) == string::npos))
    value += token;
  else
    value = string("");
}

void  FGGroundNetXMLLoader::pi (const char * target, const char * data) {
  //cout << "Processing instruction " << target << ' ' << data << endl;
}

void  FGGroundNetXMLLoader::warning (const char * message, int line, int column) {
    SG_LOG(SG_IO, SG_DEV_WARN, "Warning: " << message << " (" << line << ',' << column << ')');
}

void  FGGroundNetXMLLoader::error (const char * message, int line, int column) {
    SG_LOG(SG_IO, SG_DEV_ALERT, "Error: " << message << " (" << line << ',' << column << ')');
}
