/******************************************************************************
 *
 * Project:  OpenCPN
 * Purpose:  Read and write KML Format
 *(http://en.wikipedia.org/wiki/Keyhole_Markup_Language) Author:   Jesper
 *Weissglas
 *
 ***************************************************************************
 *   Copyright (C) 2012 by David S. Register                               *
 *                                                                         *
 *   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.         *
 ***************************************************************************
 *
 *
 */

#include "config.h"

#include <wx/wxprec.h>

#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif

#include <vector>

#include <wx/file.h>
#include <wx/datetime.h>
#include <wx/clipbrd.h>

#include "ocpn_types.h"
#include "navutil.h"
#include "tinyxml.h"
#include "kml.h"
#include "track.h"
#include "route.h"
#include "ocpn_frame.h"
#include "own_ship.h"

extern MyFrame* gFrame;

int Kml::seqCounter = 0;
bool Kml::insertQtVlmExtendedData = false;

int Kml::ParseCoordinates(TiXmlNode* node, dPointList& points) {
  TiXmlElement* e = node->FirstChildElement("coordinates");
  if (!e) {
    wxString msg(_T("KML Parser found no <coordinates> for the element: "));
    msg << wxString(node->ToElement()->Value(), wxConvUTF8);
    wxLogMessage(msg);
    return 0;
  }

  // Parse "long,lat,z" format.

  dPoint point;

  std::stringstream ss(e->GetText());
  std::string txtCoord;

  while (1) {
    if (!std::getline(ss, txtCoord, ',')) break;
    ;
    if (txtCoord.length() == 0) break;

    point.x = atof(txtCoord.c_str());
    std::getline(ss, txtCoord, ',');
    point.y = atof(txtCoord.c_str());
    std::getline(ss, txtCoord, ' ');
    point.z = atof(txtCoord.c_str());

    points.push_back(point);
  }
  return points.size();
}

KmlPastebufferType Kml::ParseTrack(TiXmlNode* node, wxString& name) {
  parsedTrack = new Track();
  parsedTrack->SetName(name);

  if (0 == strncmp(node->ToElement()->Value(), "LineString", 10)) {
    dPointList coordinates;
    if (ParseCoordinates(node, coordinates) > 2) {
      TrackPoint* trackpoint = NULL;

      for (unsigned int i = 0; i < coordinates.size(); i++) {
        trackpoint = new TrackPoint(coordinates[i].y, coordinates[i].x);
        parsedTrack->AddPoint(trackpoint);
      }
    }
    return KML_PASTE_TRACK;
  }

  if (0 == strncmp(node->ToElement()->Value(), "gx:Track", 8)) {
    TrackPoint* trackpoint = NULL;
    TiXmlElement* point = node->FirstChildElement("gx:coord");
    int pointCounter = 0;

    for (; point; point = point->NextSiblingElement("gx:coord")) {
      double lat, lon;
      std::stringstream ss(point->GetText());
      std::string txtCoord;
      std::getline(ss, txtCoord, ' ');
      lon = atof(txtCoord.c_str());
      std::getline(ss, txtCoord, ' ');
      lat = atof(txtCoord.c_str());

      parsedTrack->AddPoint(new TrackPoint(lat, lon));
      pointCounter++;
    }

    TiXmlElement* when = node->FirstChildElement("when");

    wxDateTime whenTime;

    int i = 0;
    for (; when; when = when->NextSiblingElement("when")) {
      trackpoint = parsedTrack->GetPoint(i);
      if (!trackpoint) continue;
      whenTime.ParseFormat(wxString(when->GetText(), wxConvUTF8),
                           _T("%Y-%m-%dT%H:%M:%SZ"));
      trackpoint->SetCreateTime(whenTime);
      i++;
    }

    return KML_PASTE_TRACK;
  }
  return KML_PASTE_INVALID;
}

