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

const Gio = imports.gi.Gio;
const GioUnix = imports.gi.GioUnix;
const GLib = imports.gi.GLib;
const Signals = imports.signals;

function _log(msg) {
    if (global.settings.get_boolean('debug-screensaver')) {
        global.log(msg);
    }
}

const SystemdLoginManagerIface = `
<node>
  <interface name="org.freedesktop.login1.Manager">
    <method name="Inhibit">
      <arg type="s" direction="in"/>
      <arg type="s" direction="in"/>
      <arg type="s" direction="in"/>
      <arg type="s" direction="in"/>
      <arg type="h" direction="out"/>
    </method>
    <method name="GetSession">
      <arg type="s" direction="in"/>
      <arg type="o" direction="out"/>
    </method>
    <signal name="PrepareForSleep">
      <arg type="b" direction="out"/>
    </signal>
  </interface>
</node>`;

const SystemdLoginManagerProxy = Gio.DBusProxy.makeProxyWrapper(SystemdLoginManagerIface);

const SystemdLoginSessionIface = `
<node>
  <interface name="org.freedesktop.login1.Session">
    <signal name="Lock"/>
    <signal name="Unlock"/>
    <property name="Active" type="b" access="read"/>
    <method name="SetLockedHint">
      <arg type="b" direction="in"/>
    </method>
  </interface>
</node>`;

const SystemdLoginSessionProxy = Gio.DBusProxy.makeProxyWrapper(SystemdLoginSessionIface);

const ConsoleKitManagerIface = `
<node>
  <interface name="org.freedesktop.ConsoleKit.Manager">
    <method name="GetCurrentSession">
      <arg type="o" direction="out"/>
    </method>
  </interface>
</node>`;

const ConsoleKitManagerProxy = Gio.DBusProxy.makeProxyWrapper(ConsoleKitManagerIface);

const ConsoleKitSessionIface = `
<node>
  <interface name="org.freedesktop.ConsoleKit.Session">
    <signal name="Lock"/>
    <signal name="Unlock"/>
    <signal name="ActiveChanged">
      <arg type="b" direction="out"/>
    </signal>
  </interface>
</node>`;

const ConsoleKitSessionProxy = Gio.DBusProxy.makeProxyWrapper(ConsoleKitSessionIface);

function haveSystemd() {
    return GLib.access("/run/systemd/seats", 0) >= 0;
}

var LoginManagerSystemd = class {
    constructor() {
        this._managerProxy = null;
        this._sessionProxy = null;
        this.sessionIsActive = true;

        this._initSession();
    }

    _initSession() {
        _log('LoginManager: Connecting to logind...');

        try {
            this._managerProxy = new SystemdLoginManagerProxy(
                Gio.DBus.system,
                'org.freedesktop.login1',
                '/org/freedesktop/login1'
            );

            this._connectPrepareForSleep();
            this._getCurrentSession();
        } catch (e) {
            global.logError('LoginManager: Failed to connect to logind: ' + e.message);
        }
    }

    _getCurrentSession() {
        let username = GLib.get_user_name();
        let proc = Gio.Subprocess.new(
            ['loginctl', 'show-user', username, '-pDisplay', '--value'],
            Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
        );

        proc.communicate_utf8_async(null, null, (proc, result) => {
            try {
                let [, stdout] = proc.communicate_utf8_finish(result);

                if (!proc.get_successful()) {
                    throw new Error('loginctl command failed');
                }

                let sessionId = stdout.trim();
                if (!sessionId) {
                    throw new Error('No session ID found');
                }

                _log(`LoginManager: Found session ID: ${sessionId}`);

                this._managerProxy.GetSessionRemote(sessionId, (result, error) => {
                    if (error) {
                        global.logError('LoginManager: Failed to get session path: ' + error);
                        return;
                    }

                    let [sessionPath] = result;
                    _log(`LoginManager: Got session path: ${sessionPath}`);

                    this._connectToSession(sessionPath);
                });
            } catch (e) {
                global.logError('LoginManager: Error getting logind session: ' + e.message);
            }
        });
    }

    _connectToSession(sessionPath) {
        try {
            this._sessionProxy = new SystemdLoginSessionProxy(
                Gio.DBus.system,
                'org.freedesktop.login1',
                sessionPath
            );

            _log('LoginManager: Successfully connected to logind session');

            this._sessionProxy.connectSignal('Lock', () => {
                _log('LoginManager: Received Lock signal from logind, emitting lock');
                this.emit('lock');
            });

            this._sessionProxy.connectSignal('Unlock', () => {
                _log('LoginManager: Received Unlock signal from logind, emitting unlock');
                this.emit('unlock');
            });

            this._sessionProxy.connect('g-properties-changed', (proxy, changed, invalidated) => {
                if ('Active' in changed.deep_unpack()) {
                    let active = this._sessionProxy.Active;
                    this.sessionIsActive = active;
                    _log(`LoginManager: Session Active property changed: ${active}`);
                    this.emit('active-changed', active);
                }
            });

            this.sessionIsActive = this._sessionProxy.Active;
            this.emit('session-ready');
        } catch (e) {
            global.logError('LoginManager: Failed to connect to logind session: ' + e.message);
        }
    }

    _connectPrepareForSleep() {
        if (!this._managerProxy)
            return;

        this._managerProxy.connectSignal('PrepareForSleep', (proxy, sender, [aboutToSuspend]) => {
            _log(`LoginManager: PrepareForSleep signal received (aboutToSuspend=${aboutToSuspend})`);
            this.emit('prepare-for-sleep', aboutToSuspend);
        });
    }

    inhibit(reason, callback) {
        if (!this._managerProxy) {
            _log('LoginManager: inhibit() called but no manager proxy');
            callback(null);
            return;
        }

        _log(`LoginManager: Requesting sleep inhibitor: "${reason}"`);

        let inVariant = GLib.Variant.new('(ssss)',
            ['sleep', 'cinnamon-screensaver', reason, 'delay']);

        this._managerProxy.call_with_unix_fd_list(
            'Inhibit', inVariant, 0, -1, null, null,
            (proxy, result) => {
                try {
                    let [outVariant_, fdList] = proxy.call_with_unix_fd_list_finish(result);
                    let fd = fdList.steal_fds()[0];
                    _log(`LoginManager: Sleep inhibitor acquired (fd=${fd})`);
                    callback(new GioUnix.InputStream({ fd }));
                } catch (e) {
                    global.logError('LoginManager: Error getting inhibitor: ' + e.message);
                    callback(null);
                }
            });
    }

    setLockedHint(locked) {
        if (!this._sessionProxy)
            return;

        this._sessionProxy.SetLockedHintRemote(locked, (result, error) => {
            if (error)
                global.logError('LoginManager: SetLockedHint failed: ' + error);
        });
    }
};
Signals.addSignalMethods(LoginManagerSystemd.prototype);

