public struct SwayncDaemonData {
    public bool dnd;
    public bool cc_open;
    public uint count;
    public bool inhibited;
}

[DBus (name = "org.erikreider.swaync.cc")]
interface CcDaemon : Object {

    public abstract bool reload_css () throws Error;

    public abstract void reload_config () throws Error;

    public abstract void hide_latest_notifications (bool close) throws DBusError, IOError;

    public abstract void close_all_notifications () throws DBusError, IOError;

    public abstract uint notification_count () throws DBusError, IOError;

    public abstract bool get_dnd () throws DBusError, IOError;

    public abstract void set_dnd (bool state) throws DBusError, IOError;

    public abstract bool get_visibility () throws DBusError, IOError;

    public abstract void toggle_visibility () throws DBusError, IOError;

    public abstract bool toggle_dnd () throws DBusError, IOError;

    public abstract void set_visibility (bool value) throws DBusError, IOError;

    [DBus (name = "GetSubscribeData")]
    public abstract SwayncDaemonData get_subscribe_data () throws Error;

    public signal void subscribe_v2 (uint count, bool dnd, bool cc_open, bool inhibited);

    public abstract bool add_inhibitor (string application_id) throws DBusError, IOError;
    public abstract bool remove_inhibitor (string application_id) throws DBusError, IOError;
    public abstract uint number_of_inhibitors () throws DBusError, IOError;
    public abstract bool is_inhibited () throws DBusError, IOError;
    public abstract bool clear_inhibitors () throws DBusError, IOError;
}

private CcDaemon cc_daemon = null;

private void print_help (string[] args) {
    print ("Usage:\n");
    print ("  %s <OPTION>\n".printf (args[0]));
    print ("Help:\n");
    print ("  -h, \t --help \t\t\t Show help options\n");
    print ("  -v, \t --version \t\t\t Prints version\n");
    print ("Options:\n");
    print ("  -R, \t --reload-config \t\t Reload the config file\n");
    print ("  -rs, \t --reload-css \t\t\t Reload the css file. Location change requires restart\n");
    print ("  -t, \t --toggle-panel \t\t Toggle the notification panel\n");
    print ("  -op, \t --open-panel \t\t\t Opens the notification panel\n");
    print ("  -cp, \t --close-panel \t\t\t Closes the notification panel\n");
    print ("  -d, \t --toggle-dnd \t\t\t Toggle and print the current dnd state\n");
    print ("  -D, \t --get-dnd \t\t\t Print the current dnd state\n");
    print ("  -dn, \t --dnd-on \t\t\t Turn dnd on and print the new dnd state\n");
    print ("  -df, \t --dnd-off \t\t\t Turn dnd off and print the new dnd state\n");
    print ("  -I, \t --get-inhibited \t\t Print if currently inhibited or not\n");
    print ("  -In, \t --get-num-inhibitors \t\t Print number of inhibitors\n");
    print ("  -Ia, \t --inhibitor-add [APP_ID] \t Add an inhibitor\n");
    print ("  -Ir, \t --inhibitor-remove [APP_ID] \t Remove an inhibitor\n");
    print ("  -Ic, \t --inhibitors-clear \t\t Clears all inhibitors\n");
    print ("  -c, \t --count \t\t\t Print the current notification count\n");
    print ("      \t --hide-latest \t\t\t Hides latest notification. Still shown in Control Center\n");
    print ("      \t --close-latest \t\t Closes latest notification\n");
    print ("  -C, \t --close-all \t\t\t Closes all notifications\n");
    print ("  -sw, \t --skip-wait \t\t\t Doesn't wait when swaync hasn't been started\n");
    print ("  -s, \t --subscribe \t\t\t Subscribe to notification add and close events\n");
    print ("  -swb,  --subscribe-waybar \t\t Subscribe to notification add and close events "
           + "with waybar support. Read README for example\n");
}

private void on_subscribe (uint count, bool dnd, bool cc_open, bool inhibited) {
    stdout.printf (
        "{ \"count\": %u, \"dnd\": %s, \"visible\": %s, \"inhibited\": %s }\n",
         count, dnd.to_string (), cc_open.to_string (), inhibited.to_string ());
    stdout.flush ();
}

private void print_subscribe () {
    try {
        SwayncDaemonData data = cc_daemon.get_subscribe_data ();
        on_subscribe (data.count, data.dnd, data.cc_open, data.inhibited);
    } catch (Error e) {
        on_subscribe (0, false, false, false);
    }
}

private void on_subscribe_waybar (uint count, bool dnd, bool cc_open, bool inhibited) {
    string state = (dnd ? "dnd-" : "")
                   + (inhibited ? "inhibited-" : "")
                   + (count > 0 ? "notification" : "none");

    string tooltip = "";
    if (count > 0) {
        tooltip = "%u Notification%s".printf (count, count > 1 ? "s" : "");
    }

    string _class = "\"%s\"".printf (state);
    if (cc_open) {
        _class = "[%s, \"cc-open\"]".printf (_class);
    }

    print (
        "{\"text\": \"%u\", \"alt\": \"%s\", \"tooltip\": \"%s\", \"class\": %s}\n",
        count, state, tooltip, _class);
}

private void print_subscribe_waybar () {
    try {
        SwayncDaemonData data = cc_daemon.get_subscribe_data ();
        on_subscribe_waybar (data.count, data.dnd, data.cc_open, data.inhibited);
    } catch (Error e) {
        on_subscribe_waybar (0, false, false, false);
    }
}