KmlPastebufferType Kml::ParseOnePlacemarkPoint(TiXmlNode* node,
                                               wxString& name) {
  double newLat = 0., newLon = 0.;
  dPointList coordinates;

  if (ParseCoordinates(node->ToElement(), coordinates)) {
    newLat = coordinates[0].y;
    newLon = coordinates[0].x;
  }

  if (newLat == 0.0 && newLon == 0.0) {
    wxString msg(_T("KML Parser failed to convert <Point> coordinates."));
    wxLogMessage(msg);
    return KML_PASTE_INVALID;
  }
  wxString pointName = wxEmptyString;
  TiXmlElement* e = node->Parent()->FirstChild("name")->ToElement();
  if (e) pointName = wxString(e->GetText(), wxConvUTF8);

  wxString pointDescr = wxEmptyString;
  e = node->Parent()->FirstChildElement("description");

  // If the <description> is an XML element we must convert it to text,
  // otherwise it gets lost.
  if (e) {
    TiXmlNode* n = e->FirstChild();
    if (n) switch (n->Type()) {
        case TiXmlNode::TINYXML_TEXT:
          pointDescr = wxString(e->GetText(), wxConvUTF8);
          break;
        case TiXmlNode::TINYXML_ELEMENT:
          TiXmlPrinter printer;
          printer.SetIndent("\t");
          n->Accept(&printer);
          pointDescr = wxString(printer.CStr(), wxConvUTF8);
          break;
      }
  }

  // Extended data will override description.
  TiXmlNode* n = node->Parent()->FirstChild("ExtendedData");
  if (n) {
    TiXmlPrinter printer;
    printer.SetIndent("\t");
    n->Accept(&printer);
    pointDescr = wxString(printer.CStr(), wxConvUTF8);
  }

  // XXX leak ?
  parsedRoutePoint = new RoutePoint();
  parsedRoutePoint->m_lat = newLat;
  parsedRoutePoint->m_lon = newLon;
  parsedRoutePoint->m_bIsolatedMark = true;
  parsedRoutePoint->m_bPtIsSelected = false;
  parsedRoutePoint->m_MarkDescription = pointDescr;
  parsedRoutePoint->SetName(pointName);

  return KML_PASTE_WAYPOINT;
}

