/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2016-08-26
**  Copyright: 2016-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "UtmZone.h"
#include "CoordinateFile.h"
#include "GoogleEarthKML.h"

namespace QGpCoreMath {

  /*!
    \class CoordinateFile CoordinateFile.h
    \brief Parser for all coordinate text files including KML.


  */

  QStringList CoordinateFile::_types;
  const QString CoordinateFile::_rectFileFilter=
      QT_TRANSLATE_NOOP("CoordinateFile", "Any coordinate file (*);;Local coordinate file (*.coord);;"
                                          "UTM coordinate file (*.utm)");
  const QString CoordinateFile::_geoFileFilter=
      QT_TRANSLATE_NOOP("CoordinateFile", "Geographical coordinate file (*.geo);;"
                                          "Google Earth KML (*.kml)");

  /*!
    Description of constructor still missing
  */
  CoordinateFile::CoordinateFile()
    : ColumnTextFile()
  {
    TRACE;

    if(_types.isEmpty()) {
      // Order here is quite important, see all functions below in case of modification
      // 2 to 5: cartesian, 7 to 14: geographical
      _types << tr("Station name")    // 1
             << tr("X (m)")           // 2
             << tr("X (km)")          // 3
             << tr("Y (m)")           // 4
             << tr("Y (km)")          // 5
             << tr("Z (m)")           // 6
             << tr("Longitude (D)")   // 7
             << tr("Longitude (M)")   // 8
             << tr("Longitude (S)")   // 9
             << tr("Longitude (DEG)") // 10
             << tr("Latitude (D)")    // 11
             << tr("Latitude (M)")    // 12
             << tr("Latitude (S)")    // 13
             << tr("Latitude (DEG)")  // 14
             << tr("UTM zone");       // 15
    }

    setStandardTypes(_types);
    QVector<int> defaultTypes;
    defaultTypes << 1 << 2 << 4 << 6;
    setTypes(defaultTypes);
    _nameRequired=true;
  }

  /*!
    Description of destructor still missing
  */
  CoordinateFile::~CoordinateFile()
  {
    TRACE;
  }

  /*!
    If \a fileName is empty a Message::getOpenFileName() is used to get a file name from user.
  */
  bool CoordinateFile::setFile(QString fileName)
  {
    TRACE;
    if(fileName.isEmpty()) {
      fileName=Message::getOpenFileName(tr("Load coordinates"), allFileFilter());
      if(fileName.isEmpty()) {
        return false;
      }
    }
    if(fileName.endsWith(".kml")) {
      GoogleEarthKML kml;
      XMLHeader hdr(&kml);
      if(hdr.xml_restoreFile(fileName, 0, XMLClass::XmlFile)!=XMLClass::NoError) {
        Message::warning(MSG_ID, tr("Loading Google Earth KML ..."),
                         tr("Error while reading file %1").arg(fileName), Message::cancel());
        return false;
      }
      QList<NamedPoint> list=kml.document().points();
      QString str;
      for(QList<NamedPoint>::iterator it=list.begin(); it!=list.end(); it++) {
        str+=it->toString(20)+"\n";
      }
      QVector<int> defaultTypes;
      defaultTypes << 10 << 14 << 6 << 1;
      setTypes(defaultTypes);
      setMaximumColumnCount(4);
      setBuffer(str);
      startUpdates();
    } else {
      if(!ColumnTextFile::setFile(fileName)) {
        return false;
      }
    }
    return true;
  }

