/* SPDX-License-Identifier: LGPL-3.0-or-later */

/*
 * Copyright (C) 2021 Perry Werneck <perry.werneck@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 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 Lesser General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

 #include <config.h>
 #include <udjat/defs.h>
 #include <udjat/tools/logger.h>
 #include <udjat/tools/dbus/message.h>
 #include <iostream>

 using namespace std;

 namespace Udjat {

	DBus::Message::Message(const char *destination, const char *path, const char *iface, const char *method) {
		message.value = dbus_message_new_method_call(destination, path, iface, method);
		message.valid = true;
		dbus_message_iter_init_append(message.value, &message.iter);
	}

	DBus::Message::Message(const DBusError &error) {
		message.value = nullptr;
		message.valid = false;
		err.valid = true;
		err.name = error.name;
		err.message = error.message;
	}

	DBus::Message::Message(DBusMessage *message) {

		if(dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_ERROR) {

			this->message.valid = false;
			err.valid = true;
			err.name = dbus_message_get_error_name(message);

			debug("Error name=",err.name);

			// Get error message.
			DBusMessageIter iter;
			dbus_message_iter_init(message, &iter);

			err.message.clear();
			if(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) {
				DBusBasicValue value;
				dbus_message_iter_get_basic(&iter,&value);
				err.message = value.str;
				debug("Error message=",err.message);
			}


		} else {

			this->message.value = message;
			dbus_message_ref(message);
			this->message.valid = dbus_message_iter_init(this->message.value, &this->message.iter);

		}

	}

	DBus::Message::~Message() {
		if(message.value) {
			dbus_message_unref(message.value);
		}
	}

	DBusMessageIter * DBus::Message::getIter() {
		if(err.valid) {
			throw runtime_error(err.message);
		}
		return & this->message.iter;
	}


	bool DBus::Message::next() {
		if(err.valid) {
			throw runtime_error(err.message);
		} else if(!message.valid) {
			return false;
		}
		return dbus_message_iter_next(&message.iter);
	}

	void DBus::Message::except() const {
		if(err.valid) {
			throw runtime_error(err.message);
		}
	}

	static void to_value(DBusMessageIter *iter, Udjat::Value &value) {

		DBusBasicValue dval;

		value.clear();
		auto type = dbus_message_iter_get_arg_type(iter);

		switch(type) {
		case DBUS_TYPE_INVALID:
			throw runtime_error("Invalid d-bus value");

		case DBUS_TYPE_ARRAY:
			// TODO: Convert to Value::Array
			// Reference: https://android.googlesource.com/platform/frameworks/base/+/a45746e/core/jni/android_bluetooth_common.cpp
			throw system_error(ENOTSUP,system_category(),"Unsupported DBUS_TYPE_ARRAY value");

		case DBUS_TYPE_DICT_ENTRY:
			// TODO: Convert to Value::Object
			throw system_error(ENOTSUP,system_category(),"Unsupported DBUS_TYPE_DICT_ENTRY value");

		case DBUS_TYPE_VARIANT:
			{
				DBusMessageIter sub;
				dbus_message_iter_recurse(iter, &sub);
				to_value(&sub,value);
			}
			break;

		case DBUS_TYPE_STRING:
		case DBUS_TYPE_OBJECT_PATH:
			dbus_message_iter_get_basic(iter,&dval);
			value.set(dval.str);
			break;

		case DBUS_TYPE_BOOLEAN:
			dbus_message_iter_get_basic(iter,&dval);
			value.set((bool) dval.bool_val);
			break;

		case DBUS_TYPE_INT16:
			dbus_message_iter_get_basic(iter,&dval);
			value.set((int) dval.i16);
			break;

		case DBUS_TYPE_INT32:
			dbus_message_iter_get_basic(iter,&dval);
			value.set((int) dval.i32);
			break;

		case DBUS_TYPE_UINT32:
			dbus_message_iter_get_basic(iter,&dval);
			value.set((unsigned int) dval.u32);
			break;

		default:
			debug("Unsupported d-bus value type:",type," (",(char) type,")");
			throw system_error(EINVAL, system_category(), String{"Unexpected d-bus value type '",type,"'"});

		}

		return;

	}

	DBus::Message & DBus::Message::pop(Udjat::Value &value) {
		if(err.valid) {
			throw runtime_error(err.message);
		}
		to_value(&message.iter,value);
		return *this;
	}

	static int to_value(DBusMessageIter *iter, DBusBasicValue &value) {
		int type = dbus_message_iter_get_arg_type(iter);

		if(type == DBUS_TYPE_VARIANT) {
			DBusMessageIter sub;
			dbus_message_iter_recurse(iter, &sub);
			type = to_value(&sub,value);
			dbus_message_iter_next(iter);
			return type;
		}

		dbus_message_iter_get_basic(iter,&value);
		dbus_message_iter_next(iter);

		return type;
	}

	int DBus::Message::get(DBusBasicValue &value) {

		if(message.valid) {
			int type = dbus_message_iter_get_arg_type(&message.iter);

			if(type == DBUS_TYPE_VARIANT) {
				DBusMessageIter sub;
				dbus_message_iter_recurse(&message.iter, &sub);
				type = to_value(&sub,value);
				dbus_message_iter_next(&message.iter);
				return type;
			}

			dbus_message_iter_get_basic(&message.iter,&value);
			message.valid = dbus_message_iter_next(&message.iter);

			return type;
		}

		return DBUS_TYPE_INVALID;
	}


	static Udjat::String get_string(DBusMessageIter *iter) {

		DBusBasicValue dval;
		int type = dbus_message_iter_get_arg_type(iter);

		if(type == DBUS_TYPE_VARIANT) {
			DBusMessageIter sub;
			dbus_message_iter_recurse(iter, &sub);
			return get_string(&sub);
		}

		switch(type) {
		case DBUS_TYPE_STRING:
		case DBUS_TYPE_OBJECT_PATH:
			dbus_message_iter_get_basic(iter,&dval);
			return String{dval.str};

		default:
			throw runtime_error("Message iterator is not string");
		}

	}

	Udjat::String DBus::Message::to_string() {
		if(!message.valid) {
			return "";
		}
		return get_string(&message.iter);
	}


	DBus::Message & DBus::Message::pop(std::string &value) {

		if(!message.valid) {
			throw runtime_error("Empty message");
		}

		value = get_string(&message.iter);
		dbus_message_iter_next(&message.iter);

		return *this;
	}

	DBus::Message & DBus::Message::pop(int &value) {

		DBusBasicValue dval;
		int type = get(dval);

		switch(type) {
		case DBUS_TYPE_STRING:
			value = atoi(dval.str);
			break;

		case DBUS_TYPE_BOOLEAN:
			value = (int) dval.bool_val;
			break;

		case DBUS_TYPE_INT16:
			value = (int) dval.i16;
			break;

		case DBUS_TYPE_INT32:
			value = (int) dval.i32;
			break;

		case DBUS_TYPE_UINT16:
			value = (int) dval.u16;
			break;

		case DBUS_TYPE_UINT32:
			value = (int) dval.u32;
			break;
			
		default:
			debug("Unsupported d-bus value type:",type," (",(char) type,")");
			throw system_error(EINVAL, system_category(), String{"Unexpected d-bus value type '",type,"' poping int"});

		}

		return *this;

	}

	DBus::Message & DBus::Message::pop(unsigned int &value) {

		DBusBasicValue dval;
		auto type = get(dval);

		switch(type) {
		case DBUS_TYPE_STRING:
			value = (unsigned int) atoi(dval.str);
			break;

		case DBUS_TYPE_BOOLEAN:
			value = (unsigned int) dval.bool_val;
			break;

		case DBUS_TYPE_INT16:
			value = (unsigned int) dval.i16;
			break;

		case DBUS_TYPE_INT32:
			value = (unsigned int) dval.i32;
			break;

		case DBUS_TYPE_UINT16:
			value = (unsigned int) dval.u16;
			break;

		case DBUS_TYPE_UINT32:
			value = (unsigned int) dval.u32;
			break;
			
		default:
			debug("Unsupported d-bus value type:",type," (",(char) type,")");
			throw system_error(EINVAL, system_category(), String{"Unexpected d-bus value type '",type,"' poping unsigned int"});

		}

		return *this;

	}

	DBus::Message & DBus::Message::pop(bool &value) {

		DBusBasicValue dval;
		auto type = get(dval);

		switch(type) {
		case DBUS_TYPE_STRING:
			value = String{dval.str}.as_bool();
			break;

		case DBUS_TYPE_BOOLEAN:
			value = dval.bool_val;
			break;

		case DBUS_TYPE_INT16:
			value = dval.i16;
			break;

		case DBUS_TYPE_INT32:
			value = dval.i32;
			break;

		case DBUS_TYPE_UINT16:
			value = dval.u16;
			break;

		case DBUS_TYPE_UINT32:
			value = dval.u32;
			break;
			
		default:
			debug("Unsupported d-bus value type:",type," (",(char) type,")");
			throw system_error(EINVAL, system_category(), String{"Unexpected d-bus value type '",type,"' poping boolean"});

		}

		return *this;

	}

	DBus::Message & DBus::Message::pop(double &value) {

		DBusBasicValue dval;
		auto type = get(dval);

		switch(type) {
		case DBUS_TYPE_STRING:
			value = atof(dval.str);
			break;

		case DBUS_TYPE_DOUBLE:
			value = dval.dbl;
			break;

		break;

		default:
			debug("Unsupported d-bus value type:",type," (",(char) type,")");
			throw system_error(EINVAL, system_category(), String{"Unexpected d-bus value type '",type,"' poping double"});

		}

		return *this;
	}

	DBus::Message & DBus::Message::push_back(const char *value) {
		dbus_message_iter_append_basic(&message.iter, DBUS_TYPE_STRING, &value);
		return *this;
	}

	DBus::Message & DBus::Message::push_back(const bool value) {
		dbus_bool_t dval = value;
		dbus_message_iter_append_basic(&message.iter, DBUS_TYPE_BOOLEAN, &dval);
		return *this;
	}

	DBus::Message & DBus::Message::push_back(const int value) {
		dbus_int32_t dval = value;
		dbus_message_iter_append_basic(&message.iter, DBUS_TYPE_INT32, &dval);
		return *this;
	}

	DBus::Message & DBus::Message::push_back(const unsigned int value) {
		dbus_uint32_t dval = value;
		dbus_message_iter_append_basic(&message.iter, DBUS_TYPE_UINT32, &dval);
		return *this;
	}

	DBus::Message & DBus::Message::push_back(const double value) {
		dbus_message_iter_append_basic(&message.iter, DBUS_TYPE_DOUBLE, &value);
		return *this;
	}

	bool DBus::Message::for_each(const std::function<bool (const Udjat::Value &value)> &call) {

		DBusMessageIter iter;

		if(!dbus_message_iter_init(this->message.value, &iter)) {
			return false;
		}

		int type;
		while ((type = dbus_message_iter_get_arg_type (&iter)) != DBUS_TYPE_INVALID) {
			Udjat::Value val;
			to_value(&iter, val);
			if(call(val)) {
				return true;
			}
			dbus_message_iter_next(&iter);		
		}
		return false;
	}

 }