KmlPastebufferType Kml::ParsePasteBuffer() {
  if (!wxTheClipboard->IsOpened())
    if (!wxTheClipboard->Open()) return KML_PASTE_INVALID;

  wxTextDataObject data;
  wxTheClipboard->GetData(data);
  kmlText = data.GetText();
  wxTheClipboard->Close();

  if (kmlText.Find(_T("<kml")) == wxNOT_FOUND) return KML_PASTE_INVALID;

  TiXmlDocument doc;
  if (!doc.Parse(kmlText.mb_str(wxConvUTF8), 0, TIXML_ENCODING_UTF8)) {
    wxLogError(wxString(doc.ErrorDesc(), wxConvUTF8));
    return KML_PASTE_INVALID;
  }
  if (0 != strncmp(doc.RootElement()->Value(), "kml", 3))
    return KML_PASTE_INVALID;

  TiXmlHandle docHandle(doc.RootElement());

  // We may or may not have a <document> depending on what the user copied.
  TiXmlElement* placemark =
      docHandle.FirstChild("Document").FirstChild("Placemark").ToElement();
  if (!placemark) {
    placemark = docHandle.FirstChild("Placemark").ToElement();
  }
  if (!placemark) {
    wxString msg(_T("KML Parser found no <Placemark> tag in the KML."));
    wxLogMessage(msg);
    return KML_PASTE_INVALID;
  }

  int pointCounter = 0;
  wxString name;
  for (; placemark; placemark = placemark->NextSiblingElement()) {
    TiXmlElement* e = placemark->FirstChildElement("name");
    if (e) name = wxString(e->GetText(), wxConvUTF8);
    pointCounter++;
  }

  if (pointCounter == 1) {
    // Is it a single waypoint?
    TiXmlNode* element = docHandle.FirstChild("Document")
                             .FirstChild("Placemark")
                             .FirstChild("Point")
                             .ToNode();
    if (!element)
      element = docHandle.FirstChild("Placemark").FirstChild("Point").ToNode();
    if (element) return ParseOnePlacemarkPoint(element, name);

    // Is it a dumb <LineString> track?
    element = docHandle.FirstChild("Document")
                  .FirstChild("Placemark")
                  .FirstChild("LineString")
                  .ToNode();
    if (!element)
      element =
          docHandle.FirstChild("Placemark").FirstChild("LineString").ToNode();
    if (element) return ParseTrack(element, name);

    // Is it a smart extended <gx:track> track?
    element = docHandle.FirstChild("Document")
                  .FirstChild("Placemark")
                  .FirstChild("gx:Track")
                  .ToNode();
    if (!element)
      element =
          docHandle.FirstChild("Placemark").FirstChild("gx:Track").ToNode();
    if (element) return ParseTrack(element, name);

    wxString msg(
        _T("KML Parser found a single <Placemark> in the KML, but no useable ")
        _T("data in it."));
    wxLogMessage(msg);
    return KML_PASTE_INVALID;
  }

  // Here we go with a full route.

  parsedRoute = new Route();
  bool foundPoints = false;
  bool foundTrack = false;
  TiXmlElement* element =
      docHandle.FirstChild("Document").FirstChild("name").ToElement();
  if (element)
    parsedRoute->m_RouteNameString = wxString(element->GetText(), wxConvUTF8);

  placemark =
      docHandle.FirstChild("Document").FirstChild("Placemark").ToElement();
  for (; placemark; placemark = placemark->NextSiblingElement()) {
    TiXmlNode* n = placemark->FirstChild("Point");
    if (n) {
      if (ParseOnePlacemarkPoint(n->ToElement(), name) == KML_PASTE_WAYPOINT) {
        parsedRoute->AddPoint(new RoutePoint(parsedRoutePoint));
        delete parsedRoutePoint;
        parsedRoutePoint = 0;
        foundPoints = true;
      }
    }

    n = placemark->FirstChild("LineString");
    if (n) {
      ParseTrack(n->ToElement(), name);
      foundTrack = true;
    }
    n = placemark->FirstChild("gx:Track");
    if (n) {
      ParseTrack(n->ToElement(), name);
      foundTrack = true;
    }
  }

  if (foundPoints && parsedRoute->GetnPoints() < 2) {
    wxString msg(
        _T("KML Parser did not find enough <Point>s to make a route."));
    wxLogMessage(msg);
    foundPoints = false;
  }

  if (foundPoints && !foundTrack) return KML_PASTE_ROUTE;
  if (foundPoints && foundTrack) return KML_PASTE_ROUTE_TRACK;
  if (!foundPoints && foundTrack) return KML_PASTE_TRACK;
  return KML_PASTE_INVALID;
}

TiXmlElement* Kml::StandardHead(TiXmlDocument& xmlDoc, wxString name) {
  TiXmlDeclaration* decl = new TiXmlDeclaration("1.0", "UTF-8", "");
  xmlDoc.LinkEndChild(decl);

  TiXmlElement* kml = new TiXmlElement("kml");
  kml->SetAttribute("xmlns:atom", "http://www.w3.org/2005/Atom");
  kml->SetAttribute("xmlns", "http://www.opengis.net/kml/2.2");
  kml->SetAttribute("xmlns:gx", "http://www.google.com/kml/ext/2.2");
  kml->SetAttribute("xmlns:kml", "http://www.opengis.net/kml/2.2");

  if (insertQtVlmExtendedData)
    kml->SetAttribute("xmlns:vlm", "http://virtual-loup-de-mer.org");

  xmlDoc.LinkEndChild(kml);

  TiXmlElement* document = new TiXmlElement("Document");
  kml->LinkEndChild(document);
  TiXmlElement* docName = new TiXmlElement("name");
  document->LinkEndChild(docName);
  TiXmlText* docNameVal = new TiXmlText(name.mb_str(wxConvUTF8));
  docName->LinkEndChild(docNameVal);
  return document;
}