var LoginManagerConsoleKit = class {
    constructor() {
        this._managerProxy = null;
        this._sessionProxy = null;
        this.sessionIsActive = true;

        this._initSession();
    }

    _initSession() {
        _log('LoginManager: Connecting to ConsoleKit...');

        try {
            this._managerProxy = new ConsoleKitManagerProxy(
                Gio.DBus.system,
                'org.freedesktop.ConsoleKit',
                '/org/freedesktop/ConsoleKit/Manager'
            );

            this._managerProxy.GetCurrentSessionRemote((result, error) => {
                if (error) {
                    global.logError('LoginManager: Failed to get ConsoleKit session: ' + error);
                    global.logError('LoginManager: Automatic unlocking from greeter will not work');
                    return;
                }

                let [sessionPath] = result;
                _log(`LoginManager: Got ConsoleKit session path: ${sessionPath}`);

                this._connectToSession(sessionPath);
            });
        } catch (e) {
            global.logError('LoginManager: Failed to connect to ConsoleKit: ' + e.message);
            global.logError('LoginManager: Automatic unlocking from greeter will not work');
        }
    }

    _connectToSession(sessionPath) {
        try {
            this._sessionProxy = new ConsoleKitSessionProxy(
                Gio.DBus.system,
                'org.freedesktop.ConsoleKit',
                sessionPath
            );

            _log('LoginManager: Successfully connected to ConsoleKit session');

            this._sessionProxy.connectSignal('Lock', () => {
                _log('LoginManager: Received Lock signal from ConsoleKit, emitting lock');
                this.emit('lock');
            });

            this._sessionProxy.connectSignal('Unlock', () => {
                _log('LoginManager: Received Unlock signal from ConsoleKit, emitting unlock');
                this.emit('unlock');
            });

            this._sessionProxy.connectSignal('ActiveChanged', (proxy, sender, [active]) => {
                this.sessionIsActive = active;
                _log(`LoginManager: ConsoleKit ActiveChanged: ${active}`);
                this.emit('active-changed', active);
            });

            this.emit('session-ready');
        } catch (e) {
            global.logError('LoginManager: Failed to connect to ConsoleKit session: ' + e.message);
            global.logError('LoginManager: Automatic unlocking from greeter will not work');
        }
    }

    inhibit(reason, callback) {
        // ConsoleKit doesn't have inhibitors
        callback(null);
    }

    setLockedHint(_locked) {
        // ConsoleKit doesn't have SetLockedHint
    }
};
Signals.addSignalMethods(LoginManagerConsoleKit.prototype);

let _loginManager = null;

function getLoginManager() {
    if (_loginManager == null) {
        if (haveSystemd()) {
            _loginManager = new LoginManagerSystemd();
        } else {
            _loginManager = new LoginManagerConsoleKit();
        }
    }

    return _loginManager;
}
