/*
 * oFono - Open Source Telephony
 * Copyright (C) 2015 Canonical Ltd
 *
 * SPDX-License-Identifier: GPL-2.0-only
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <glib.h>
#include <gdbus.h>
#include <ofono.h>

#define OFONO_API_SUBJECT_TO_CHANGE
#include <ofono/plugin.h>
#include <ofono/modem.h>
#include <ofono/log.h>
#include <ofono/emulator.h>

#define DBUS_PROPERTIES_INTERFACE     "org.freedesktop.DBus.Properties"
#define UPOWER_SERVICE                "org.freedesktop.UPower"
#define UPOWER_PATH                   "/org/freedesktop/UPower"
#define UPOWER_INTERFACE              UPOWER_SERVICE
#define UPOWER_DEVICE_INTERFACE       UPOWER_SERVICE ".Device"

static guint modem_watch;
static guint upower_daemon_watch;
static DBusConnection *connection;
static int last_battery_level;
static char *battery_device_path;

static void emulator_battery_cb(struct ofono_atom *atom, void *data)
{
	struct ofono_emulator *em = __ofono_atom_get_data(atom);
	int val = GPOINTER_TO_INT(data);

	DBG("calling set_indicator: %d", val);
	ofono_emulator_set_indicator(em, OFONO_EMULATOR_IND_BATTERY, val);
}

static void update_modem_battery_indicator(struct ofono_modem *modem,
								void *data)
{
	__ofono_modem_foreach_registered_atom(modem,
				OFONO_ATOM_TYPE_EMULATOR_HFP,
				emulator_battery_cb,
				data);
}

static void update_battery_level(double percentage_val)
{
	int battery_level;

	if (percentage_val <= 1.00) {
		battery_level = 0;
	} else if (percentage_val > 1.00 && percentage_val <= 100.00) {
		battery_level = ((int) percentage_val - 1) / 20 + 1;
	} else {
		ofono_error("%s: Invalid value for battery level: %f",
								__func__,
								percentage_val);
		return;
	}

	DBG("last_battery_level: %d battery_level: %d (%f)", last_battery_level,
						battery_level, percentage_val);

	if (last_battery_level == battery_level)
		return;

	last_battery_level = battery_level;

	__ofono_modem_foreach(update_modem_battery_indicator,
					GINT_TO_POINTER(battery_level));
}

static gboolean battery_props_changed(DBusConnection *conn, DBusMessage *msg,
				void *user_data)

{
	const char *iface;
	DBusMessageIter iter, dict;
	double percentage_val;
	gboolean percentage_found = FALSE;
	gboolean retval = FALSE;

	DBG("");

	dbus_message_iter_init(msg, &iter);

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
		ofono_error("%s: iface != TYPE_STRING!", __func__);
		goto done;
	}

	dbus_message_iter_get_basic(&iter, &iface);

	if (g_str_equal(iface, UPOWER_DEVICE_INTERFACE) != TRUE) {
		ofono_error("%s: wrong iface: %s!", __func__, iface);
		goto done;
	}

	if (!dbus_message_iter_next(&iter)) {
		ofono_error("%s: advance iter failed!", __func__);
		goto done;
	}

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
		ofono_error("%s: type != ARRAY!", __func__);
		goto done;
	}

	dbus_message_iter_recurse(&iter, &dict);

	while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry, val;
		const char *key;

		dbus_message_iter_recurse(&dict, &entry);

		if (dbus_message_iter_get_arg_type(&entry) !=
				DBUS_TYPE_STRING) {
			ofono_error("%s: key type != STRING!", __func__);
			goto done;
		}

		dbus_message_iter_get_basic(&entry, &key);

		if (g_str_equal(key, "Percentage") != TRUE) {
			dbus_message_iter_next(&dict);
			continue;
		}

		dbus_message_iter_next(&entry);
		if (dbus_message_iter_get_arg_type(&entry) !=
				DBUS_TYPE_VARIANT) {
			ofono_error("%s: 'Percentage' val != VARIANT",
								__func__);
			goto done;
		}

		dbus_message_iter_recurse(&entry, &val);

		if (dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_DOUBLE) {
			ofono_error("%s: 'Percentage' val != DOUBLE", __func__);
			goto done;
		}

		dbus_message_iter_get_basic(&val, &percentage_val);
		percentage_found = TRUE;
		break;
	}

	/* No errors found during parsing, so don't trigger cb removal */
	retval = TRUE;

	if (percentage_found == FALSE)
		goto done;

	update_battery_level(percentage_val);

done:
	return retval;
}

