//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2012      Dennis Nienhüser <earthwings@gentoo.org>
//

#include "OSRMRunner.h"

#include "MarbleAbstractRunner.h"
#include "MarbleDebug.h"
#include "MarbleLocale.h"
#include "GeoDataDocument.h"
#include "GeoDataPlacemark.h"
#include "GeoDataExtendedData.h"
#include "routing/Maneuver.h"
#include "TinyWebBrowser.h"

#include <QtCore/QString>
#include <QtCore/QVector>
#include <QtCore/QUrl>
#include <QtCore/QTime>
#include <QtCore/QTimer>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValueIterator>

namespace Marble
{

QVector<QPair<GeoDataCoordinates,QString> > OSRMRunner:: m_cachedHints;

QString OSRMRunner:: m_hintChecksum;

OSRMRunner::OSRMRunner( QObject *parent ) :
    MarbleAbstractRunner( parent ),
    m_networkAccessManager( new QNetworkAccessManager( this ) )
{
    connect( m_networkAccessManager, SIGNAL( finished( QNetworkReply * ) ),
             this, SLOT( retrieveData( QNetworkReply * ) ) );
}

OSRMRunner::~OSRMRunner()
{
    // nothing to do
}

GeoDataFeature::GeoDataVisualCategory OSRMRunner::category() const
{
    return GeoDataFeature::OsmSite;
}

void OSRMRunner::retrieveRoute(const RouteRequest *route)
{
	if (route->size() < 2) {
		return;

	}

	QString url = "http://router.project-osrm.org/route/v1/driving/";
	GeoDataCoordinates::Unit const degree = GeoDataCoordinates::Degree;
	for (int i = 0; i<route->size(); ++i) {
		GeoDataCoordinates const coordinates = route->at(i);
		url += QString::number(coordinates.longitude(degree), 'f', 6);
		url += ',';
		url += QString::number(coordinates.latitude(degree), 'f', 6);
		if (i + 1<route->size()) {
			url += ';';

		}

	}

	//add options
	url += "?alternatives=false&overview=full&geometries=polyline6&steps=true";

	QNetworkRequest request = QNetworkRequest(QUrl(url));
	request.setRawHeader("User-Agent", TinyWebBrowser::userAgent("Browser", "OSRMRunner"));
	QNetworkReply *reply = m_networkAccessManager->get(request);
	connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
		this, SLOT(handleError(QNetworkReply::NetworkError)));

	QEventLoop eventLoop;

	QTimer timer;
	timer.setSingleShot(true);
	timer.setInterval(15000);

	connect(&timer, SIGNAL(timeout()),
		&eventLoop, SLOT(quit()));
	connect(this, SIGNAL(routeCalculated(GeoDataDocument*)),
		&eventLoop, SLOT(quit()));

	// @todo FIXME Must currently be done in the main thread, see bug 257376
	QTimer::singleShot(0, this, SLOT(get()));
	timer.start();

	eventLoop.exec();
}

void OSRMRunner::retrieveData( QNetworkReply *reply )
{
    if ( reply->isFinished() ) {
        QByteArray data = reply->readAll();
        reply->deleteLater();
        GeoDataDocument* document = parse( data );

        if ( !document ) {
            mDebug() << "Failed to parse the downloaded route data" << data;
        }

        emit routeCalculated( document );
    }
}

void OSRMRunner::handleError( QNetworkReply::NetworkError error )
{
    mDebug() << " Error when retrieving OSRM route: " << error;
}

void OSRMRunner::append(QString *input, const QString &key, const QString &value) const
{
	*input += QLatin1Char('&') + key + QLatin1Char('=') + value;
}

GeoDataLineString *OSRMRunner::decodePolyline( const QString &geometry ) const
{
    // See https://developers.google.com/maps/documentation/utilities/polylinealgorithm
    GeoDataLineString* lineString = new GeoDataLineString;
    int coordinates[2] = { 0, 0 };
    int const length = geometry.length();
    for( int i=0; i<length; /* increment happens below */ ) {
        for ( int j=0; j<2; ++j ) { // lat and lon
            int block( 0 ), shift( 0 ), result( 0 );
            do {
                block = geometry.at( i++ /* increment for outer loop */ ).toAscii() - 63;
                result |= ( block & 0x1F ) << shift;
                shift += 5;
            } while ( block >= 0x20 );
            coordinates[j] += ( ( result & 1 ) != 0 ? ~( result >> 1 ) : ( result >> 1 ) );
        }
		//enz
		int value = coordinates[1];
		if(1)//value>10000000)
			lineString->append( GeoDataCoordinates( double( coordinates[1] ) / 1E6,
                                                double( coordinates[0] ) / 1E6,
                                                0.0, GeoDataCoordinates::Degree ) );
		else
			lineString->append( GeoDataCoordinates( double( coordinates[1] ) / 1E5,
                                                double( coordinates[0] ) / 1E5,
                                                0.0, GeoDataCoordinates::Degree ) );
    }
    return lineString;
}
/** enz: changed parsing related to new OSRM api */
RoutingInstruction::TurnType OSRMRunner::parseTurnType( const QString &instruction ) const
{   
    if ( instruction == "straight" ) {
        return RoutingInstruction::Straight;
    } else if ( instruction == "slight right" ) {
        return RoutingInstruction::SlightRight;
    } else if ( instruction == "right" ) {
        return RoutingInstruction::Right;
    } else if ( instruction == "sharp right" ) {
        return RoutingInstruction::SharpRight;
    } else if ( instruction == "turn around" ) {
        return RoutingInstruction::TurnAround;
    } 
	else if (instruction == "turn") {
	 return RoutingInstruction::TurnAround;
	}
	else if (instruction == "sharp left") {
        return RoutingInstruction::SharpLeft;
    } else if ( instruction == "left" ) {
        return RoutingInstruction::Left;
    } else if ( instruction == "slight left" ) {
        return RoutingInstruction::SlightLeft;
    } else if ( instruction == "continue" ) {
        return RoutingInstruction::Continue;
    } else if ( instruction == "roundabout first exit"  ){
         return RoutingInstruction::RoundaboutFirstExit;
	}
	else if (instruction == "roundabout second exit"){
		return RoutingInstruction::RoundaboutSecondExit;
	}
	else if (instruction == "roundabout third exit"){
		return RoutingInstruction::RoundaboutThirdExit;
	}
	else if (instruction == "roundabout exit"){
        return RoutingInstruction::RoundaboutExit;
        
    }

    // ignoring ReachViaPoint = 9;
    // ignoring StayOnRoundAbout = 13;
    // ignoring StartAtEndOfStreet = 14;
    // ignoring ReachedYourDestination = 15;

    return RoutingInstruction::Unknown;
}