std::string Kml::PointPlacemark(TiXmlElement* document,
                                RoutePoint* routepoint) {
  TiXmlElement* pmPoint = new TiXmlElement("Placemark");
  document->LinkEndChild(pmPoint);
  TiXmlElement* pmPointName = new TiXmlElement("name");
  pmPoint->LinkEndChild(pmPointName);
  TiXmlText* pmPointNameVal =
      new TiXmlText(routepoint->GetName().mb_str(wxConvUTF8));
  pmPointName->LinkEndChild(pmPointNameVal);

  TiXmlElement* pointDescr = new TiXmlElement("description");
  pmPoint->LinkEndChild(pointDescr);

  bool descrIsPlainText = true;
  wxCharBuffer descrString = routepoint->m_MarkDescription.mb_str(wxConvUTF8);

  if (insertQtVlmExtendedData) {
    // Does the RoutePoint description parse as XML with an <ExtendedData> root
    // tag?
    TiXmlDocument descrDoc;
    TiXmlElement* extendedData;
    if (descrDoc.Parse(descrString, 0, TIXML_ENCODING_UTF8)) {
      if (0 == strncmp(descrDoc.RootElement()->Value(), "ExtendedData", 12)) {
        descrIsPlainText = false;
        extendedData = descrDoc.RootElement();
        TiXmlHandle docHandle(&descrDoc);
        TiXmlElement* seq = docHandle.FirstChild("ExtendedData")
                                .FirstChild("vlm:sequence")
                                .ToElement();
        if (!seq) {
          seq = new TiXmlElement("vlm:sequence");
          TiXmlText* snVal = new TiXmlText(
              wxString::Format(_T("%04d"), seqCounter).mb_str(wxConvUTF8));
          seq->LinkEndChild(snVal);
          descrDoc.RootElement()->LinkEndChild(seq);
        }
        pmPoint->LinkEndChild(descrDoc.RootElement()->Clone());
      }
    }
    if (descrIsPlainText) {
      // We want Sequence names but there was some non-parsing stuff in the
      // description. Push that into a sub-tag of an XML formatted description.
      extendedData = new TiXmlElement("ExtendedData");
      pmPoint->LinkEndChild(extendedData);
      TiXmlElement* seq = new TiXmlElement("vlm:sequence");
      extendedData->LinkEndChild(seq);
      TiXmlText* snVal = new TiXmlText(
          wxString::Format(_T("%04d"), seqCounter).mb_str(wxConvUTF8));
      seq->LinkEndChild(snVal);

      if (routepoint->m_MarkDescription.Length()) {
        TiXmlElement* data = new TiXmlElement("Data");
        data->SetAttribute("name", "Description");
        extendedData->LinkEndChild(data);

        TiXmlElement* value = new TiXmlElement("value");
        data->LinkEndChild(value);
        TiXmlText* txtVal = new TiXmlText(descrString);
        value->LinkEndChild(txtVal);
      }
    }
    if (extendedData && seqCounter == 0) {
      const wxCharBuffer ownshipPos =
          wxString::Format(_T("%f %f"), gLon, gLat).mb_str(wxConvUTF8);
      TiXmlHandle h(extendedData);
      TiXmlElement* route = h.FirstChild("vlm:route").ToElement();
      TiXmlElement* ownship =
          h.FirstChild("vlm:route").FirstChild("ownship").ToElement();
      if (route) {
        if (ownship) {
          TiXmlText* owns = ownship->FirstChild()->ToText();
          if (owns) {
            owns->SetValue(ownshipPos);
          } else {
            owns = new TiXmlText(ownshipPos);
            ownship->LinkEndChild(owns);
          }
        } else {
          ownship = new TiXmlElement("ownship");
          route->LinkEndChild(ownship);
          TiXmlText* owns = new TiXmlText(ownshipPos);
          ownship->LinkEndChild(owns);
        }
      } else {
        route = new TiXmlElement("vlm:route");
        extendedData->LinkEndChild(route);
        ownship = new TiXmlElement("ownship");
        route->LinkEndChild(ownship);
        TiXmlText* owns = new TiXmlText(ownshipPos);
        ownship->LinkEndChild(owns);
      }
    }
  }

  else {
    // Add description as dumb text.
    TiXmlText* pointDescrVal = new TiXmlText(descrString);
    pointDescr->LinkEndChild(pointDescrVal);
  }

  TiXmlElement* point = new TiXmlElement("Point");
  pmPoint->LinkEndChild(point);

  TiXmlElement* pointCoord = new TiXmlElement("coordinates");
  point->LinkEndChild(pointCoord);

  std::stringstream pointCoordStr;
  pointCoordStr << routepoint->m_lon << "," << routepoint->m_lat << ",0. ";

  TiXmlText* pointText = new TiXmlText(pointCoordStr.str());
  pointCoord->LinkEndChild(pointText);

  return pointCoordStr.str();
}

