/*
    Copyright (C) 2016 Robert Lipe, robertlipe+source@gpsbabel.org

    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 <QtCore/QByteArray>       // for QByteArray
#include <QtCore/QIODevice>        // for operator|, QIODevice, QIODevice::ReadOnly, QIODevice::Text
#include <QtCore/QJsonArray>       // for QJsonArray
#include <QtCore/QJsonDocument>    // for QJsonDocument, QJsonDocument::Compact, QJsonDocument::Indented, QJsonDocument::JsonFormat
#include <QtCore/QJsonObject>      // for QJsonObject
#include <QtCore/QJsonParseError>  // for QJsonParseError
#include <QtCore/QJsonValue>       // for QJsonValue
#include <QtCore/QJsonValueRef>    // for QJsonValueRef

#include "defs.h"
#include "geojson.h"
#include "src/core/file.h"         // for File
#include "src/core/logging.h"      // for Fatal


void
GeoJsonFormat::rd_init(const QString& fname) {
	ifd = new gpsbabel::File(fname);
	ifd->open(QIODevice::ReadOnly | QIODevice::Text);
}

void
GeoJsonFormat::wr_init(const QString& fname) {
  feature_collection = new QJsonArray;
  ofd = new gpsbabel::File(fname);
  ofd->open(QIODevice::WriteOnly);
}

void
GeoJsonFormat::geojson_waypt_pr(const Waypoint* waypoint) const
{
  QJsonObject geometry;
  geometry[TYPE] = POINT;
  QJsonArray coordinates;
  coordinates.append(waypoint->longitude);
  coordinates.append(waypoint->latitude);
  if (waypoint->altitude != unknown_alt && waypoint->altitude != 0) {
    coordinates.append(waypoint->altitude);
  }
  geometry[COORDINATES] = coordinates;

  QJsonObject feature;
  feature[TYPE] = FEATURE;
  feature[GEOMETRY] = geometry;

  // Build up the properties.
  QJsonObject properties;
  if (!waypoint->shortname.isEmpty()) {
    properties[NAME] = waypoint->shortname;
  }
  if (!waypoint->description.isEmpty()) {
    properties[DESCRIPTION] = waypoint->description;
  }
  if (waypoint->HasUrlLink()) {
    UrlLink link = waypoint->GetUrlLink();
    if (!link.url_.isEmpty()) {
      properties[URL] = link.url_;
    }
    if (!link.url_link_text_.isEmpty()) {
      properties[URLNAME] = link.url_link_text_;
    }
  }
  if (!properties.empty()) {
    feature[PROPERTIES] = properties;
  }
  
  feature_collection->append(feature);
}

void
GeoJsonFormat::rd_deinit() {
  ifd->close();
  delete ifd;
  ifd = nullptr;
}

void
GeoJsonFormat::wr_deinit() {
  QJsonObject object;
  object[TYPE] = FEATURE_COLLECTION;
  object[FEATURES]  = *feature_collection;

  QJsonDocument save(object);
  QJsonDocument::JsonFormat style = compact_opt ? QJsonDocument::Compact : QJsonDocument::Indented;
  ofd->write(save.toJson(style));
  ofd->close();

  delete ofd;
  ofd = nullptr;
  delete feature_collection;
  feature_collection = nullptr;
}

Waypoint* 
GeoJsonFormat::waypoint_from_coordinates(const QJsonArray& coordinates)
{
	auto waypoint = new Waypoint();
	waypoint->latitude = coordinates.at(1).toDouble();
	waypoint->longitude = coordinates.at(0).toDouble();
	if (coordinates.size() > 2)
	{
		waypoint->altitude = coordinates.at(3).toDouble();
	}
	return waypoint;
}

void 
GeoJsonFormat::routes_from_polygon_coordinates(const QJsonArray& polygon)
{
	for (auto && lineStringIterator : polygon)
	{
		QJsonArray coordinates = (lineStringIterator).toArray();
		auto* route = new route_head;
		route_add_head(route);
		for (auto && coordinate : coordinates)
		{
			auto waypoint = waypoint_from_coordinates(coordinate.toArray());
			route_add_wpt(route, waypoint);
		}
	}
}

void
GeoJsonFormat::read() {
	QString file_content = ifd->readAll();
	QJsonParseError error{};
	QJsonDocument document = QJsonDocument::fromJson(file_content.toUtf8(), &error);
  if (error.error != QJsonParseError::NoError) {
    fatal(FatalMsg().nospace() << MYNAME << ": GeoJSON parse error in " << ifd->fileName() << ": " << error.errorString());
  }
	QJsonObject rootObject = document.object();

	if (rootObject[TYPE] != FEATURE_COLLECTION)
	{
		return;
	}
	QJsonArray features = rootObject.value(FEATURES).toArray();
	for (auto && iterator : features)
	{
		QJsonObject feature = iterator.toObject();
		QJsonObject properties = (feature.value(PROPERTIES)).toObject();
		QString name;
		QString description;
		if (!properties.empty())
		{
			if (properties.contains(NAME))
			{
				name = properties[NAME].toString();
			}
			if (properties.contains(DESCRIPTION))
			{
				description = properties[DESCRIPTION].toString();
			}
		}
		
		QJsonObject geometry = feature.value(GEOMETRY).toObject();
		auto geometry_type = geometry[TYPE];
		if (geometry_type == POINT)
		{
			QJsonArray coordinates = geometry.value(COORDINATES).toArray();
			auto waypoint = waypoint_from_coordinates(coordinates);
			waypoint->shortname = name;
			waypoint->description = description;
			if (properties.contains(URL))
			{
				QString url = properties[URL].toString();
				if (properties.contains(URLNAME))
				{
					QString url_text = properties[URLNAME].toString();
					waypoint->AddUrlLink(UrlLink(url, url_text));
				}
				else
				{
					waypoint->AddUrlLink(UrlLink(url));
				}
			}
			waypt_add(waypoint);
		}
		else if (geometry_type == MULTIPOINT)
		{
			QJsonArray coordinates = geometry.value(COORDINATES).toArray();
			for (auto && coordinate : coordinates)
			{
				auto waypoint = waypoint_from_coordinates(coordinate.toArray());
				waypt_add(waypoint);
			}
		}
		else if (geometry_type == LINESTRING)
		{
			QJsonArray coordinates = geometry.value(COORDINATES).toArray();
			auto* route = new route_head;
			route->rte_name = name;
			route_add_head(route);
			for (auto && coordinate : coordinates)
			{
				auto waypoint = waypoint_from_coordinates(coordinate.toArray());
				route_add_wpt(route, waypoint);
			}
		}
		else if (geometry_type == POLYGON)
		{
			QJsonArray polygon = geometry.value(COORDINATES).toArray();
			routes_from_polygon_coordinates(polygon);
		}
		else if (geometry_type == MULTIPOLYGON)
		{
			QJsonArray polygons = geometry.value(COORDINATES).toArray();
			for (auto && polygons_iterator : polygons)
			{
				QJsonArray polygon = polygons_iterator.toArray();
				routes_from_polygon_coordinates(polygon);
			}
		}
		else if (geometry_type == MULTILINESTRING)
		{
			QJsonArray line_strings = geometry.value(COORDINATES).toArray();
			for (auto && line_string : line_strings)
			{
				QJsonArray coordinates = line_string.toArray();
				auto* route = new route_head;
				track_add_head(route);
				for (auto && coordinate : coordinates)
				{
					auto waypoint = waypoint_from_coordinates(coordinate.toArray());
					route_add_wpt(route, waypoint);
				}
			}
		}
	}
}


void GeoJsonFormat::geojson_track_hdr(const route_head* track) {
  track_object = new QJsonObject();

  (*track_object)[TYPE] = FEATURE;
  track_coords = new QJsonArray();

  QJsonObject properties;
  if (!track->rte_name.isEmpty()) {
    properties[NAME] = track->rte_name;
  }
  (*track_object)[PROPERTIES] = properties;
}

void GeoJsonFormat::geojson_track_disp(const Waypoint* trackpoint) const
{

  QJsonArray coords;
  coords.append(trackpoint->longitude);
  coords.append(trackpoint->latitude);
  if (trackpoint->altitude != unknown_alt && trackpoint->altitude != 0) {
    coords.append(trackpoint->altitude);
  }
  (*track_coords).append(coords);
}

void GeoJsonFormat::geojson_track_tlr(const route_head* /*unused*/) {
  QJsonObject geometry;
  geometry[TYPE] = LINESTRING;
  geometry[COORDINATES] = *track_coords;
  (*track_object)[GEOMETRY] = geometry;
  feature_collection->append(*track_object);
  delete track_object;
  track_object = nullptr;
  delete track_coords;
  track_coords = nullptr;
}

void
GeoJsonFormat::write() {
  auto geojson_waypt_pr_lambda = [this](const Waypoint* waypointp)->void {
    geojson_waypt_pr(waypointp);
  };
  waypt_disp_all(geojson_waypt_pr_lambda);

  auto geojson_track_hdr_lambda = [this](const route_head* rte)->void {
    geojson_track_hdr(rte);
  };
  auto geojson_track_tlr_lambda = [this](const route_head* rte)->void {
    geojson_track_tlr(rte);
  };
  auto geojson_track_disp_lambda = [this](const Waypoint* waypointp)->void {
    geojson_track_disp(waypointp);
  };
  track_disp_all(geojson_track_hdr_lambda, geojson_track_tlr_lambda, geojson_track_disp_lambda);
}

