/*
 *  Copyright (C) 2009
 *      Authors (alphabetical) :
 *              Baruch Even <baruch@ev-en.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Public License as published by
 *  the Free Software Foundation; version 2 of the license.
 *
 *  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 Lesser Public License for more details.
 */
using GLib;

namespace WebManager {
	class Avahi : Object {

		private dynamic DBus.Object server;
		private dynamic DBus.Object group;

		enum AvahiServerStatus {
			INVALID,
			REGISTERING,
			RUNNING,
			COLLISION,
			FAILURE
		}

		enum AvahiEntryGroupState {
			UNCOMMITED,
			REGISTERING,
			ESTABLISHED,
			COLLISION,
			FAILURE
		}

		static string dbus_name = "org.freedesktop.Avahi";
		static string dbus_path_server = "/";
		static string dbus_interface_server = dbus_name + ".Server";
		static string dbus_entry_group = dbus_name + ".EntryGroup";
		string service_name;
		int rename_count;

		construct {
			service_name = "Neo Freerunner Web Manager";
			rename_count = 12;

			try {
				var dbus = DBus.Bus.get(DBus.BusType.SYSTEM);
				// server = dbus.Interface(bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ), avahi.DBUS_INTERFACE_SERVER )
				server = dbus.get_object(dbus_name, dbus_path_server, dbus_interface_server);
			} catch (DBus.Error e) {
				debug("Error getting avahi server interface: %s", e.message);
			}
		}

		public void run() {
			return_if_fail(server != null);

			server.EntryGroupNew(cb_entry_group_new);
		}

		private void cb_entry_group_new(dynamic DBus.Object entry_group_path, GLib.Error? e) {
			if (e != null) {
				debug("Error allocating a new entry group: %s", e.message);
				return;
			}

			if (entry_group_path == null) {
				debug("Got an empty entry group");
				return;
			}

			try {
				var dbus = DBus.Bus.get(DBus.BusType.SYSTEM);
				group = dbus.get_object(dbus_name, entry_group_path.get_path(), dbus_entry_group);
				if (group == null) {
					debug("Failed to allocate group");
					return;
				}
			} catch (DBus.Error e) {
				debug("Error getting entry group object: %s", e.message);
				return;
			}

			group.StateChanged += cb_group_state_changed;

			// Initiate the server work
			server.StateChanged += cb_server_state_changed;
			server.GetState(reply_server_get_state);
		}

		private void cb_group_state_changed(dynamic DBus.Object dbus_obj, int state) {
			debug("Group state changed to %d", state);
			if (state == AvahiEntryGroupState.ESTABLISHED) {
				debug("Entry group established");
			} else if (state == AvahiEntryGroupState.COLLISION) {
				debug("Entry group collision");
				if (rename_count <= 0) {
					debug("Too many collisions, not trying anymore");
				} else {
					string service_name = server.GetAlternativeServiceName(service_name);
					debug("Changing name to %s", service_name);
					remove_service();
					add_service();
				}
				rename_count -= 1;
			}
		}

		private void cb_server_state_changed(dynamic DBus.Object dbus_obj, int state) {
			reply_server_get_state(state, null);
		}

		private void reply_server_get_state(int state, GLib.Error? e) {
			if (e != null) {
				debug("Error when getting reply from server for get state: %s", e.message);
				return;
			}

			if (state == AvahiServerStatus.COLLISION) {
				debug("There is an avahi server collision, removing ourselves");
				remove_service();
			} else if (state == AvahiServerStatus.RUNNING) {
				debug("Avahi server is running, adding ourselves");
				add_service();
			}
		}

		private void remove_service() {
			if (group != null)
				group.Reset();
		}

		private void add_service() {
			group.AddService(
					-1, //IF_UNSPEC,
					-1, //PROTO_UNSPEC,
					(uint32)0,  //flags
					service_name, "_http._tcp",
					"", "", // Domain and host are local
					(uint16)80
					 , ""// service TXT
					);
			group.Commit();
		}
	}
}