wxString Kml::MakeKmlFromRoute(Route* route, bool insertSeq) {
  insertQtVlmExtendedData = insertSeq;
  seqCounter = 0;
  TiXmlDocument xmlDoc;
  wxString name = _("OpenCPN Route");
  if (route->m_RouteNameString.Length()) name = route->m_RouteNameString;
  TiXmlElement* document = StandardHead(xmlDoc, name);

  std::stringstream lineStringCoords;

  RoutePointList* pointList = route->pRoutePointList;
  wxRoutePointListNode* pointnode = pointList->GetFirst();
  RoutePoint* routepoint;

  while (pointnode) {
    routepoint = pointnode->GetData();

    lineStringCoords << PointPlacemark(document, routepoint);
    seqCounter++;
    pointnode = pointnode->GetNext();
  }

  TiXmlElement* pmPath = new TiXmlElement("Placemark");
  document->LinkEndChild(pmPath);

  TiXmlElement* pmName = new TiXmlElement("name");
  pmPath->LinkEndChild(pmName);
  TiXmlText* pmNameVal = new TiXmlText("Path");
  pmName->LinkEndChild(pmNameVal);

  TiXmlElement* linestring = new TiXmlElement("LineString");
  pmPath->LinkEndChild(linestring);

  TiXmlElement* coordinates = new TiXmlElement("coordinates");
  linestring->LinkEndChild(coordinates);

  TiXmlText* text = new TiXmlText(lineStringCoords.str());
  coordinates->LinkEndChild(text);

  TiXmlPrinter printer;
  printer.SetIndent("  ");
  xmlDoc.Accept(&printer);

  return wxString(printer.CStr(), wxConvUTF8);
}

wxString Kml::MakeKmlFromTrack(Track* track) {
  TiXmlDocument xmlDoc;
  wxString name = _("OpenCPN Track");
  if (track->GetName().Length()) name = track->GetName();
  TiXmlElement* document = StandardHead(xmlDoc, name);

  TiXmlElement* pmTrack = new TiXmlElement("Placemark");
  document->LinkEndChild(pmTrack);

  TiXmlElement* pmName = new TiXmlElement("name");
  pmTrack->LinkEndChild(pmName);
  TiXmlText* pmNameVal = new TiXmlText(track->GetName().mb_str(wxConvUTF8));
  pmName->LinkEndChild(pmNameVal);

  TiXmlElement* gxTrack = new TiXmlElement("gx:Track");
  pmTrack->LinkEndChild(gxTrack);

  std::stringstream lineStringCoords;

  for (int i = 0; i < track->GetnPoints(); i++) {
    TrackPoint* trackpoint = track->GetPoint(i);

    TiXmlElement* when = new TiXmlElement("when");
    gxTrack->LinkEndChild(when);

    wxDateTime whenTime(trackpoint->GetCreateTime());
    TiXmlText* whenVal = new TiXmlText(
        whenTime.Format(_T("%Y-%m-%dT%H:%M:%SZ")).mb_str(wxConvUTF8));
    when->LinkEndChild(whenVal);
  }

  for (int i = 0; i < track->GetnPoints(); i++) {
    TrackPoint* trackpoint = track->GetPoint(i);

    TiXmlElement* coord = new TiXmlElement("gx:coord");
    gxTrack->LinkEndChild(coord);
    wxString coordStr =
        wxString::Format(_T("%f %f 0.0"), trackpoint->m_lon, trackpoint->m_lat);
    TiXmlText* coordVal = new TiXmlText(coordStr.mb_str(wxConvUTF8));
    coord->LinkEndChild(coordVal);
  }

  TiXmlPrinter printer;
  printer.SetIndent("  ");
  xmlDoc.Accept(&printer);

  return wxString(printer.CStr(), wxConvUTF8);
}

