// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Config = imports.misc.config;
const Extension = imports.ui.extension;
const KeyboardManager = imports.ui.keyboardManager;
const Main = imports.ui.main;
const AppletManager = imports.ui.appletManager;
const DeskletManager = imports.ui.deskletManager;
const ExtensionSystem = imports.ui.extensionSystem;
const SearchProviderManager = imports.ui.searchProviderManager;
const Util = imports.misc.util;

const CinnamonIface =
    '<node> \
        <interface name="org.Cinnamon"> \
            <method name="Eval"> \
                <arg type="s" direction="in" name="script" /> \
                <arg type="b" direction="out" name="success" /> \
                <arg type="s" direction="out" name="result" /> \
            </method> \
            <method name="ShowOSD"> \
                <arg type="a{sv}" direction="in" name="params"/> \
            </method> \
            <method name="highlightXlet"> \
                <arg type="s" direction="in" /> \
                <arg type="s" direction="in" /> \
                <arg type="b" direction="in" /> \
            </method> \
            <method name="highlightPanel"> \
                <arg type="i" direction="in" /> \
                <arg type="b" direction="in" /> \
            </method> \
            <method name="addPanelQuery"> \
            </method> \
            <method name="destroyDummyPanels"> \
            </method> \
            <method name="activateCallback"> \
                <arg type="s" direction="in" /> \
                <arg type="s" direction="in" /> \
                <arg type="s" direction="in" /> \
            </method> \
            <method name="updateSetting"> \
                <arg type="s" direction="in" /> \
                <arg type="s" direction="in" /> \
                <arg type="s" direction="in" /> \
                <arg type="s" direction="in" /> \
            </method> \
            <method name="induceSegfault" /> \
            <method name="leakMemory"> \
                <arg type="i" direction="in" name="mb" /> \
            </method> \
            <method name="switchWorkspaceRight" /> \
            <method name="switchWorkspaceLeft" /> \
            <method name="switchWorkspaceUp" /> \
            <method name="switchWorkspaceDown" /> \
            <method name="JumpToNewWorkspace" /> \
            <method name="RemoveCurrentWorkspace" /> \
            <method name="ShowExpo" /> \
            <method name="ShowOverview" /> \
            <method name="GetRunningXletUUIDs"> \
                <arg type="s" direction="in" /> \
                <arg type="as" direction="out" /> \
            </method> \
            <method name="ReloadXlet"> \
                <arg type="s" direction="in" name="uuid" /> \
                <arg type="s" direction="in" name="type" /> \
            </method> \
            <property name="OverviewActive" type="b" access="readwrite" /> \
            <property name="ExpoActive" type="b" access="readwrite" /> \
            <property name="CinnamonVersion" type="s" access="read" /> \
            <signal name="XletAddedComplete"> \
                <arg type="b" direction="out" /> \
                <arg type="s" direction="out" /> \
            </signal> \
            <method name="PushSubprocessResult"> \
                <arg type="i" direction="in" name="process_id" /> \
                <arg type="s" direction="in" name="result" /> \
                <arg type="b" direction="in" name="success" /> \
            </method> \
            <method name="ToggleKeyboard"/> \
            <method name="GetMonitors"> \
                <arg type="ai" direction="out" name="monitors" /> \
            </method> \
            <method name="GetMonitorWorkRect"> \
                <arg type="i" direction="in" name="monitor" /> \
                <arg type="ai" direction="out" name="rect" /> \
            </method> \
            <signal name="MonitorsChanged"/> \
            <method name="GetRunState"> \
                <arg type="i" direction="out" name="state" /> \
            </method> \
            <method name="RestartCinnamon"> \
                <arg type="b" direction="in" name="show_osd" /> \
            </method> \
            <method name="ReloadTheme"/> \
            <method name="DismissInternalModals"/> \
            <signal name="RunStateChanged"/> \
            <signal name="XletsLoadedComplete"/> \
            <property name="AnimationsEnabled" type="b" access="read" /> \
            <method name="ShowMonitorLabels"> \
                <arg type="a{sv}" direction="in" name="params"/> \
            </method> \
            <method name="HideMonitorLabels"/> \
            <method name="ShowEndSessionDialog"> \
                <arg type="i" direction="in" name="mode"/> \
            </method> \
            <method name="CloseEndSessionDialog"/> \
            <method name="GetInputSources"> \
                <arg type="a(ssisssssssib)" direction="out" name="layouts"/> \
            </method> \
            <method name="ActivateInputSourceIndex"> \
                <arg type="i" direction="in" name="index"/> \
            </method> \
            <signal name="CurrentInputSourceChanged"> \
                <arg type="s" direction="out" /> \
            </signal> \
            <signal name="InputSourcesChanged"/> \
        </interface> \
    </node>';

