/***************************************************************************
**
**  This file is part of gpcoord.
**
**  gpcoord 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 3 of the License, or
**  (at your option) any later version.
**
**  gpcoord 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 Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2009-06-19
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>

#include <QGpCoreTools.h>
#include <ArrayCore.h>

#include "CoordReader.h"

/*!
  \class CoordReader CoordReader.h
  \brief Brief description of class still missing

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
CoordReader::CoordReader()
    : ArgumentStdinReader()
{
  TRACE;
  _mode=None;
  _averageCount=0;
  _kml=nullptr;
  _distanceStddev=0.01; // 1 cm
  _priorPrecision=5.0;  // 2 m
  _posteriorPrecision=0;  // No posterior precision specified
  _fromDistancesOutMode=NoOutput;
  _progress=new ConsoleProgress;
  _clusterCount=500;
}

CoordReader::~CoordReader()
{
  TRACE;
  delete _progress;
}

bool CoordReader::terminate()
{
  QTextStream sOut(stdout);
  switch(_mode) {
  case Distance:
  case DistanceLookup:
  case DistanceGeo:
  case Azimuth:
  case AzimuthGeo:
  case FromGeo:
  case ToGeo:
  case FromDMS:
  case ToDMS:
  case FromDM:
  case ToDM:
  case ToUTM:
  case FromUTM:
  case Translate:
  case Rotate:
  case Localize:
  case Lookup:
  case GenerateCircle:
  case UTMDeclination:
  case MagDeclination:
  case AllDistances:
  case None:
    break;
  case TriangulateDistance:
    if(_circles.count()==2) {
      QVector<Point2D> p=_circles.first().intersect(_circles.last());
      for(QVector<Point2D>::iterator it=p.begin(); it!=p.end(); it++) {
        sOut << it->x() << " " << it->y() << endl;
      }
    } else if(_circles.count()>=3) {
      bool ok;
      int n=0;
      Point2D pAver(0, 0), pDev(0, 0);
      for(int i=0; i<_circles.count(); i++) {
        for(int j=i+1; j<_circles.count(); j++) {
          // Used to discriminate between the classical
          // two solutions of the intersection of two circles.
          int disc;
          if(j+1<_circles.count()) {
            disc=j+1;
          } else if(i+1<j) {
            disc=i+1;
          } else {
            disc=i-1;
          }
          Point2D p=_circles.at(i).intersect(_circles.at(j), ok, _circles.at(disc));
          if(ok) {
            pAver+=p;
            pDev+=p*p;
            n++;
          }
        }
      }
      if(n>0) {
        pAver/=n;
        pDev/=n;
        pDev-=pAver * pAver;
      } else {
        App::log(tr("No intersection\n") );
        return false;
      }
      sOut << pAver.x() << " " << pAver.y() << " "
           << sqrt(pDev.x()) << " " << sqrt(pDev.y()) << endl;
    } else {
      App::log(tr("At least two distances are required to triangulate.\n") );
      return false;
    }
    break;
  case TriangulateAzimuth:
    if(_lines.count()>=2) {
      bool ok;
      int n=0;
      Point2D pAver(0, 0), pDev(0, 0);
      for(int i=0; i<_lines.count(); i++) {
        for(int j=i+1; j<_lines.count(); j++) {
          Point2D p=_lines.at(i).intersect(_lines.at(j), ok);
          if(ok) {
            pAver+=p;
            pDev+=p*p;
            n++;
          }
        }
      }
      if(n>0) {
        pAver/=n;
        pDev/=n;
        pDev-=pAver * pAver;
      } else {
        App::log(tr("No intersection\n") );
        return false;
      }
      sOut << pAver.x() << " " << pAver.y() << " "
           << sqrt(pDev.x()) << " " << sqrt(pDev.y()) << endl;
    } else {
      App::log(tr("At least two azimuth are required to triangulate.\n") );
      return false;
    }
    break;
  case Average:
    if(_averageCount>0) {
      _average/=static_cast<double>(_averageCount);
      sOut << _average.toString(20) << endl;
    } else {
      App::log(tr("No point available to calculate average\n") );
      return false;
    }
    break;
  case ToKML:
    _kml->save(_kmlFileName);
    delete _kml;
    break;
  case FromKML: {
      GoogleEarthKML kml;
      XMLHeader hdr(&kml);
      if(hdr.xml_restoreFile(_kmlFileName, 0, XMLClass::XmlFile)!=XMLClass::NoError) {
        App::log(tr("Error while reading file %1\n").arg(_kmlFileName) );
        return false;
      }
      QList<NamedPoint> list=kml.document().points();
      for(QList<NamedPoint>::iterator it=list.begin(); it!=list.end(); it++) {
        sOut << (*it).toString(20) << endl;
      }
    }
    break;
  case FromDistances: {
      Node * origin=_fromDistanceFactory.node(_origin);
      if(!origin) {
        App::log(tr("gpcoord: origin node '%1' does not exist\n").arg(_origin) );
        return false;
      }
      Node * north=_fromDistanceFactory.node(_north);
      if(!north) {
        App::log(tr("gpcoord: north node '%1' does not exist\n").arg(_north) );
        return false;
      }
      Node * eastward=_fromDistanceFactory.node(_eastward);
      if(!eastward) {
        App::log(tr("gpcoord: eastward node '%1' does not exist\n").arg(_eastward) );
        return false;
      }
      if(_posteriorPrecision>0.0) {
        _fromDistanceFactory.setPosteriorPrecision(_posteriorPrecision);
      }
      CoreApplication::instance()->debugUserInterrupts(false);
      _fromDistanceFactory.aggregate(_clusterCount, _distanceStddev);
      if(_progress) {
        _progress->end(_fromDistanceFactory.clusters().count());
      }
      CoreApplication::instance()->debugUserInterrupts(true);
      _fromDistanceFactory.setCoordinates(origin, north, eastward);
      if(!_refPoints.isEmpty() ) {
        _fromDistanceFactory.setPriorCoordinates(_refPoints, _priorPrecision);
      }
      QList<Node *> nodes=_fromDistanceFactory.nodes();
      for(QList<Node *>::iterator it=nodes.begin(); it!=nodes.end(); it++) {
        Node * n=*it;
        if(_fromDistancesOutMode & Raw) {
          QSet<Point2D> coordinates=_fromDistanceFactory.solutions(n->name());
          for(QSet<Point2D>::iterator its=coordinates.begin(); its!=coordinates.end(); its++) {
            sOut << its->toString() << " 0 " << n->name() << endl;
          }
        }
        if(_fromDistancesOutMode & (Mean | Ellipse)) {
          Covariance cov=_fromDistanceFactory.covariance(n->name());
          if(_fromDistancesOutMode & Ellipse) {
            sOut << cov.stddev2D().toString() << endl;
          }
          if(_fromDistancesOutMode & Mean) {
            sOut << cov.mean(0) << " " << cov.mean(1) << " 0 " <<  n->name() << endl;
          }
        }
      }
      if(_fromDistancesOutMode & Clusters) {
        _fromDistanceFactory.printClusters();
      }
    }
    break;
  case ArrayLimits: {
      KminSolver kminSolver(_array);
      bool ok=true;
      double kmin=kminSolver.calculate(ok);
      if(ok && kmin>0.0) {
        KmaxSolver kmaxSolver(_array, kmin, 0.25);
        kmaxSolver.calculate();
        while(!kmaxSolver.isRunning()) {}
        if(!kmaxSolver.wait(10000)) {
          sOut << "# kmax is at least... search stopped after 10 s" << endl;
        }
        double kmax=kmaxSolver.kmax(ok);
        if(ok) {
          sOut << "#kmin kmax [rad/m, according to Wathelet et al. 2008]" << endl;
          sOut << kmin << " " << kmax << endl;
        } else {
          App::log(tr("error computing kmax\n"));
        }
      } else {
        App::log(tr("error computing kmin (linear array?)\n"));
      }
    }
    break;
  }
  return true;
}

bool CoordReader::setOptions(int& argc, char ** argv)
{
  TRACE;
  QString coordFile;
  int i, j=1;
  for(i=1; i<argc; i++) {
    QByteArray arg=argv[i];
    if(arg[0]=='-') {
      if(arg=="-distance") {
        _mode=Distance;
      } else if(arg=="-distance-lookup") {
        _mode=DistanceLookup;
        CoreApplication::checkOptionArg(i, argc, argv);
        coordFile=argv[i];
      } else if(arg=="-distance-geo") {
        _mode=DistanceGeo;
      } else if(arg=="-all-distances") {
        _mode=AllDistances;
        CoreApplication::checkOptionArg(i, argc, argv);
        coordFile=argv[i];
      } else if(arg=="-azimuth") {
        _mode=Azimuth;
      } else if(arg=="-azimuth-geo") {
        _mode=AzimuthGeo;
      } else if(arg=="-from-dms") {
        _mode=FromDMS;
      } else if(arg=="-to-dms") {
        _mode=ToDMS;
      } else if(arg=="-from-dm") {
        _mode=FromDM;
      } else if(arg=="-to-dm") {
        _mode=ToDM;
      } else if(arg=="-from-geo") {
        _mode=FromGeo;
      } else if(arg=="-to-geo") {
        _mode=ToGeo;
      } else if(arg=="-to-utm") {
        if(CoreApplication::checkOptionArg(i, argc, argv, false)) {
          _utmZone.fromString(argv[i]);
          if(!_utmZone.isValid()) {
            App::log(tr("gpcoord: %1\n").arg(UtmZone::parsingErrorMessage(argv[i])) );
            return false;
          }
        }
        _mode=ToUTM;
      } else if(arg=="-from-utm") {
        if(CoreApplication::checkOptionArg(i, argc, argv, false)) {
          _utmZone.fromString(argv[i]);
          if(!_utmZone.isValid()) {
            App::log(tr("gpcoord: %1\n").arg(UtmZone::parsingErrorMessage(argv[i])) );
            return false;
          }
        }
        _mode=FromUTM;
      } else if(arg=="-to-kml") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _kmlFileName=argv[i];
        _mode=ToKML;
        delete _kml;
        _kml=new GoogleEarthKML;
        GoogleEarthKML::Document& doc=_kml->document();
        QFileInfo fi(_kmlFileName);
        doc.setName(fi.baseName());
        GoogleEarthKML::Folder * f=doc.mainFolder();
        f->setName(fi.baseName());
      } else if(arg=="-from-kml") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _kmlFileName=argv[i];
        _mode=FromKML;
      } else if(arg=="-reference") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _geoReference.setX(atof(argv[i]));
        CoreApplication::checkOptionArg(i, argc, argv);
        _geoReference.setY(atof(argv[i]));
      } else if(arg=="-translate") {
        _mode=Translate;
        CoreApplication::checkOptionArg(i, argc, argv);
        _translateVector.setX(atof(argv[i]));
        CoreApplication::checkOptionArg(i, argc, argv);
        _translateVector.setY(atof(argv[i]));
      } else if(arg=="-rotate") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _mode=Rotate;
        // Input is counted clockwize while Angle uses math angles
        _angle.setDegrees(-atof(argv[i]));
      } else if(arg=="-average") {
        _mode=Average;
      } else if(arg=="-localize") {
        _mode=Localize;
        CoreApplication::checkOptionArg(i, argc, argv);
        _cartReference.setX(atof(argv[i]));
        CoreApplication::checkOptionArg(i, argc, argv);
        _cartReference.setY(atof(argv[i]));
      } else if(arg=="-lookup") {
        _mode=Lookup;
        CoreApplication::checkOptionArg(i, argc, argv);
        coordFile=argv[i];
      } else if(arg=="-trig-dist") {
        _mode=TriangulateDistance;
        CoreApplication::checkOptionArg(i, argc, argv);
        coordFile=argv[i];
      } else if(arg=="-trig-az") {
        _mode=TriangulateAzimuth;
        CoreApplication::checkOptionArg(i, argc, argv);
        coordFile=argv[i];
      } else if(arg=="-circle") {
        _mode=GenerateCircle;
        CoreApplication::checkOptionArg(i, argc, argv);
        double radius=atof(argv[i]);
        CoreApplication::checkOptionArg(i, argc, argv);
        int n=atoi(argv[i]);
        int i0=0;
        if(CoreApplication::checkOptionArg(i, argc, argv, false)) {
          i0=atoi(argv[i]);
        }
        QTextStream sOut(stdout);
        Angle a;
        NamedPoint p;
        p.setName(QString("S%1").arg(i0, 3, 10, QChar('0')));
        sOut << p.toString(20) << endl;
        for(int i=0; i<n; i++) {
          a.setDegreeAzimuth(i*360.0/n);
          p.setX(radius*a.cos());
          p.setY(radius*a.sin());
          p.setName(QString("S%1").arg(i0+i+1, 3, 10, QChar('0')));
          sOut << p.toString(20) << endl;
        }
      } else if(arg=="-from-distances") {
        _mode=FromDistances;
      } else if(arg=="-array-limits") {
        _mode=ArrayLimits;
      } else if(arg=="-stddev") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _distanceStddev=atof(argv[i]);
      } else if(arg=="-origin") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _origin=argv[i];
      } else if(arg=="-north") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _north=argv[i];
      } else if(arg=="-eastward") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _eastward=argv[i];
      } else if(arg=="-prior-positions") {
        CoreApplication::checkOptionArg(i, argc, argv);
        coordFile=argv[i];
      } else if(arg=="-prior-precision") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _priorPrecision=atof(argv[i]);
      } else if(arg=="-posterior-precision") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _posteriorPrecision=atof(argv[i]);
      } else if(arg=="-cluster-count") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _clusterCount=atoi(argv[i]);
      } else if(arg=="-no-progress") {
        delete _progress;
        _progress=nullptr;
      } else if(arg=="-utm-declination") {
        _mode=UTMDeclination;
      } else if(arg=="-mag-declination") {
        _mode=MagDeclination;
        CoreApplication::checkOptionArg(i, argc, argv);
        int year=atoi(argv[i]);
        CoreApplication::checkOptionArg(i, argc, argv);
        int month=atoi(argv[i]);
        CoreApplication::checkOptionArg(i, argc, argv);
        int day=atoi(argv[i]);
        _date=QDate(year, month, day);
      } else if(arg=="-out") {
        CoreApplication::checkOptionArg(i, argc, argv);
        QString m=argv[i];
        m=m.toLower();
        if(m=="raw") {
          _fromDistancesOutMode=static_cast<FromDistancesOutMode>(_fromDistancesOutMode | Raw);
        } else if(m=="ellipse") {
          _fromDistancesOutMode=static_cast<FromDistancesOutMode>(_fromDistancesOutMode| Ellipse);
        } else if(m=="clusters") {
          _fromDistancesOutMode=static_cast<FromDistancesOutMode>(_fromDistancesOutMode | Clusters);
        } else if(m=="mean") {
          _fromDistancesOutMode=static_cast<FromDistancesOutMode>(_fromDistancesOutMode | Mean);
        } else {
          App::log(tr("gpcoord: unrecognized output mode '%1'', see -help\n").arg(argv[i]) );
          return false;
        }
      } else {
        App::log(tr("gpcoord: bad option %1, see -help\n").arg(argv[i]) );
        return false;
      }
    } else {
      argv[j++]=argv[i];
    }
  }
  if(j < argc) {
    argv[j]=nullptr;
    argc=j;
  }
  if(!coordFile.isEmpty()) {
    QFile fCoord(coordFile);
    if(!fCoord.open(QIODevice::ReadOnly) ) {
      App::log(tr("gpcoord: cannot read file %1.\n").arg(coordFile) );
      return false;
    }
    QTextStream sCoord(&fCoord);
    LineParser p;
    bool ok=true;
    int iLine=0;
    while( ! sCoord.atEnd()) {
      QString l=sCoord.readLine();
      iLine++;
      if(!l.isEmpty() && l[0]!='#') {
        p.setString(l);
        double x, y, z;
        QString name;
        x=p.toDouble(0, ok);
        y=p.toDouble(1, ok);
        if(p.count()==4) {
          z=p.toDouble(2, ok);
          name=p.toString(3, ok);
        } else {
          z=0.0;
          name=p.toString(2, ok);
        }
        if(!ok) {
          App::log(tr("gpcoord: error reading file %1 at line %2\n").arg(coordFile).arg(iLine) );
          return false;
        }
        _refPoints.insert(name, Point(x, y, z));
      }
    }
  }
  if(_mode==FromDistances) {
    if(_origin.isEmpty()) {
      App::log(tr("gpcoord: missing option '-origin'\n") );
      return false;
    }
    if(_north.isEmpty()) {
      App::log(tr("gpcoord: missing option '-north'\n") );
      return false;
    }
    if(_eastward.isEmpty()) {
      App::log(tr("gpcoord: missing option '-eastward'\n") );
      return false;
    }
    if(_origin==_north) {
      App::log(tr("gpcoord: origin and north cannot refer to the same point\n") );
      return false;
    }
    if(_origin==_eastward) {
      App::log(tr("gpcoord: origin and eastward cannot refer to the same point\n") );
      return false;
    }
    if(_north==_eastward) {
      App::log(tr("gpcoord: north and eastward cannot refer to the same point\n") );
      return false;
    }
    if(_fromDistancesOutMode==NoOutput) {
      _fromDistancesOutMode=Mean;
    }
    _fromDistanceFactory.setProgess(_progress);
  }
  return true;
}

bool CoordReader::hasInput() const
{
  TRACE;
  switch(_mode) {
  case Distance:
  case DistanceLookup:
  case DistanceGeo:
  case Azimuth:
  case AzimuthGeo:
  case FromGeo:
  case ToGeo:
  case FromDMS:
  case ToDMS:
  case FromDM:
  case ToDM:
  case ToUTM:
  case FromUTM:
  case ToKML:
  case ArrayLimits:
  case UTMDeclination:
  case MagDeclination:
  case Translate:
  case Rotate:
  case Localize:
  case Average:
  case Lookup:
  case TriangulateDistance:
  case TriangulateAzimuth:
  case FromDistances:
  case AllDistances:
  case None:
    break;
  case FromKML:
  case GenerateCircle:
    return false;
    break;
  }
  return true;
}

bool CoordReader::parse(QTextStream& s)
{
  TRACE;
  QTextStream sOut(stdout);

  QString buf;
  bool ok=true;
  while(!s.atEnd()) {
    buf=s.readLine();
    if(!buf.isEmpty() && buf[0]!='\n') {
      if(buf[0]=='#') {
        sOut << buf << endl;
      } else {
        switch(_mode) {
        case Distance: {
            LineParser par(buf);
            if(par.count()==6) {
              Point p1(par.toDouble(0,ok), par.toDouble(1,ok), par.toDouble(2,ok));
              Point p2(par.toDouble(3,ok), par.toDouble(4,ok), par.toDouble(5,ok));
              sOut << p1.distanceTo(p2) << endl;
            } else {
              Point p1(par.toDouble(0,ok), par.toDouble(1,ok));
              Point p2(par.toDouble(2,ok), par.toDouble(3,ok));
              sOut << p1.distanceTo(p2) << endl;
            }
          }
          break;
        case DistanceLookup: {
            LineParser par(buf);
            QString name1=par.toString(0,ok);
            QString name2=par.toString(1,ok);
            QMap<QString, Point>::iterator it1=_refPoints.find(name1);
            if(it1==_refPoints.end()) {
              sOut << tr("Error unknown point name: %1").arg(name1) << endl;
              return true;
            }
            QMap<QString, Point>::iterator it2=_refPoints.find(name2);
            if(it2==_refPoints.end()) {
              sOut << tr("Error unknown point name: %1").arg(name2) << endl;
              return true;
            }
            Point p1=it1.value();
            Point p2=it2.value();
            double calcDist=p1.distanceTo(p2);
            if(par.count()==3) {
              double refDist=par.toDouble(2,ok);
              sOut << QString("%1 %2 %3 %4 %5 %6").arg(name1).arg(name2)
                      .arg(calcDist, 0, 'f', 2)
                      .arg(refDist, 0, 'f', 2)
                      .arg(refDist-calcDist, 0, 'f', 2)
                      .arg(100.0*(refDist-calcDist)/refDist, 0, 'f', 1) << endl;
            } else {
              sOut << QString("%1 %2 %3").arg(name1).arg(name2).arg(calcDist, 0, 'f', 2) << endl;
            }
          }
          break;
        case DistanceGeo: {
            LineParser par(buf);
            if(par.count()==6) {
              Point p1(par.toDouble(0,ok), par.toDouble(1,ok), par.toDouble(2,ok));
              Point p2(par.toDouble(3,ok), par.toDouble(4,ok), par.toDouble(5,ok));
              sOut << p1.geographicalDistanceTo(p2) << endl;
            } else {
              Point p1(par.toDouble(0,ok), par.toDouble(1,ok));
              Point p2(par.toDouble(2,ok), par.toDouble(3,ok));
              sOut << p1.geographicalDistanceTo(p2) << endl;
            }
          }
          break;
        case AllDistances: {
            NamedPoint p;
            ok=p.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input: %1\n").arg(buf) );
              return false;
            }
            for(QMap<QString, Point>::const_iterator it=_refPoints.begin();
                it!=_refPoints.end(); it++) {
              sOut << p.distanceTo(it.value()) << endl;
            }
          }
          break;
        case Azimuth: {
            LineParser par(buf);
            double az;
            if(par.count()==6) {
              Point p1(par.toDouble(0,ok), par.toDouble(1,ok), par.toDouble(2,ok));
              Point p2(par.toDouble(3,ok), par.toDouble(4,ok), par.toDouble(5,ok));
              az=p1.azimuthTo(p2);
            } else {
              Point p1(par.toDouble(0,ok), par.toDouble(1,ok));
              Point p2(par.toDouble(2,ok), par.toDouble(3,ok));
              az=p1.azimuthTo(p2);
            }
            sOut << Angle::mathToGeographic(az) << endl;
          }
          break;
        case AzimuthGeo: {
            LineParser par(buf);
            double az;
            if(par.count()==6) {
              Point p1(par.toDouble(0,ok), par.toDouble(1,ok), par.toDouble(2,ok));
              Point p2(par.toDouble(3,ok), par.toDouble(4,ok), par.toDouble(5,ok));
              az=p1.geographicalAzimuthTo(p2);
            } else {
              Point p1(par.toDouble(0,ok), par.toDouble(1,ok));
              Point p2(par.toDouble(2,ok), par.toDouble(3,ok));
              az=p1.geographicalAzimuthTo(p2);
            }
            sOut << Angle::radiansToDegrees(az) << endl;
          }
          break;
        case FromGeo: {
            NamedPoint longLat;
            ok=longLat.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input: %1\n").arg(buf) );
              return false;
            }
            longLat.geographicalToRectangular(_geoReference);
            sOut << " " << longLat.toString(20) << endl;
          }
          break;
        case ToGeo: {
            NamedPoint longLat;
            ok=longLat.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            longLat.rectangularToGeographical(_geoReference);
            sOut << longLat.toString(20) << endl;
          }
          break;
        case FromDMS: {
            NamedPoint longLat;
            ok=longLat.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input: %1\n").arg(buf) );
              return false;
            }
            longLat.DMSToDegrees();
            sOut << " " << longLat.toString(20) << endl;
          }
          break;
        case ToDMS: {
            NamedPoint longLat;
            ok=longLat.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            longLat.degreesToDMS();
            sOut << longLat.toString(20) << endl;
          }
          break;
        case FromDM: {
            NamedPoint longLat;
            ok=longLat.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input: %1\n").arg(buf) );
              return false;
            }
            longLat.DMToDegrees();
            sOut << " " << longLat.toString(20) << endl;
          }
          break;
        case ToDM: {
            NamedPoint longLat;
            ok=longLat.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            longLat.degreesToDM();
            sOut << longLat.toString(20) << endl;
          }
          break;
        case ToUTM: {
            NamedPoint longLat;
            ok=longLat.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            UtmZone zone=_utmZone;
            if(!zone.isValid()) {
              zone=UtmZone(longLat);
            }
            longLat.geographicalToUtm(zone);
            sOut << zone.toString() << " " << longLat.toString(20) << endl;
          }
          break;
        case FromUTM: {
            LineParser par(buf);
            bool ok=true;
            UtmZone zone;
            zone.fromString(par.toString(0, ok));
            if(!ok) {
              App::log(tr("Error parsing input, cannot read first column.\n") );
              return false;
            }
            QString pointString;
            if(zone.isValid()) {
              pointString=par.toString(1, par.count(), ok);
              if(!ok) {
                App::log(tr("Error parsing input, missong point coordinates.\n") );
                return false;
              }
            } else {
              pointString=buf;
            }
            NamedPoint utmCoord;
            ok=utmCoord.fromString(pointString);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            if(zone.isValid()) {
              utmCoord.utmToGeographical(zone);
            } else if(_utmZone.isValid()){
              utmCoord.utmToGeographical(_utmZone);
            } else {
              App::log(tr("Missing specification of UTM zone (stdin or argument, see -help)\n") );
              return false;
            }
            sOut << utmCoord.toString(20) << endl;
          }
          break;
        case ToKML: {
            NamedPoint longLat;
            ok=longLat.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            GoogleEarthKML::Document& doc=_kml->document();
            GoogleEarthKML::Folder * f=doc.mainFolder();
            GoogleEarthKML::Placemark * p=new GoogleEarthKML::Placemark;
            p->setName(longLat.name());
            p->setCoordinates(longLat);
            f->addPlacemark(p);
          }
          break;
        case ArrayLimits: {
            LineParser par(buf);
            Point2D p(par.toDouble(0,ok), par.toDouble(1,ok));
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            _array.append(p);
          }
          break;
        case FromKML:
        case GenerateCircle:
          ASSERT(false);
          break;
        case Translate: {
            NamedPoint p;
            ok=p.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            p.translate(_translateVector);
            sOut << p.toString(20) << endl;
          }
          break;
        case Rotate: {
            NamedPoint p;
            ok=p.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            p.rotate(_angle);
            sOut << p.toString(20) << endl;
          }
          break;
        case Localize: {
            NamedPoint v, p;
            ok=v.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            Angle a;
            a.setDegreeAzimuth(v.y());
            p.setX(_cartReference.x()+v.x()*a.cos());
            p.setY(_cartReference.y()+v.x()*a.sin());
            p.setName(v.name());
            sOut << p.toString(20) << endl;
          }
          break;
        case Average: {
            Point p;
            ok=p.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            _average+=p;
            _averageCount++;
          }
          break;
        case Lookup: {
            QMap<QString, Point>::iterator it=_refPoints.find(buf);
            if(it==_refPoints.end()) {
              sOut << tr("Error unknown point name: %1").arg(buf) << endl;
              return false;
            }
            sOut << it.value().toString(20) << " " << it.key() << endl;
          }
          break;
        case TriangulateDistance: {
            LineParser par(buf);
            if(par.count()==2) {
              QString name=par.toString(0,ok);
              if(!ok) {
                App::log(tr("Error parsing input, missing reference point name.\n") );
                return false;
              }
              QMap<QString, Point>::iterator it=_refPoints.find(name);
              if(it==_refPoints.end()) {
                App::log(tr("Error unknown point name: %1\n").arg(name) );
                return false;
              }
              _circles.append(Circle(it.value().x(), it.value().y(), par.toDouble(1,ok)));
              if(!ok) {
                App::log(tr("Error parsing input, missing distance for point %1.\n").arg(name) );
                return false;
              }
            } else {
              App::log(tr("Error parsing input, NAME DISTANCE expected\n") );
              return false;
            }
          }
          break;
        case TriangulateAzimuth: {
            LineParser par(buf);
            if(par.count()==2) {
              QString name=par.toString(0,ok);
              if(!ok) {
                App::log(tr("Error parsing input, missing reference point name.\n") );
                return false;
              }
              QMap<QString, Point>::iterator it=_refPoints.find(name);
              if(it==_refPoints.end()) {
                App::log(tr("Error unknown point name: %1\n").arg(name) );
                return false;
              }
              double az=par.toDouble(1,ok)*M_PI/180.0;
              _lines.append(Line2D(it.value().x(), it.value().y(), az));
              if(!ok) {
                App::log(tr("Error parsing input, missing azimuth for point %1.\n").arg(name) );
                return false;
              }
            } else {
              App::log(tr("Error parsing input, NAME AZIMUTH expected\n") );
              return false;
            }
          }
          break;
        case FromDistances: {
            LineParser par(buf);
            if(par.count()==3) {
              QString name1=par.toString(0,ok);
              if(!ok) {
                App::log(tr("Error parsing input, missing first point name.\n") );
                return false;
              }
              QString name2=par.toString(1,ok);
              if(!ok) {
                App::log(tr("Error parsing input, missing second point name.\n") );
                return false;
              }
              double d=par.toDouble(2,ok);
              if(!ok) {
                App::log(tr("Error parsing input, missing distance for couple %1-%2.\n").arg(name1).arg(name2) );
                return false;
              }
              if(name1==name2) {
                App::log(tr("Error parsing input, name of points must differ within the same couple ('%1').\n").arg(name1) );
                return false;
              }
              _fromDistanceFactory.addDistance(name1, name2, d);
            } else {
              App::log(tr("Error parsing input, NAME1 NAME2 DISTANCE expected\n") );
              return false;
            }
          }
          break;
        case UTMDeclination: {
            NamedPoint longLat;
            ok=longLat.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            Point2D north;
            UtmZone zone;
            north=longLat;
            north.geographicalToUtm(zone);
            north+=Point2D(0.0, 100.0); // 100 m in UTM north direction
            north.utmToGeographical(zone);
            double angle=longLat.geographicalAzimuthTo(north);
            if(angle>M_PI) {
              angle-=2*M_PI;
            }
            sOut << Angle::radiansToDegrees(angle) << endl;
          }
          break;
        case MagDeclination: {
            NamedPoint longLat;
            ok=longLat.fromString(buf);
            if(!ok) {
              App::log(tr("Error parsing input\n") );
              return false;
            }
            QString cmd("wget -O - --quiet https://www.ngdc.noaa.gov/geomag-web/calculators/calculateDeclination?"
                        "lon1=%1&lat1=%2&model=IGRF&startYear=%3&startMonth=%4&startDay=%5&"
                        "resultFormat=xml");
            cmd=cmd.arg(longLat.x())
                   .arg(longLat.y())
                   .arg(_date.year())
                   .arg(_date.month())
                   .arg(_date.day());
            App::log(1, cmd+"\n");
            QProcess p;
            p.start(cmd);
            p.waitForFinished();
            QString xml=p.readAllStandardOutput();
            App::log(2, xml);
            XMLGenericItem item("maggridresult");
            XMLHeader header(&item);
            XMLClass::Error err=header.xml_restoreString(xml);
            if(err!=XMLClass::NoError) {
              App::log(XMLClass::message(err)+"\n");
              return false;
            }
            QList<XMLGenericItem *> result=item.children("result");
            if(result.count()!=1) {
              App::log(tr("Bad XML response from server, increase verbosity to get details\n") );
              return false;
            }
            QList<XMLGenericItem *> subResults=result.first()->children("declination");
            if(subResults.count()!=1) {
              App::log(tr("Bad XML response from server, increase verbosity to get details\n") );
              return false;
            }
            sOut << QString::number(subResults.first()->value().toDouble()) << endl;
          }
          break;
        case None:
          App::log(tr("gpcoord: no operation defined, see -h\n") );
          return false;
        }
      }
    }
  }
  return true;
}