  /*!
    Extract a list of points from parser.
  */
  QList<NamedPoint> CoordinateFile::points() const
  {
    TRACE;
    QList<NamedPoint> list;
    int nColumns=columnCount();
    int nRows=rowCount();
    for(int iRow=0; iRow<nRows; iRow++) {
      Point p;
      QString name;
      for(int iCol=0; iCol<nColumns; iCol++) {
        switch(type(iCol)) {
        case 1:
          name=text(iRow, iCol);
          break;
        case 2:
          p.setX(text(iRow, iCol).toDouble());
          break;
        case 3:
          p.setX(text(iRow, iCol).toDouble() * 1000.0);
          break;
        case 4:
          p.setY(text(iRow, iCol).toDouble());
          break;
        case 5:
          p.setY(text(iRow, iCol).toDouble() * 1000.0);
          break;
        case 6:
          p.setZ(text(iRow, iCol).toDouble());
          break;
        case 7:
          p.setX(p.x()+text(iRow, iCol).toDouble());
          break;
        case 8:
          p.setX(p.x()+text(iRow, iCol).toDouble()/60.0);
          break;
        case 9:
          p.setX(p.x()+text(iRow, iCol).toDouble()/3600.0);
          break;
        case 10:
          p.setX(text(iRow, iCol).toDouble());
          break;
        case 11:
          p.setY(p.y()+text(iRow, iCol).toDouble());
          break;
        case 12:
          p.setY(p.y()+text(iRow, iCol).toDouble()/60.0);
          break;
        case 13:
          p.setY(p.y()+text(iRow, iCol).toDouble()/3600.0);
          break;
        case 14:
          p.setY(text(iRow, iCol).toDouble());
          break;
        case 15:
          // UTM zone not reported here, see utmZone()
          break;
        default:
          break;
        }
      }
      if(_nameRequired && name.isEmpty()) {
        name=QString("p_%1").arg(iRow+1, 3, 10, QChar('0'));
        Message::warning(MSG_ID, tr("Read coordinates"), tr("Empty station name at line %1, using '%2' instead.")
                            .arg(lineNumber(iRow)).arg(name), true);
      }
      list.append(NamedPoint(name,p));
    }
    return list;
  }

  /*!
    Return the UTM zone which contains the highest number of points.
    Column 'UTM zone' must be available in the parser, if not, a non valid zone is returned.
  */
  UtmZone CoordinateFile::utmZone() const
  {
    TRACE;
    QHash<UtmZone, int> stat;
    UtmZone zone;
    int nColumns=columnCount();
    int nRows=rowCount();
    for(int iRow=0; iRow<nRows; iRow++) {
      for(int iCol=0; iCol<nColumns; iCol++) {
        switch(type(iCol)) {
        case 15:
          zone.fromString(text(iRow, iCol));
          if(zone.isValid()) {
            stat[zone]++;
          } else {
            Message::warning(MSG_ID, tr("Read utm zone"), tr("Bad UTM zone at line %1.")
                                     .arg(lineNumber(iRow)), true);
          }
          break;
        default:
          break;
        }
      }
    }
    return utmZone(stat);
  }

  /*!
    Return the UTM zone which contains the highest number of points.
    \a points must be in longitude and latitude.
  */
  UtmZone CoordinateFile::utmZone(const QList<NamedPoint> &points)
  {
    TRACE;
    QList<NamedPoint>::const_iterator it;
    QHash<UtmZone, int> stat;
    for(it=points.begin(); it!=points.end(); ++it) {
      stat[UtmZone(*it)]++;
    }
    return utmZone(stat);
  }

  /*!
    Return the zone with the highest number of hits

    \internal
  */
  UtmZone CoordinateFile::utmZone(const QHash<UtmZone, int> &zoneStat)
  {
    TRACE;
    int max=0;
    UtmZone index;
    for(QHash<UtmZone, int>::const_iterator it=zoneStat.begin(); it!=zoneStat.end(); ++it) {
      if(it.value()>max) {
        index=it.key();
        max=it.value();
      }
    }
    return index;
  }

