/*=========================================================================

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkGPXReader.h"

#include "vtkObjectFactory.h"
#include "vtkXMLParser.h"
#include "vtkPolyData.h"
#include "vtkPoints.h"
#include "vtkCellArray.h"

#include <vtkstd/string>
#include <vtkstd/vector>

#include <vtkMath.h>

#include <time.h>

//----------------------------------------------------------------------------
//****************************************************************************
class vtkGPXPoint
{
public:
  vtkGPXPoint()
    {
    this->Clean();
    }
  virtual ~vtkGPXPoint() {}
  virtual void Clean()
    {
    this->Latitude = 0;
    this->Longitude = 0;
    this->Elevation = 0;
    this->Time = 0;
    }
  double Latitude;
  double Longitude;
  double Elevation;
  int Time;
};

class vtkGPXWayPoint : public vtkGPXPoint
{
public:
  vtkGPXWayPoint()
    {
    this->Clean();
    }
  virtual ~vtkGPXWayPoint() {}
  virtual void Clean()
    {
    this->Name = "";
    this->Description = "";
    this->Symbol = "";
    }
  vtkstd::string Name;
  vtkstd::string Description;
  vtkstd::string Symbol;
};

//****************************************************************************
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
//****************************************************************************
class vtkGPXReaderParser : public vtkXMLParser
{
  friend class vtkGPXReader;
public:
  static vtkGPXReaderParser* New();
  vtkTypeMacro(vtkGPXReaderParser, vtkXMLParser);
  void ParseFile(const char* fname, vtkPolyData* wpd, vtkPolyData* rpd, vtkPolyData* tpd);

protected:
  vtkGPXReaderParser()
    {
    this->LastTime = 0;
    this->CurrentPoint[0] = this->CurrentPoint[1] = this->CurrentPoint[2] = 0;

    this->InCurrentPoint = 0;
    this->InRTE = 0;
    this->InTrack = 0;
    this->InTRKSEG = 0;

    this->WPTPreviousPoint = -1;
    this->RTEPreviousPoint = -1;
    this->TRKPreviousPoint = -1;

    this->WPTOutput = 0;
    this->RTEOutput = 0;
    this->TRKOutput = 0;
    }

  ~vtkGPXReaderParser()
    {
    this->SetLastTime(0);
    }

  const char* FindAttribute( const char** atts, const char* attribute );

  void StartPoint(const char* name, const char** atts);
  void EndPoint(const char* name);

  void EndElement(const char* name);
  void StartElement(const char* name, const char** atts);
  void CharacterDataHandler(const char* data, int length);
  vtkstd::string CharacterData;

  vtkSetStringMacro(LastTime);
  char* LastTime;

  double CurrentPoint[3];
  int InCurrentPoint;
  int InRTE;
  int InTrack;
  int InTRKSEG;

  vtkIdType WPTPreviousPoint;
  vtkIdType RTEPreviousPoint;
  vtkIdType TRKPreviousPoint;

  vtkPolyData* WPTOutput;
  vtkPolyData* RTEOutput;
  vtkPolyData* TRKOutput;

  vtkstd::vector<vtkGPXPoint> TrackPoints;
  vtkstd::vector<vtkGPXWayPoint> WayPoints;
  vtkstd::vector<vtkGPXWayPoint> RoutePoints;

  vtkGPXPoint    CurrentTrackPoint;
  vtkGPXWayPoint CurrentWayPoint;
  vtkGPXWayPoint CurrentRoutePoint;

  vtkGPXPoint* GetCurrentPoint();

private:
  vtkGPXReaderParser(const vtkGPXReaderParser&); // Not implemented
  void operator=(const vtkGPXReaderParser&); // Not implemented
};
//****************************************************************************
vtkStandardNewMacro(vtkGPXReaderParser);
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
const char* vtkGPXReaderParser::FindAttribute( const char** atts,
					      const char* attribute )
{
  if ( !atts || !attribute )
    {
    return 0;
    }
  const char **atr = atts;
  while ( *atr && **atr && **(atr+1) )
    {
    if ( strcmp(*atr, attribute) == 0 )
      {
      return *(atr+1);
      }
    atr+=2;
    }
  return 0;
}

//----------------------------------------------------------------------------
vtkGPXPoint* vtkGPXReaderParser::GetCurrentPoint()
{
  if ( !this->InCurrentPoint )
    {
    return 0;
    }
  if ( this->InRTE )
    {
    return &this->CurrentRoutePoint;
    }
  else if ( this->InTrack )
    {
    return &this->CurrentTrackPoint;
    }
  return &this->CurrentWayPoint;
}

//----------------------------------------------------------------------------
void vtkGPXReaderParser::StartElement(const char* name, const char** atts)
{
  if ( this->CharacterData.size() )
    {
    this->CharacterData = "";
    }
  if ( this->ParseError )
    {
    return;
    }
  if ( strcmp(name, "gpx") == 0 )
    {
    }
  else if ( strcmp(name, "metadata") == 0 )
    {
    // Ignore metadata for now
    }
  else if ( strcmp(name, "time") == 0 )
    {
    // handle in end element
    }
  else if ( strcmp(name, "bounds") == 0 )
    {
    // Ignore bounds for now
    }
  else if ( strcmp(name, "wpt") == 0 || strcmp(name, "rtept") == 0 || strcmp(name, "trkpt") == 0)
    {
    this->StartPoint(name, atts);
    }
  else if ( strcmp(name, "ele") == 0 )
    {
    if ( !this->InCurrentPoint )
      {
      vtkErrorMacro("Found tag \"" << name << "\" outside \"wpt\"");
      this->ParseError = 1;
      return;
      }
    // Handle in end element
    }
  else if ( strcmp(name, "name") == 0 )
    {
    if ( !this->InCurrentPoint && !this->InTrack )
      {
      vtkErrorMacro("Found tag \"" << name << "\" outside \"wpt\" or \"trk\"");
      this->ParseError = 1;
      return;
      }
    // Ignore name for now
    }
  else if ( strcmp(name, "desc") == 0 )
    {
    if ( !this->InCurrentPoint && !this->InTrack  )
      {
      vtkErrorMacro("Found tag \"" << name << "\" outside \"wpt\" or \"trk\"");
      this->ParseError = 1;
      return;
      }
    // Ignore desc for now
    }
  else if ( strcmp(name, "type") == 0 )
    {
    if ( !this->InTrack  )
      {
      vtkErrorMacro("Found tag \"" << name << "\" outside \"trk\"");
      this->ParseError = 1;
      return;
      }
    // Ignore sym for now
    }
  else if ( strcmp(name, "sym") == 0 )
    {
    if ( !this->InCurrentPoint && !this->InTrack  )
      {
      vtkErrorMacro("Found tag \"" << name << "\" outside \"wpt\" or \"trk\"");
      this->ParseError = 1;
      return;
      }
    // Ignore sym for now
    }
  else if ( strcmp(name, "cmt") == 0 )
    {
    if ( !this->InCurrentPoint )
      {
      vtkErrorMacro("Found tag \"" << name << "\" outside \"wpt\"");
      this->ParseError = 1;
      return;
      }
    // Ignore cmt for now
    }
  else if ( strcmp(name, "rte") == 0 )
    {
    this->InRTE = 1;
    }
  else if ( strcmp(name, "number") == 0 )
    {
    if ( !this->InRTE && !this->InTrack )
      {
      vtkErrorMacro("Found tag \"" << name << "\" outside \"rte\"");
      this->ParseError = 1;
      return;
      }
    // Ignore for now
    }
  else if ( strcmp(name, "trk") == 0 )
    {
    this->InTrack = 1;
    }
  else if ( strcmp(name, "trkseg") == 0 )
    {
    this->InTRKSEG = 1;
    }
  else if ( strcmp(name, "extensions") == 0 )
    {
    // Ignore for now
    }
  else
    {
    this->ReportUnknownElement(name);
    this->ParseError = 1;
    }
}

//----------------------------------------------------------------------------
void vtkGPXReaderParser::EndElement(const char* name)
{
  if ( this->ParseError )
    {
    return;
    }
  if ( strcmp(name, "gpx") == 0 )
    {
    }
  else if ( strcmp(name, "metadata") == 0 )
    {
    // Ignore for now
    }
  else if ( strcmp(name, "bounds") == 0 )
    {
    // Ignore bounds for now
    }
  else if ( strcmp(name, "time") == 0 )
    {
    this->SetLastTime(this->CharacterData.c_str());
    vtkGPXPoint* pt = this->GetCurrentPoint();
    if ( pt )
      {
      int year = 0;
      int month = 0;
      int day = 0;
      int hour = 0;
      int minute = 0;
      int second = 0;
      sscanf(this->CharacterData.c_str(), "%d-%d-%dT%d:%d:%d.",
        &year, &month, &day, &hour, &minute, &second);
      struct tm curr_time;
      curr_time.tm_sec = second;
      curr_time.tm_min = minute;
      curr_time.tm_hour = hour;
      curr_time.tm_mday = day;
      curr_time.tm_mon = month -1 ;
      curr_time.tm_year = year - 1900;
      time_t curr_seconds = mktime(&curr_time);
      pt->Time = curr_seconds;
      }
    }
  else if ( strcmp(name, "wpt") == 0 || strcmp(name, "rtept") == 0 || strcmp(name, "trkpt") == 0)
    {
    this->EndPoint(name);
    }
  else if ( strcmp(name, "ele") == 0 )
    {
    double elevation = atof(this->CharacterData.c_str()) * .0001;
    this->CurrentPoint[2] = elevation;
    vtkGPXPoint* pt = this->GetCurrentPoint();
    pt->Elevation = elevation;
    }
  else if ( strcmp(name, "name") == 0 )
    {
    if ( this->InCurrentPoint )
      {
      if ( this->InRTE )
        {
        this->CurrentRoutePoint.Name = this->CharacterData;
        }
      else
        {
        this->CurrentWayPoint.Name = this->CharacterData;
        }
      }
    // Ignore for now
    }
  else if ( strcmp(name, "desc") == 0 )
    {
    if ( this->InCurrentPoint )
      {
      if ( this->InRTE )
        {
        this->CurrentRoutePoint.Description = this->CharacterData;
        }
      else
        {
        this->CurrentWayPoint.Description = this->CharacterData;
        }
      }
    // Ignore for now
    }
  else if ( strcmp(name, "type") == 0 )
    {
    // Ignore for now
    }
  else if ( strcmp(name, "sym") == 0 )
    {
    if ( this->InCurrentPoint )
      {
      if ( this->InRTE )
        {
        this->CurrentRoutePoint.Symbol = this->CharacterData;
        }
      else
        {
        this->CurrentWayPoint.Symbol = this->CharacterData;
        }
      }
    // Ignore for now
    }
  else if ( strcmp(name, "cmt") == 0 )
    {
    // Ignore for now
    }
  else if ( strcmp(name, "rte") == 0 )
    {
    this->InRTE = 0;
    }
  else if ( strcmp(name, "trk") == 0 )
    {
    this->InTrack = 0;
    }
  else if ( strcmp(name, "trkseg") == 0 )
    {
    this->InTRKSEG = 0;
    }
  else if ( strcmp(name, "number") == 0 )
    {
    // Ignore for now
    }
  else if ( strcmp(name, "extensions") == 0 )
    {
    // Ignore for now
    }
  else
    {
    this->ReportUnknownElement(name);
    this->ParseError = 1;
    }
}

//----------------------------------------------------------------------------
void vtkGPXReaderParser::StartPoint(const char*, const char** atts)
{
  this->InCurrentPoint = 1;
  this->CurrentPoint[0] = this->CurrentPoint[1] = this->CurrentPoint[2] = 0;

  // X = -545730
  // Y = 167430
  double lat = 0;
  double lon = 0;
  const char* slat = this->FindAttribute(atts, "lat");
  if ( slat )
    {
    lat = atof(slat);
    }
  const char* slon = this->FindAttribute(atts, "lon");
  if ( slon )
    {
    lon = atof(slon);
    }

  double earthEquatorRadius = 6378.5 * 1000;
  double earthPolarRadius = 6356.9 * 1000;
  double earthRadius = (earthPolarRadius + earthEquatorRadius) / 2;

  double mx = 2.0 * vtkMath::Pi() * earthRadius * cos(lat * vtkMath::Pi() / 180.0);
  double my = 2.0 * vtkMath::Pi() * earthRadius;

  this->CurrentPoint[0] = ( mx / 360 ) * lon;
  this->CurrentPoint[1] = ( my / 360 ) * lat;

  vtkGPXPoint* pt = this->GetCurrentPoint();
  pt->Longitude = lon;
  pt->Latitude = lat;
}

//----------------------------------------------------------------------------
void vtkGPXReaderParser::EndPoint(const char*)
{
  int pp = -1;
  vtkPolyData* out = 0;
  if ( this->InRTE )
    {
    out = this->RTEOutput;
    pp = this->RTEPreviousPoint;
    }
  else if ( this->InTrack )
    {
    out = this->TRKOutput;
    pp = this->TRKPreviousPoint;
    }
  else
    {
    out = this->WPTOutput;
    pp = this->WPTPreviousPoint;
    }
  //cout << out << " Found point: " << this->CurrentPoint[0] << ", " << this->CurrentPoint[1] << ", " << this->CurrentPoint[2] << endl;

  vtkIdType cp = out->GetPoints()->InsertNextPoint(this->CurrentPoint);
  if ( pp >= 0 )
    {
    vtkIdType pts[2];
    pts[0] = pp;
    pts[1] = cp;
    out->InsertNextCell(VTK_LINE, 2, pts);
    }

  if ( this->InRTE )
    {
    this->RTEPreviousPoint = cp;
    this->RoutePoints.push_back(this->CurrentRoutePoint);
    this->CurrentRoutePoint.Clean();
    if ( this->RoutePoints.size()-1 != (vtkstd::vector<vtkGPXWayPoint>::size_type) cp )
      {
      cerr << "Problem inserting route point" << endl;
      }
    }
  else if ( this->InTrack )
    {
    this->TRKPreviousPoint = cp;
    this->TrackPoints.push_back(this->CurrentTrackPoint);
    this->CurrentTrackPoint.Clean();
    if ( this->TrackPoints.size()-1 != (vtkstd::vector<vtkGPXWayPoint>::size_type) cp )
      {
      cerr << "Problem inserting track point" << endl;
      }
    }
  else
    {
    this->WPTPreviousPoint = cp;
    this->WayPoints.push_back(this->CurrentWayPoint);
    this->CurrentWayPoint.Clean();
    if ( this->WayPoints.size()-1 != (vtkstd::vector<vtkGPXWayPoint>::size_type) cp )
      {
      cerr << "Problem inserting way point" << endl;
      }
    }

  this->InCurrentPoint = 0;
}

//----------------------------------------------------------------------------
void vtkGPXReaderParser::CharacterDataHandler(const char* data, int length)
{
  this->CharacterData += vtkstd::string(data, length);
}

//----------------------------------------------------------------------------
void vtkGPXReaderParser::ParseFile(const char* fname, vtkPolyData* wpt,
  vtkPolyData* rte, vtkPolyData* trk)
{
  this->SetFileName(fname);
  this->WPTOutput = wpt;
  this->RTEOutput = rte;
  this->TRKOutput = trk;

  this->Parse();
}

//----------------------------------------------------------------------------
vtkStandardNewMacro(vtkGPXReader);
vtkCxxRevisionMacro(vtkGPXReader, "$Revision: 1.7 $");

//----------------------------------------------------------------------------
vtkGPXReader::vtkGPXReader()
{
  this->FileName = NULL;
  this->Parser = 0;
}

//----------------------------------------------------------------------------
vtkGPXReader::~vtkGPXReader()
{
  this->SetFileName(0);
  if ( this->Parser )
    {
    this->Parser->Delete();
    this->Parser = 0;
    }
}

//----------------------------------------------------------------------------
void vtkGPXReader::Execute()
{
  int cc;
  for ( cc = 0; cc < 3; cc ++ )
    {
    vtkPoints* pts = vtkPoints::New();
    vtkCellArray* lines = vtkCellArray::New();
    this->GetOutput(cc)->SetPoints(pts);
    this->GetOutput(cc)->SetLines(lines);
    lines->Delete();
    pts->Delete();
    }

  if ( this->Parser )
    {
    this->Parser->Delete();
    this->Parser = 0;
    }

  this->Parser = vtkGPXReaderParser::New();
  this->Parser->ParseFile(this->FileName, this->GetOutput(0), this->GetOutput(1), this->GetOutput(2));
}

//----------------------------------------------------------------------------
void vtkGPXReader::ExecuteInformation()
{
  int cc;
  for ( cc = 0; cc < 3; cc ++ )
    {
    if ( !this->GetOutput(cc) || !this->GetOutput(cc)->IsA("vtkPolyData") )
      {
      vtkPolyData* no = vtkPolyData::New();
      this->SetNthOutput(cc, no);
      no->Delete();
      }
    }
  this->Superclass::ExecuteInformation();
}

//----------------------------------------------------------------------------
void vtkGPXReader::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);

  os << indent << "File Name: "
    << (this->FileName ? this->FileName : "(none)") << endl;
}

//----------------------------------------------------------------------------

#define vtkGPXReaderGetArgumentFromId(rettype, type, arg) \
  rettype vtkGPXReader::Get##type##Point##arg(vtkIdType id) \
  { \
    if ( id < 0 ) \
      { \
      return 0; \
      } \
    if ( !this->Parser ) \
      { \
      return 0; \
      } \
    if( (vtkIdType)this->Parser->type##Points.size() <= id ) \
      { \
      return 0; \
      } \
    return this->Parser->type##Points[id].arg;\
  }

vtkGPXReaderGetArgumentFromId(double, Way, Longitude);
vtkGPXReaderGetArgumentFromId(double, Way, Latitude);
vtkGPXReaderGetArgumentFromId(double, Way, Elevation);
vtkGPXReaderGetArgumentFromId(int,    Way, Time);
vtkGPXReaderGetArgumentFromId(double, Route, Longitude);
vtkGPXReaderGetArgumentFromId(double, Route, Latitude);
vtkGPXReaderGetArgumentFromId(double, Route, Elevation);
vtkGPXReaderGetArgumentFromId(int,    Route, Time);
vtkGPXReaderGetArgumentFromId(double, Track, Longitude);
vtkGPXReaderGetArgumentFromId(double, Track, Latitude);
vtkGPXReaderGetArgumentFromId(double, Track, Elevation);
vtkGPXReaderGetArgumentFromId(int,    Track, Time);

#define vtkGPXReaderGetStringArgumentFromId(type, arg) \
  const char* vtkGPXReader::Get##type##Point##arg(vtkIdType id) \
  { \
    if ( id < 0 ) \
      { \
      return 0; \
      } \
    if ( !this->Parser ) \
      { \
      return 0; \
      } \
    if( (vtkIdType)this->Parser->type##Points.size() <= id ) \
      { \
      return 0; \
      } \
    return this->Parser->type##Points[id].arg.c_str();\
  }

vtkGPXReaderGetStringArgumentFromId(Way, Name);
vtkGPXReaderGetStringArgumentFromId(Way, Description);
vtkGPXReaderGetStringArgumentFromId(Way, Symbol);
vtkGPXReaderGetStringArgumentFromId(Route, Name);
vtkGPXReaderGetStringArgumentFromId(Route, Description);
vtkGPXReaderGetStringArgumentFromId(Route, Symbol);

vtkIdType vtkGPXReader::GetWayPointFromTrackPoint(vtkIdType id)
{
  if ( id < 0 )
    {
    return -1;
    }
  if ( !this->Parser )
    {
    return -1;
    }
  if ( (vtkIdType)this->Parser->TrackPoints.size() <= id )
    {
    return -1;
    }
  vtkGPXPoint* pt = &this->Parser->TrackPoints[id];
  vtkIdType ii;
  for ( ii = 0; ii < (vtkIdType)this->Parser->WayPoints.size(); ++ ii )
    {
    vtkGPXPoint* wpt = &this->Parser->WayPoints[ii];
    if ( wpt->Longitude == pt->Longitude &&
      wpt->Latitude == pt->Latitude &&
      wpt->Elevation == pt->Elevation )
      {
      return ii;
      }
    }
  return -1;
}