var CinnamonDBus = class {
    constructor() {
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(CinnamonIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/Cinnamon');

        /* Although this signal comes from muffin, it is actually initiated by the
         * layoutManager.Chrome.updateRegions method.  Workspace code in muffin filters
         * out chrome updates that don't actually change the workarea before emitting this
         * signal, which is desirable. */
        global.display.connect("workareas-changed", ()=> this.EmitMonitorsChanged());
    }

    /**
     * Eval:
     * @code: A string containing JavaScript code
     *
     * This function executes arbitrary code in the main
     * loop, and returns a boolean success and
     * JSON representation of the object as a string.
     *
     * If evaluation completes without throwing an exception,
     * then the return value will be [true, JSON.stringify(result)].
     * If evaluation fails, then the return value will be
     * [false, JSON.stringify(exception)];
     *
     */
    Eval(code) {
        let returnValue;
        let success;
        try {
            returnValue = JSON.stringify(eval(code));
            // A hack; DBus doesn't have null/undefined
            if (returnValue == undefined)
                returnValue = '';
            success = true;
        } catch (e) {
            returnValue = JSON.stringify(e);
            success = false;
        }
        return [success, returnValue];
    }

    ShowOSD(params) {
        for (let param in params)
            params[param] = params[param].deep_unpack();

        let monitorIndex = -1;
        if (params.maybeGet('monitor_x') >= 0) {
            let x = params['monitor_x'];
            let y = params['monitor_y'];
            monitorIndex = Main.layoutManager.findMonitorIndexAt(++x, ++y);
        }

        let icon = null;
        if (params['icon'])
            icon = Gio.Icon.new_for_string(params['icon']);

        Main.osdWindowManager.show(monitorIndex, icon, params['label'], params['level']);
    }

    get OverviewActive() {
        return Main.overview.visible;
    }

    set OverviewActive(visible) {
        if (visible)
            Main.overview.show();
        else
            Main.overview.hide();
    }

    get ExpoActive() {
        return Main.expo.visible;
    }

    set ExpoActive(visible) {
        if (visible)
            Main.expo.show();
        else
            Main.expo.hide();
    }

    _getXletObject(uuid, instance_id) {
        var obj = null;

        obj = AppletManager.get_object_for_uuid(uuid, instance_id);

        if (!obj) {
            obj = DeskletManager.get_object_for_uuid(uuid, instance_id);
        }

        if (!obj) {
            obj = ExtensionSystem.get_object_for_uuid(uuid);
        }

        return obj;
    }

    EmitXletAddedComplete(success, uuid, name) {
        this._dbusImpl.emit_signal('XletAddedComplete', GLib.Variant.new('(bs)', [success, uuid]));
    }

    GetRunningXletUUIDs(type) {
        let list = null;
        let res = [];

        if (type === 'applet') {
            for (let i = 0; i < AppletManager.definitions.length; i++) {
                res.push(AppletManager.definitions[i].uuid);
            }
        } else if (type === 'desklet') {
            for (let i = 0; i < DeskletManager.definitions.length; i++) {
                res.push(DeskletManager.definitions[i].uuid);
            }
        } else {
            for (let i = 0; i < ExtensionSystem.runningExtensions.length; i++) {
                res.push(ExtensionSystem.runningExtensions[i]);
            }
        }

        return res;
    }

    ReloadXlet(uuid, type) {
        Extension.reloadExtension(uuid, Extension.Type[type]);
    }

    highlightXlet(uuid, instance_id, highlight) {
        let obj = this._getXletObject(uuid, instance_id);
        if (obj && obj.highlight) obj.highlight(highlight);
    }

    highlightPanel(id, highlight) {
        if (Main.panelManager.panels[id])
            Main.panelManager.panels[id].highlight(highlight);
    }

    addPanelQuery() {
        Main.panelManager.addPanelQuery();
    }

    destroyDummyPanels() {
        Main.panelManager._destroyDummyPanels();
    }

    activateCallback(callback, uuid, instance_id) {
        let obj = this._getXletObject(uuid, instance_id);
        let cb = obj[callback].bind(obj);
        cb();
    }

    updateSetting(uuid, instance_id, key, payload) {
        if (!Main.settingsManager.uuids[uuid]) {
            global.logWarning(
                `[CinnamonDBus] [${uuid}] Unable to find UUID from SettingsManager - ` +
                'this is likely due to configuring settings from a removed xlet.'
            );
            return;
        }
        Main.settingsManager.uuids[uuid][instance_id].remoteUpdate(key, payload);
    }

    induceSegfault() {
        global.segfault();
    }

    leakMemory(mb) {
        global.alloc_leak(mb);
    }

    switchWorkspaceLeft() {
        Main.wm.actionMoveWorkspaceLeft();
    }

    switchWorkspaceRight() {
        Main.wm.actionMoveWorkspaceRight();
    }

    switchWorkspaceUp() {
        Main.overview.toggle();
    }

    switchWorkspaceDown() {
        Main.expo.toggle();
    }

    JumpToNewWorkspace() {
        Main._addWorkspace();
        let num = global.workspace_manager.get_n_workspaces();
        if (global.workspace_manager.get_workspace_by_index(num - 1) != null) {
            global.workspace_manager.get_workspace_by_index(num - 1).activate(global.get_current_time());
        }
    }

    RemoveCurrentWorkspace() {
        let index = global.workspace_manager.get_active_workspace_index();
        if (global.workspace_manager.get_workspace_by_index(index) != null) {
            Main._removeWorkspace(global.workspace_manager.get_workspace_by_index(index));
        }
    }

    ShowExpo() {
        Main.expo.toggle();
    }

    ShowOverview() {
        Main.overview.toggle();
    }

    PushSubprocessResult(process_id, result, success) {
        if (Util.subprocess_callbacks[process_id]) {
            if (success)
                Util.subprocess_callbacks[process_id](result);
            delete Util.subprocess_callbacks[process_id];
        }
    }

    ToggleKeyboard() {
        Main.toggleKeyboard();
    }

    GetMonitors() {
        let monitors = [];

        try {
            for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
                let current = Main.layoutManager.monitors[i];

                monitors.push(current.index);
            }
        } catch (e) {
            log(e.message);
            /* Something broke, trigger a GDBus.Error back to the caller instead of returning bad things */
            monitors = [];
        }

        if (monitors.length == 0) {
            throw new Error("GetMonitors: no valid monitors");
        }

        return monitors;
    }

    GetMonitorWorkRect(index) {
        let n_mons = global.display.get_n_monitors();

        if ((index < 0) || index > (n_mons - 1)) {
            throw new Error("GetMonitorWorkRect: invalid monitor index: " + index + ".  Must be 0 to " + (n_mons - 1));
        }

        let rect = global.workspace_manager.get_active_workspace().get_work_area_for_monitor(index);

        return [rect.x, rect.y, rect.width, rect.height];
    }

    GetRunState() {
        return Main.runState;
    }

    RestartCinnamon(showOsd) {
        Main.restartCinnamon(showOsd);
    }

    ReloadTheme() {
        Main.themeManager._changeTheme()
    }

    DismissInternalModals() {
        Main.dismissInternalModals();
    }

    EmitRunStateChanged() {
        this._dbusImpl.emit_signal('RunStateChanged', null);
    }

    EmitMonitorsChanged() {
        this._dbusImpl.emit_signal('MonitorsChanged', null);
    }

    EmitXletsLoadedComplete() {
        this._dbusImpl.emit_signal('XletsLoadedComplete', null);
    }

    get AnimationsEnabled() {
        return Main.animations_enabled;
    }

    notifyAnimationsEnabled() {
        let variant = new GLib.Variant('b', Main.animations_enabled);
        this._dbusImpl.emit_property_changed('AnimationsEnabled', variant);
    }

    ShowMonitorLabelsAsync(monitor_info, invocation) {
        if (Main.monitorLabeler != null) {
            Main.monitorLabeler.show(monitor_info[0], invocation.get_sender());
        }
    }

    HideMonitorLabelsAsync(tuple, invocation) {
        if (Main.monitorLabeler != null) {
            Main.monitorLabeler.hide(invocation.get_sender());
        }
    }

    ShowEndSessionDialog(mode) {
        GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
            Main.showEndSessionDialog(mode);
            return GLib.SOURCE_REMOVE;
        });
    }

    CloseEndSessionDialog() {
        Main.closeEndSessionDialog();
    }

    GetInputSources() {
        const is_mgr = KeyboardManager.getInputSourceManager();
        const sources = is_mgr._inputSources;
        let ret = []

        for (let idx in sources) {
            const source = sources[idx];
            // global.log(source.preferences);
            ret.push([
                source.type,
                source.id,
                source.index,
                source.displayName,
                source._shortName,
                source.flagName,
                source.xkbId,
                source.xkbLayout,
                source.variant,
                source.preferences,
                source.dupeId,
                source === is_mgr.currentSource
            ]);
        }
        return ret;
    }

    ActivateInputSourceIndex(index) {
        const is_mgr = KeyboardManager.getInputSourceManager();
        is_mgr.activateInputSourceIndex(index);
    }

    EmitCurrentInputSourceChanged(id) {
        this._dbusImpl.emit_signal('CurrentInputSourceChanged', GLib.Variant.new('(s)', [id]));
    }

    EmitInputSourcesChanged() {
        this._dbusImpl.emit_signal('InputSourcesChanged', null);
    }

    get CinnamonVersion() {
        return Config.PACKAGE_VERSION;
    }
};
