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

const Config = imports.misc.config;

imports.gi.versions.Clutter = Config.LIBMUTTER_API_VERSION;
imports.gi.versions.Gio = '2.0';
imports.gi.versions.GdkPixbuf = '2.0';
imports.gi.versions.Gtk = '3.0';
imports.gi.versions.TelepathyGLib = '0.12';
imports.gi.versions.TelepathyLogger = '0.2';

const { Clutter, GLib, Shell, St } = imports.gi;
const Gettext = imports.gettext;

// We can't import shell JS modules yet, because they may have
// variable initializations, etc, that depend on init() already having
// been run.


// "monkey patch" in some varargs ClutterContainer methods; we need
// to do this per-container class since there is no representation
// of interfaces in Javascript
function _patchContainerClass(containerClass) {
    // This one is a straightforward mapping of the C method
    containerClass.prototype.child_set = function(actor, props) {
        let meta = this.get_child_meta(actor);
        for (let prop in props)
            meta[prop] = props[prop];
    };

    // clutter_container_add() actually is a an add-many-actors
    // method. We conveniently, but somewhat dubiously, take the
    // this opportunity to make it do something more useful.
    containerClass.prototype.add = function(actor, props) {
        this.add_actor(actor);
        if (props)
            this.child_set(actor, props);
    };
}

function _patchLayoutClass(layoutClass, styleProps) {
    if (styleProps)
        layoutClass.prototype.hookup_style = function(container) {
            container.connect('style-changed', () => {
                let node = container.get_theme_node();
                for (let prop in styleProps) {
                    let [found, length] = node.lookup_length(styleProps[prop], false);
                    if (found)
                        this[prop] = length;
                }
            });
        };
    layoutClass.prototype.child_set = function(actor, props) {
        let meta = this.get_child_meta(actor.get_parent(), actor);
        for (let prop in props)
            meta[prop] = props[prop];
    };
}

function _loggingFunc() {
    let fields = {'MESSAGE': [].join.call(arguments, ', ')};
    let domain = "GNOME Shell";

    // If the caller is an extension, add it as metadata
    let extension = imports.misc.extensionUtils.getCurrentExtension();
    if (extension != null) {
        domain = extension.metadata.name;
        fields['GNOME_SHELL_EXTENSION_UUID'] = extension.uuid;
        fields['GNOME_SHELL_EXTENSION_NAME'] = extension.metadata.name;
    }

    GLib.log_structured(domain, GLib.LogLevelFlags.LEVEL_MESSAGE, fields);
}

function init() {
    // Add some bindings to the global JS namespace; (gjs keeps the web
    // browser convention of having that namespace be called 'window'.)
    window.global = Shell.Global.get();

    window.log = _loggingFunc;

    window._ = Gettext.gettext;
    window.C_ = Gettext.pgettext;
    window.ngettext = Gettext.ngettext;
    window.N_ = s => s;

    // Miscellaneous monkeypatching
    _patchContainerClass(St.BoxLayout);

    _patchLayoutClass(Clutter.TableLayout, { row_spacing: 'spacing-rows',
                                             column_spacing: 'spacing-columns' });
    _patchLayoutClass(Clutter.GridLayout, { row_spacing: 'spacing-rows',
                                            column_spacing: 'spacing-columns' });
    _patchLayoutClass(Clutter.BoxLayout, { spacing: 'spacing' });

    Clutter.Actor.prototype.toString = function() {
        return St.describe_actor(this);
    };
    // Deprecation warning for former JS classes turned into an actor subclass
    Object.defineProperty(Clutter.Actor.prototype, 'actor', {
        get() {
            let klass = this.constructor.name;
            let { stack } = new Error();
            log(`Usage of object.actor is deprecated for ${klass}\n${stack}`);
            return this;
        }
    });

    let origToString = Object.prototype.toString;
    Object.prototype.toString = function() {
        let base = origToString.call(this);
        try {
            if ('actor' in this && this.actor instanceof Clutter.Actor)
                return base.replace(/\]$/, ' delegate for ' + this.actor.toString().substring(1));
            else
                return base;
        } catch(e) {
            return base;
        }
    };

    // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=508783
    Date.prototype.toLocaleFormat = function(format) {
        return Shell.util_format_date(format, this.getTime());
    };

    let slowdownEnv = GLib.getenv('GNOME_SHELL_SLOWDOWN_FACTOR');
    if (slowdownEnv) {
        let factor = parseFloat(slowdownEnv);
        if (!isNaN(factor) && factor > 0.0)
            St.set_slow_down_factor(factor);
    }

    // OK, now things are initialized enough that we can import shell JS
    const Format = imports.format;
    const Tweener = imports.ui.tweener;

    Tweener.init();
    String.prototype.format = Format.format;
}