public int command_line (string[] args) {
    bool skip_wait = "--skip-wait" in args || "-sw" in args;

    try {
        if (args.length < 2) {
            print_help (args);
            Process.exit (1);
        }
        switch (args[1]) {
            case "--help":
            case "-h":
                print_help (args);
                break;
            case "--version":
            case "-v":
                stdout.printf ("%s\n", Constants.VERSION);
                break;
            case "--reload-config":
            case "-R":
                cc_daemon.reload_config ();
                break;
            case "--reload-css":
            case "-rs":
                stdout.printf ("CSS reload success: %s\n",
                               cc_daemon.reload_css ().to_string ());
                break;
            case "--count":
            case "-c":
                print (cc_daemon.notification_count ().to_string ());
                break;
            case "--close-latest":
                cc_daemon.hide_latest_notifications (true);
                break;
            case "--hide-latest":
                cc_daemon.hide_latest_notifications (false);
                break;
            case "--close-all":
            case "-C":
                cc_daemon.close_all_notifications ();
                break;
            case "--toggle-panel":
            case "-t":
                cc_daemon.toggle_visibility ();
                break;
            case "--open-panel":
            case "-op":
                cc_daemon.set_visibility (true);
                break;
            case "--close-panel":
            case "-cp":
                cc_daemon.set_visibility (false);
                break;
            case "--toggle-dnd":
            case "-d":
                print (cc_daemon.toggle_dnd ().to_string ());
                break;
            case "--get-dnd":
            case "-D":
                print (cc_daemon.get_dnd ().to_string ());
                break;
            case "--dnd-on":
            case "-dn":
                cc_daemon.set_dnd (true);
                print (cc_daemon.get_dnd ().to_string ());
                break;
            case "--dnd-off":
            case "-df":
                cc_daemon.set_dnd (false);
                print (cc_daemon.get_dnd ().to_string ());
                break;
            case "--get-inhibited":
            case "-I":
                print (cc_daemon.is_inhibited ().to_string ());
                break;
            case "--get-num-inhibitors":
            case "-In":
                print (cc_daemon.number_of_inhibitors ().to_string ());
                break;
            case "--inhibitor-add":
            case "-Ia":
                if (args.length < 3) {
                    stderr.printf ("Application ID needed!");
                    Process.exit (1);
                }
                if (cc_daemon.add_inhibitor (args[2])) {
                    print ("Added inhibitor: \"%s\"", args[2]);
                    break;
                }
                stderr.printf ("Inhibitor: \"%s\" already added!...", args[2]);
                break;
            case "--inhibitor-remove":
            case "-Ir":
                if (args.length < 3) {
                    stderr.printf ("Application ID needed!");
                    Process.exit (1);
                }
                if (cc_daemon.remove_inhibitor (args[2])) {
                    print ("Removed inhibitor: \"%s\"", args[2]);
                    break;
                }
                stderr.printf ("Inhibitor: \"%s\" does not exist!...", args[2]);
                break;
            case "inhibitors-clear":
            case "-Ic":
                if (cc_daemon.clear_inhibitors ()) {
                    print ("Cleared all inhibitors");
                    break;
                }
                print ("No inhibitors to clear...");
                break;
            case "--subscribe":
            case "-s":
                cc_daemon.subscribe_v2.connect (on_subscribe);
                var data = cc_daemon.get_subscribe_data ();
                on_subscribe (data.count,
                              data.dnd,
                              data.cc_open,
                              data.inhibited);
                var loop = new MainLoop ();
                Bus.watch_name (
                    BusType.SESSION,
                    "org.erikreider.swaync.cc",
                    BusNameWatcherFlags.NONE,
                    print_subscribe,
                    print_subscribe);
                loop.run ();
                break;
            case "--subscribe-waybar":
            case "-swb":
                cc_daemon.subscribe_v2.connect (on_subscribe_waybar);
                var loop = new MainLoop ();
                Bus.watch_name (
                    BusType.SESSION,
                    "org.erikreider.swaync.cc",
                    BusNameWatcherFlags.NONE,
                    print_subscribe_waybar,
                    print_subscribe_waybar);
                loop.run ();
                break;
            default:
                print_help (args);
                break;
        }
    } catch (Error e) {
        stderr.printf (e.message + "\n");
        if (skip_wait) Process.exit (1);
        return 1;
    }
    return 0;
}

void print_connection_error () {
    stderr.printf (
        "Could not connect to CC service. Will wait for connection...\n");
}

int try_connect (string[] args) {
    try {
        cc_daemon = Bus.get_proxy_sync (
            BusType.SESSION,
            "org.erikreider.swaync.cc",
            "/org/erikreider/swaync/cc");
        if (command_line (args) == 1) {
            print_connection_error ();
            return 1;
        }
        return 0;
    } catch (Error e) {
        print_connection_error ();
        return 1;
    }
}

public int main (string[] args) {
    if (try_connect (args) == 1) {
        MainLoop loop = new MainLoop ();
        Bus.watch_name (
            BusType.SESSION,
            "org.erikreider.swaync.cc",
            BusNameWatcherFlags.NONE,
            (conn, name, name_owner) => {
            if (try_connect (args) == 0) loop.quit ();
        },
            null);
        loop.run ();
    }
    return 0;
}
