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

/*
 * Copyright (C) 2015 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 <udjat/defs.h>
 #include <private/mainloop.h>
 #include <udjat/module.h>
 #include <udjat/tools/mainloop.h>
 #include <udjat/tools/handler.h>
 #include <udjat/tools/logger.h>
 #include <unistd.h>

/*---[ Implement ]----------------------------------------------------------------------------------*/

 class Context : public MainLoop::Handler {
 private:

	DBusConnection	* connection	= nullptr;
	DBusWatch		* watch			= nullptr;

 protected:
	void handle_event(const Event events) override;

 public:

	Context(DBusConnection *c, int f, DBusWatch *w, short e) : MainLoop::Handler(f,(MainLoop::Handler::Event) e), connection(c), watch(w) {
	}

 };

 dbus_bool_t add_watch(DBusWatch *watch, DBusConnection *connection) {

	// Get event
	short event = 0;
 	unsigned int flags = dbus_watch_get_flags(watch);

	if (flags & DBUS_WATCH_READABLE)
		event |= POLLIN;

	if (flags & DBUS_WATCH_WRITABLE)
		event |= POLLOUT;

	if (flags & DBUS_WATCH_HANGUP)
		event |= POLLHUP;

	if (flags & DBUS_WATCH_ERROR)
		event |= POLLERR;

	Context *context = new Context(connection,dbus_watch_get_unix_fd(watch),watch,event);

	dbus_watch_set_data(watch, context, NULL);

	context->enable();

	return true;
 }

 void remove_watch(DBusWatch *watch, DBusConnection *) {

	Context *context = (Context *) dbus_watch_get_data(watch);

	if(context) {
		dbus_watch_set_data(watch, NULL, NULL);
		context->disable();
		delete context;
	}

 }

 void toggle_watch(DBusWatch *watch, DBusConnection *) {

	Context *context = (Context *) dbus_watch_get_data(watch);

	if(context) {

		context->set(dbus_watch_get_unix_fd(watch));

		if (dbus_watch_get_enabled(watch)) {
			context->enable();
		} else {
			context->disable();
		}

	}

 }

 void wake_up(DBusConnection *) {
	MainLoop::getInstance().wakeup();
 }

 void Context::handle_event(const MainLoop::Handler::Event events) {

	if(!dbus_watch_get_enabled(watch)) {
		disable();
		return;
	}

	unsigned int flags = 0;

	if (events & POLLIN) {
		flags |= DBUS_WATCH_READABLE;
	}

	if (events & POLLOUT) {
		flags |= DBUS_WATCH_WRITABLE;
	}

	if (events & POLLHUP) {
		flags |= DBUS_WATCH_HANGUP;
	}

	if (events & POLLERR) {
		flags |= DBUS_WATCH_ERROR;
	}

	if(dbus_watch_handle(watch, flags) == FALSE) {
		cerr << "d-bus\tdbus_watch_handle() failed" << endl;
		return;
	}

	dbus_connection_ref(connection);
	while (dbus_connection_get_dispatch_status(connection) == DBUS_DISPATCH_DATA_REMAINS)
        dbus_connection_dispatch(connection);
	dbus_connection_unref(connection);

 }

 void mainloop_add(DBusConnection *conn) {

	// Initialize Main loop.
	MainLoop::getInstance().type();

	// Set watch functions.
	if(!dbus_connection_set_watch_functions(
		conn,
		(DBusAddWatchFunction) add_watch,
		(DBusRemoveWatchFunction) remove_watch,
		(DBusWatchToggledFunction) toggle_watch,
		conn,
		nullptr)
	) {
		throw runtime_error("dbus_connection_set_watch_functions has failed");
	}

	// Set timeout functions.
	if(!dbus_connection_set_timeout_functions(
		conn,
		(DBusAddTimeoutFunction) add_timeout,
		(DBusRemoveTimeoutFunction) remove_timeout,
		(DBusTimeoutToggledFunction) toggle_timeout,
		conn,
		nullptr)
	) {
		throw runtime_error("dbus_connection_set_timeout_functions has failed");
	}

	dbus_connection_set_wakeup_main_function(
		conn,
		(DBusWakeupMainFunction) wake_up,
		conn,
		NULL
	);

 }

 void mainloop_remove(DBusConnection *conn) {

	if(!dbus_connection_set_watch_functions(
		conn,
		(DBusAddWatchFunction) NULL,
		(DBusRemoveWatchFunction) NULL,
		(DBusWatchToggledFunction) NULL,
		NULL,
		nullptr)
	) {
		Logger::String{"dbus_connection_set_watch_functions failed"}.error("d-bus");
	}

	if(!dbus_connection_set_timeout_functions(
		conn,
		(DBusAddTimeoutFunction) NULL,
		(DBusRemoveTimeoutFunction) NULL,
		(DBusTimeoutToggledFunction) NULL,
		NULL,
		nullptr)
	) {
		Logger::String{"dbus_connection_set_timeout_functions failed"}.error("d-bus");
	}

 }