/** enz: completely changed parsing related to new OSRM api v5 */
GeoDataDocument *OSRMRunner::parse(const QByteArray &input)
{
	QScriptEngine engine;
	// Qt requires parentheses around json code
	QScriptValue const data = engine.evaluate("(" + QString::fromUtf8(input) + ")");

	GeoDataDocument* result = 0;
	bool ret = data.property("routes").isArray();
	if (!ret)return result;

	result = new GeoDataDocument();
	if (result == NULL)return result;
	result->setName("Open Source Routing Machine");
	GeoDataPlacemark* routePlacemark = new GeoDataPlacemark;
	routePlacemark->setName("Route");
	QScriptValue rt = data.property("routes").property("0");
	GeoDataLineString* routeWaypoints = decodePolyline(rt.property("geometry").toString());
	routePlacemark->setGeometry(routeWaypoints);

	qreal duration = data.property("routes").property("0").property("duration").toNumber();
	qreal distance = data.property("routes").property("0").property("distance").toNumber();

	QString tunit = "min";
	duration /= 60.0;//min
	if (duration > 60.0)
	{
		duration /= 60.0;//h
		tunit = "h";
	}

	QString name = "%1 %2 %3 %4 (OSRM)";
	QString unit = "m";
	qreal length = routeWaypoints->length(EARTH_RADIUS);
	if (length >= 1000) {
		length /= 1000.0;
		unit = "km";
	}
	result->setName(name.arg(length, 0, 'f', 1).arg(unit).arg(duration, 0, 'f', 1).arg(tunit));
	result->append(routePlacemark);

	if (routeWaypoints)
	{
		int x = routeWaypoints->size();
		if (x < 2)return result;
	}
	else return result;

	if (data.property("waypoints").isArray())
	{
		QScriptValue wp = data.property("waypoints");
		QVariantList details = wp.toVariant().toList();
		int wpcount = details.count();
		for (int wpx = 0; wpx < wpcount; wpx++)
		{
			bool first = true;
			GeoDataCoordinates pos1;
			QScriptValue leg = data.property("routes").property("0").property("legs").property(wpx).property("steps");
			bool ret = leg.isArray();
			QVariantList details = leg.toVariant().toList();
			int count = details.count();
			for (int i = 0; i < count; i++)
			{
				//GeoDataLineString *lineString = new GeoDataLineString;
				GeoDataPlacemark* instruction = new GeoDataPlacemark;

				QScriptValue step = leg.property(i);
				QString road = step.property("name").toString();
				QScriptValue man = step.property("maneuver");
				QString type = man.property("type").toString();
				QString mod = man.property("modifier").toString();
				int exit=-1;
				if (type == "roundabout")
				{
					exit = man.property("exit").toInteger();
					switch (exit)
					{
						case 1: mod = "roundabout first exit"; break;
						case 2: mod = "roundabout second exit"; break;
						case 3: mod = "roundabout third exit"; break;
						default: mod = "roundabout exit"; break;
					}
				}

				qreal lon = man.property("location").property("0").toNumber();
				qreal lat = man.property("location").property("1").toNumber();
				GeoDataCoordinates pos(lon, lat, 0, GeoDataCoordinates::Degree);
				if (1)
				{
					QString geom = step.property("geometry").toString();
					GeoDataLineString *lineString = decodePolyline(geom);
					pos1 = pos;
					instruction->setGeometry(lineString);
					//	result->append(instruction);
					//	instruction = new GeoDataPlacemark;
				}

				GeoDataExtendedData extendedData;
				GeoDataData turnTypeData;
				turnTypeData.setName("turnType");
				RoutingInstruction::TurnType turnType = parseTurnType(mod);
				turnTypeData.setValue(turnType);
				extendedData.addValue(turnTypeData);
				if (!road.isEmpty()) {
					GeoDataData roadName;
					roadName.setName("roadName");
					roadName.setValue(road);
					extendedData.addValue(roadName);
				}

				if (first) {
					turnType = RoutingInstruction::Continue;
					first = false;
				}

				if (turnType == RoutingInstruction::Unknown) {
					instruction->setName(mod);
				}
				else {
					instruction->setName(RoutingInstruction::generateRoadInstruction(turnType, road));
				}
				instruction->setExtendedData(extendedData);
				result->append(instruction);
			}//for steps count
		}//for legs count
	}

	return result;
}

} // namespace Marble

#include "moc_OSRMRunner.cpp"
