/*
 * Copyright (C) 2019-2022 Konsulko Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <QDebug>

#include "navigation.h"
#include "vehiclesignals.h"

Navigation::Navigation(VehicleSignals *vs, QObject * parent) :
	QObject(parent),
	m_vs(vs),
	m_connected(false)

{
	QObject::connect(m_vs, &VehicleSignals::connected, this, &Navigation::onConnected);
	QObject::connect(m_vs, &VehicleSignals::authorized, this, &Navigation::onAuthorized);
	QObject::connect(m_vs, &VehicleSignals::disconnected, this, &Navigation::onDisconnected);

	if (m_vs)
		m_vs->connect();
}

Navigation::~Navigation()
{
	delete m_vs;
}

void Navigation::sendWaypoint(double lat, double lon)
{
	if (!(m_vs && m_connected))
		return;

	// The original implementation resulted in at least 9 decimal places
	// making it through to the clients, so explicitly format for 9.  In
	// practice going from the QString default 6 to 9 does make a difference
	// with respect to smoothness in the position-based map rotations done
	// in tbtnavi.
	m_vs->set("Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Latitude", QString::number(lat, 'f', 9));
	m_vs->set("Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Longitude", QString::number(lon, 'f', 9));
}

void Navigation::broadcastPosition(double lat, double lon, double drc, double dst)
{
	if (!(m_vs && m_connected))
		return;

	m_vs->set("Vehicle.CurrentLocation.Latitude", QString::number(lat, 'f', 9));
	m_vs->set("Vehicle.CurrentLocation.Longitude", QString::number(lon, 'f', 9));
	m_vs->set("Vehicle.CurrentLocation.Heading", QString::number(drc, 'f', 9));

	// NOTES:
	// - This signal is an AGL addition, it may make sense to engage with the
	//   VSS specification upstream to discuss an addition along these lines.
	// - The signal makes more sense in kilometers wrt VSS expectations, so
	//   conversion from meters happens here for now to avoid changing the
	//   existing clients.  This may be worth revisiting down the road.
	m_vs->set("Vehicle.Cabin.Infotainment.Navigation.ElapsedDistance", QString::number(dst / 1000, 'f', 9));
}

void Navigation::broadcastRouteInfo(double lat, double lon, double route_lat, double route_lon)
{
	if (!(m_vs && m_connected))
		return;

	m_vs->set("Vehicle.CurrentLocation.Latitude", QString::number(lat, 'f', 9));
	m_vs->set("Vehicle.CurrentLocation.Longitude", QString::number(lon, 'f', 9));

	m_vs->set("Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Latitude", QString::number(route_lat, 'f', 9));
	m_vs->set("Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Longitude", QString::number(route_lon, 'f', 9));
}

void Navigation::broadcastStatus(QString state)
{
	if (!(m_vs && m_connected))
		return;

	// The signal states have been changed to all uppercase to match
	// VSS 3.x expectations, enforce that.
	m_vs->set("Vehicle.Cabin.Infotainment.Navigation.State", state.toUpper());
}

void Navigation::onConnected()
{
	if (!m_vs)
		return;

	m_vs->authorize();
}

void Navigation::onAuthorized()
{
	if (!m_vs)
		return;

	m_connected = true;

	QObject::connect(m_vs, &VehicleSignals::signalNotification, this, &Navigation::onSignalNotification);

	// NOTE: This signal is another AGL addition where it is possible
	//       upstream may be open to adding it to VSS.
	m_vs->subscribe("Vehicle.Cabin.Infotainment.Navigation.State");
	m_vs->subscribe("Vehicle.CurrentLocation.Latitude");
	m_vs->subscribe("Vehicle.CurrentLocation.Longitude");
	m_vs->subscribe("Vehicle.CurrentLocation.Heading");
	m_vs->subscribe("Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Latitude");
	m_vs->subscribe("Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Longitude");
	m_vs->subscribe("Vehicle.Cabin.Infotainment.Navigation.ElapsedDistance");
}

void Navigation::onDisconnected()
{
	QObject::disconnect(m_vs, &VehicleSignals::signalNotification, this, &Navigation::onSignalNotification);

	m_connected = false;
}

void Navigation::onSignalNotification(QString path, QString value, QString timestamp)
{
	// NOTE: Since all the known AGL users of the VSS signals are users of
	//       this API, we know that updates occur in certain sequences and
	//       can leverage this to roll up for emitting the existing events.
	//       This is the path of least effort with respect to changing
	//       the existing clients, but it may make sense down the road to
	//       either switch them to using VehicleSignals directly or having
	//       a more granular signal scheme that maps more directly onto
	//       VSS.
	if (path == "Vehicle.Cabin.Infotainment.Navigation.State") {
		QVariantMap event;
		event["state"] = value;
		emit statusEvent(event);
	} else if (path == "Vehicle.CurrentLocation.Latitude") {
		m_latitude = value.toDouble();
	} else if (path == "Vehicle.CurrentLocation.Longitude") {
		m_longitude = value.toDouble();
	} else if (path == "Vehicle.CurrentLocation.Heading") {
		m_heading = value.toDouble();
	} else if (path == "Vehicle.Cabin.Infotainment.Navigation.ElapsedDistance") {
		m_distance = value.toDouble();
		QVariantMap event;
		event["position"] = "car";
		event["latitude"] = m_latitude;
		event["longitude"] = m_longitude;
		event["direction"] = m_heading;
		event["distance"] = m_distance * 1000;
		emit positionEvent(event);
	} else if (path == "Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Latitude") {
		m_dest_latitude = value.toDouble();
	} else if (path == "Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Longitude") {
		m_dest_longitude = value.toDouble();
		QVariantMap event;
		event["position"] = "route";
		event["latitude"] = m_latitude;
		event["longitude"] = m_longitude;
		event["route_latitude"] = m_dest_latitude;
		event["route_longitude"] = m_dest_longitude;
		emit positionEvent(event);

		// NOTE: Potentially could emit a waypointsEvent here, but
		//       nothing in the demo currently requires it, so do
		//       not bother for now.  If something like Alexa is
		//       added it or some other replacement / rework will
		//       be required.
	}
}