static void emulator_hfp_watch(struct ofono_atom *atom,
				enum ofono_atom_watch_condition cond,
				void *data)
{
	if (cond == OFONO_ATOM_WATCH_CONDITION_REGISTERED) {
		struct ofono_emulator *em = __ofono_atom_get_data(atom);

		DBG("REGISTERED; calling set_indicator: %d", last_battery_level);

		ofono_emulator_set_indicator(em, OFONO_EMULATOR_IND_BATTERY,
							last_battery_level);
		return;
	}

	DBG("UNREGISTERED");
}

static void modemwatch(struct ofono_modem *modem, gboolean added, void *user)
{
	const char *path = ofono_modem_get_path(modem);

	DBG("modem: %s, added: %d", path, added);

	if (added)
		__ofono_modem_add_atom_watch(modem,
					OFONO_ATOM_TYPE_EMULATOR_HFP,
					emulator_hfp_watch, NULL, NULL);
}

static void call_modemwatch(struct ofono_modem *modem, void *user)
{
	modemwatch(modem, TRUE, user);
}

static gboolean parse_devices_reply(DBusMessage *reply)
{
	DBusMessageIter array, iter;
	const char *path;

	DBG("");

	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
		ofono_error("%s: ERROR reply to EnumerateDevices", __func__);
		return FALSE;
	}

	if (dbus_message_iter_init(reply, &array) == FALSE) {
		ofono_error("%s: error initializing array iter", __func__);
		return FALSE;
	}

	if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY) {
		ofono_error("%s: type != ARRAY!", __func__);
		return FALSE;
	}

	dbus_message_iter_recurse(&array, &iter);

	while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_OBJECT_PATH) {
		dbus_message_iter_get_basic(&iter, &path);

		if (g_strrstr(path, "/battery_")) {
			ofono_info("%s: found 1st battery device: %s", __func__,
									path);
			battery_device_path = g_strdup(path);
			break;
		}

		if (!dbus_message_iter_next(&iter))
			break;
	}

	return TRUE;
}

static void enum_devices_reply(DBusPendingCall *call, void *user_data)
{
	DBusMessage *reply;

	DBG("");

	reply = dbus_pending_call_steal_reply(call);
	if (reply == NULL) {
		ofono_error("%s: dbus_message_new_method failed", __func__);
		goto done;
	}

	if (parse_devices_reply(reply) == FALSE)
		goto done;

	DBG("parse_devices_reply OK");

	/* TODO: handle removable batteries */

	if (battery_device_path == NULL) {
		ofono_error("%s: no battery detected", __func__);
		goto done;
	}

	/* Always listen to PropertiesChanged for battery */
	g_dbus_add_signal_watch(connection, UPOWER_SERVICE, battery_device_path,
						DBUS_INTERFACE_PROPERTIES,
						"PropertiesChanged",
						battery_props_changed,
						NULL, NULL);

	modem_watch = __ofono_modemwatch_add(modemwatch, NULL, NULL);
	__ofono_modem_foreach(call_modemwatch, NULL);

done:
	if (reply)
		dbus_message_unref(reply);

	dbus_pending_call_unref(call);
}

static void upower_connect(DBusConnection *conn, void *user_data)
{
	DBusPendingCall *call;
	DBusMessage *msg;

	DBG("upower connect");

	msg = dbus_message_new_method_call(UPOWER_SERVICE,
						UPOWER_PATH,
						UPOWER_INTERFACE,
						"EnumerateDevices");
	if (msg == NULL) {
		ofono_error("%s: dbus_message_new_method failed", __func__);
		return;
	}

	if (!dbus_connection_send_with_reply(conn, msg, &call, -1)) {
		ofono_error("%s: Sending EnumerateDevices failed", __func__);
		goto done;
	}

	dbus_pending_call_set_notify(call, enum_devices_reply, NULL, NULL);
done:
	dbus_message_unref(msg);
}

static void upower_disconnect(DBusConnection *conn, void *user_data)
{
	DBG("upower disconnect");

	if (modem_watch) {
		__ofono_modemwatch_remove(modem_watch);
		modem_watch = 0;
	}

	if (battery_device_path) {
		g_free(battery_device_path);
		battery_device_path = NULL;
	}
}

static int upower_init(void)
{
	DBG("upower init");

	connection = ofono_dbus_get_connection();
	upower_daemon_watch = g_dbus_add_service_watch(connection,
							UPOWER_SERVICE,
							upower_connect,
							upower_disconnect,
							NULL, NULL);

	return 0;
}

static void upower_exit(void)
{
	if (upower_daemon_watch)
		g_dbus_remove_watch(connection, upower_daemon_watch);

	if (modem_watch)
		__ofono_modemwatch_remove(modem_watch);

	if (battery_device_path)
		g_free(battery_device_path);
}

OFONO_PLUGIN_DEFINE(upower, "upower battery monitor", VERSION,
		OFONO_PLUGIN_PRIORITY_DEFAULT, upower_init, upower_exit)