  /*!
    Check parser columns for geographical coordinates.
  */
  CoordinateFile::CoordinateType CoordinateFile::coordinateType(bool warn) const
  {
    TRACE;
    int nColumns=columnCount();
    // Check whether we are in cartesian or geographical coordinates
    CoordinateType coordType=Undefined;
    for(int i=0; i<nColumns; i++) {
      int t=type(i);
      if(t>=2 && t<=5) {
        switch(coordType) {
        case Undefined:
          coordType=Local;
          break;
        case Local:
        case UniversalTransverseMercator:
          break;
        case Geographical:
          if(warn) {
            Message::warning(MSG_ID, tr("Read coordinates"),
                             tr("Mix of geographical and cartesian coordinates for column %1.").arg(i));
            return Undefined;
          }
          break;
        }
      } else if(t>=7 && t<=14) {
        switch(coordType) {
        case Undefined:
          coordType=Geographical;
          break;
        case Geographical:
          break;
        case UniversalTransverseMercator:
        case Local:
          if(warn) {
            Message::warning(MSG_ID, tr("Read coordinates"),
                             tr("Mix of geographical, UTM or local coordinates for column %1.").arg(i));
            return Undefined;
          } else {
            coordType=Geographical; // If warn is false, this function is used to detect the presence
                                    // of geographical columns, Geographical has then priority over Local or Utm.
          }
          break;
        }
      } else if(t==15) {
        switch(coordType) {
        case Local:
        case Undefined:
          coordType=UniversalTransverseMercator;
          break;
        case UniversalTransverseMercator:
          break;
        case Geographical:
          if(warn) {
            Message::warning(MSG_ID, tr("Read coordinates"),
                             tr("Mix of geographical and Utm coordinates for column %1.").arg(i));
            return Undefined;
          }
          break;
        }
      }
    }
    return coordType;
  }

  inline void CoordinateFile::toGeographicalCoordinates(NamedPoint& p, const UtmZone& utmZone, const Point *reference)
  {
    TRACE;
    if(utmZone.isValid()) {
      p.utmToGeographical(utmZone);
    } else {
      ASSERT(reference);
      p.rectangularToGeographical(*reference);
    }
  }

  /*!
    If \a fileName ends with .geo, .utm or .kml and if \a reference is not null, \a points are converted
    to geographical coordinates. If \a utmZone is valid, input \a points are considered as UTM coordinates within
    the zone \a utmZone.
  */
  bool CoordinateFile::write(const QList<NamedPoint>& points, UtmZone utmZone, Point *reference, QString fileName)
  {
    TRACE;
    if(fileName.isEmpty()) {
      QString fileFilter;
      if(reference) {
        fileFilter=allFileFilter();
      } else {
        fileFilter=rectFileFilter();
      }
      fileName=Message::getSaveFileName(tr("Save coordinates"), fileFilter);
      if(fileName.isEmpty()) {
        return false;
      }
    }
    if(fileName.endsWith(".kml")) {
      GoogleEarthKML kml;
      GoogleEarthKML::Document& doc=kml.document();
      QFileInfo fi(fileName);
      doc.setName(fi.baseName());
      GoogleEarthKML::Folder * f=doc.mainFolder();
      f->setName(fi.baseName());
      NamedPoint point;
      for(QList<NamedPoint>::const_iterator it=points.begin(); it!=points.end(); it++) {
        GoogleEarthKML::Placemark * p=new GoogleEarthKML::Placemark;
        point=*it;
        p->setName(point.name());
        toGeographicalCoordinates(point, utmZone, reference);
        p->setCoordinates(point);
        f->addPlacemark(p);
      }
      return kml.save(fileName);
    } else {
      QFile f(fileName);
      if(!f.open(QIODevice::WriteOnly)) {
        Message::warning(MSG_ID, tr("Saving coordinates ..."),
                         tr("Error while writing to file %1").arg(fileName), Message::cancel());
        return false;
      }
      QTextStream s(&f);
      if(fileName.endsWith(".geo")) {
        NamedPoint p;
        for(QList<NamedPoint>::const_iterator it=points.begin(); it!=points.end(); it++) {
          p=*it;
          toGeographicalCoordinates(p, utmZone, reference);
          s << p.toString(20) << "\n";
        }
        return true;
      } else if(fileName.endsWith(".utm")) {
        if(utmZone.isValid()) {
          for(QList<NamedPoint>::const_iterator it=points.begin(); it!=points.end(); it++) {
            s << utmZone.toString() << " " << it->toString(20) << "\n";
          }
        } else {
          NamedPoint p;
          for(QList<NamedPoint>::const_iterator it=points.begin(); it!=points.end(); it++) {
            p=*it;
            toGeographicalCoordinates(p, utmZone, reference);
            p.geographicalToUtm(utmZone);
            s << utmZone.toString() << " " << p.toString(20) << "\n";
          }
          return true;
        }
      } else {
        for(QList<NamedPoint>::const_iterator it=points.begin(); it!=points.end(); it++) {
          s << it->toString(20) << "\n";
        }
      }
    }
    return true;
  }

} // namespace QGpCoreMath