wxString Kml::MakeKmlFromWaypoint(RoutePoint* routepoint) {
  TiXmlDocument xmlDoc;
  wxString name = _("OpenCPN Waypoint");
  if (routepoint->GetName().Length()) name = routepoint->GetName();
  TiXmlElement* document = StandardHead(xmlDoc, name);

  insertQtVlmExtendedData = false;
  PointPlacemark(document, routepoint);

  TiXmlPrinter printer;
  printer.SetIndent("  ");
  xmlDoc.Accept(&printer);

  return wxString(printer.CStr(), wxConvUTF8);
}

void Kml::CopyRouteToClipboard(Route* route) {
  KmlFormatDialog* formatDlg = new KmlFormatDialog(gFrame);
  int format = formatDlg->ShowModal();

  if (format != wxID_CANCEL) {
    format = formatDlg->GetSelectedFormat();
    bool extradata = (format == KML_COPY_EXTRADATA);

    ::wxBeginBusyCursor();
    if (wxTheClipboard->Open()) {
      wxTextDataObject* data = new wxTextDataObject;
      data->SetText(MakeKmlFromRoute(route, extradata));
      wxTheClipboard->SetData(data);
    }
    ::wxEndBusyCursor();
  }
  delete formatDlg;
}

void Kml::CopyTrackToClipboard(Track* track) {
  ::wxBeginBusyCursor();
  if (wxTheClipboard->Open()) {
    wxTextDataObject* data = new wxTextDataObject;
    data->SetText(MakeKmlFromTrack(track));
    wxTheClipboard->SetData(data);
  }
  ::wxEndBusyCursor();
}

void Kml::CopyWaypointToClipboard(RoutePoint* rp) {
  if (wxTheClipboard->Open()) {
    wxTextDataObject* data = new wxTextDataObject;
    data->SetText(MakeKmlFromWaypoint(rp));
    wxTheClipboard->SetData(data);
  }
}

Kml::Kml() {
  parsedRoute = NULL;
  parsedTrack = NULL;
  parsedRoutePoint = NULL;
}

Kml::~Kml() {
  delete parsedTrack;
  if (parsedRoute) {
    for (int i = 1; i <= parsedRoute->GetnPoints(); i++) {
      if (parsedRoute->GetPoint(i)) delete parsedRoute->GetPoint(i);
    }
    delete parsedRoute;
  }
  delete parsedRoutePoint;
}

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

KmlFormatDialog::KmlFormatDialog(wxWindow* parent)
    : wxDialog(parent, wxID_ANY, _("Choose Format for Copy"), wxDefaultPosition,
               wxSize(250, 230)) {
  wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);

  wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
  topSizer->Add(sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5);

  choices.push_back(new wxRadioButton(
      this, KML_COPY_STANDARD, _("KML Standard (Google Earth and others)"),
      wxDefaultPosition, wxDefaultSize, wxRB_GROUP));

  choices.push_back(new wxRadioButton(
      this, KML_COPY_EXTRADATA, _("KML with extended waypoint data (QtVlm)"),
      wxDefaultPosition));

  wxStdDialogButtonSizer* buttonSizer =
      CreateStdDialogButtonSizer(wxOK | wxCANCEL);

  sizer->Add(choices[0], 0, wxEXPAND | wxALL, 5);
  sizer->Add(choices[1], 0, wxEXPAND | wxALL, 5);
  sizer->Add(buttonSizer, 0, wxEXPAND | wxTOP, 5);

  topSizer->SetSizeHints(this);
  SetSizer(topSizer);
}

int KmlFormatDialog::GetSelectedFormat() {
  for (unsigned int i = 0; i < choices.size(); i++) {
    if (choices[i]->GetValue()) return choices[i]->GetId();
  }
  return 0;
}
