const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;

const DND = imports.ui.dnd;
const Hash = imports.misc.hash;
const Lang = imports.lang;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;

const SLEClassicExt = imports.ui.SLEClassicExt;

const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const SLETweaks = Me.imports.SLETweaks;

const ICON_TEXTURE_SIZE = 24;
const DND_ACTIVATE_TIMEOUT = 500;

const GroupingMode = {
    NEVER: 0,
    AUTO: 1,
    ALWAYS: 2
};


function _minimizeOrActivateWindow(window) {
        let focusWindow = global.display.focus_window;
        if (focusWindow == window ||
            focusWindow && focusWindow.get_transient_for() == window)
            window.minimize();
        else
            window.activate(global.get_current_time());
}

function _openMenu(menu) {
    menu.open();

    let event = Clutter.get_current_event();
    if (event && event.type() == Clutter.EventType.KEY_RELEASE)
        menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
}


const WindowContextMenu = new Lang.Class({
    Name: 'WindowContextMenu',
    Extends: PopupMenu.PopupMenu,

    _init: function(source, metaWindow) {
        this.parent(source, 0.5, St.Side.BOTTOM);

        this._metaWindow = metaWindow;

        this._minimizeItem = new PopupMenu.PopupMenuItem('');
        this._minimizeItem.connect('activate', Lang.bind(this, function() {
            if (this._metaWindow.minimized)
                this._metaWindow.unminimize();
            else
                this._metaWindow.minimize();
        }));
        this.addMenuItem(this._minimizeItem);

        this._notifyMinimizedId =
            this._metaWindow.connect('notify::minimized',
                                     Lang.bind(this, this._updateMinimizeItem));
        this._updateMinimizeItem();

        this._maximizeItem = new PopupMenu.PopupMenuItem('');
        this._maximizeItem.connect('activate', Lang.bind(this, function() {
            if (this._metaWindow.maximized_vertically &&
                this._metaWindow.maximized_horizontally)
                this._metaWindow.unmaximize(Meta.MaximizeFlags.HORIZONTAL |
                                            Meta.MaximizeFlags.VERTICAL);
            else
                this._metaWindow.maximize(Meta.MaximizeFlags.HORIZONTAL |
                                          Meta.MaximizeFlags.VERTICAL);
        }));
        this.addMenuItem(this._maximizeItem);

        this._notifyMaximizedHId =
            this._metaWindow.connect('notify::maximized-horizontally',
                                     Lang.bind(this, this._updateMaximizeItem));
        this._notifyMaximizedVId =
            this._metaWindow.connect('notify::maximized-vertically',
                                     Lang.bind(this, this._updateMaximizeItem));
        this._updateMaximizeItem();

        let item = new PopupMenu.PopupMenuItem(_("Close"));
        item.connect('activate', Lang.bind(this, function() {
            this._metaWindow.delete(global.get_current_time());
        }));
        this.addMenuItem(item);

        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
    },

    _updateMinimizeItem: function() {
        this._minimizeItem.label.text = this._metaWindow.minimized ? _("Unminimize")
                                                                   : _("Minimize");
    },

    _updateMaximizeItem: function() {
        let maximized = this._metaWindow.maximized_vertically &&
                        this._metaWindow.maximized_horizontally;
        this._maximizeItem.label.text = maximized ? _("Unmaximize")
                                                  : _("Maximize");
    },

    _onDestroy: function() {
        this._metaWindow.disconnect(this._notifyMinimizedId);
        this._metaWindow.disconnect(this._notifyMaximizedHId);
        this._metaWindow.disconnect(this._notifyMaximizedVId);
    }
});

const WindowTitle = new Lang.Class({
    Name: 'WindowTitle',

    _init: function(metaWindow) {
        this._metaWindow = metaWindow;
        this.actor = new St.BoxLayout();

        let app = Shell.WindowTracker.get_default().get_window_app(metaWindow);
        this._icon = new St.Bin({ style_class: 'window-button-icon',
                                  child: app.create_icon_texture(ICON_TEXTURE_SIZE) });
        this.actor.add(this._icon);
        this._label = new St.Label();
        this.actor.add(this._label);

        this._textureCache = St.TextureCache.get_default();
        this._iconThemeChangedId =
            this._textureCache.connect('icon-theme-changed', Lang.bind(this,
                function() {
                    this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE);
                }));
        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));

        this._notifyTitleId =
            this._metaWindow.connect('notify::title',
                                    Lang.bind(this, this._updateTitle));
        this._notifyMinimizedId =
            this._metaWindow.connect('notify::minimized',
                                    Lang.bind(this, this._minimizedChanged));
        this._minimizedChanged();
    },

    _minimizedChanged: function() {
        this._icon.opacity = this._metaWindow.minimized ? 128 : 255;
        this._updateTitle();
    },

    _updateTitle: function() {
        if (this._metaWindow.minimized)
            this._label.text = '[%s]'.format(this._metaWindow.title);
        else
            this._label.text = this._metaWindow.title;
    },

    _onDestroy: function() {
        this._textureCache.disconnect(this._iconThemeChangedId);
        this._metaWindow.disconnect(this._notifyTitleId);
        this._metaWindow.disconnect(this._notifyMinimizedId);
    }
});


const WindowButton = new Lang.Class({
    Name: 'WindowButton',

    _init: function(metaWindow) {
        this.metaWindow = metaWindow;

        this._windowTitle = new WindowTitle(this.metaWindow);
        this.actor = new St.Button({ style_class: 'window-button',
                                     x_fill: true,
                                     can_focus: true,
                                     button_mask: St.ButtonMask.ONE |
                                                  St.ButtonMask.THREE,
                                     child: this._windowTitle.actor });
        this.actor._delegate = this;

        this._menuManager = new PopupMenu.PopupMenuManager(this);
        this._contextMenu = new WindowContextMenu(this.actor, this.metaWindow);
        this._contextMenu.actor.hide();
        this._menuManager.addMenu(this._contextMenu);
        Main.uiGroup.add_actor(this._contextMenu.actor);

        this.actor.connect('allocation-changed',
                           Lang.bind(this, this._updateIconGeometry));
        this.actor.connect('clicked', Lang.bind(this, this._onClicked));
        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
        this.actor.connect('popup-menu', Lang.bind(this, this._onPopupMenu));

        this._switchWorkspaceId =
            global.window_manager.connect('switch-workspace',
                                          Lang.bind(this, this._updateVisibility));
        this._updateVisibility();

        this._notifyFocusId =
            global.display.connect('notify::focus-window',
                                   Lang.bind(this, this._updateStyle));
        this._updateStyle();
    },

    _onClicked: function(actor, button) {
        if (this._contextMenu.isOpen) {
            this._contextMenu.close();
            return;
        }

        if (button == 1)
            _minimizeOrActivateWindow(this.metaWindow);
        else
            _openMenu(this._contextMenu);
    },

    _onPopupMenu: function(actor) {
        if (this._contextMenu.isOpen)
            return;
        _openMenu(this._contextMenu);
    },

    _updateStyle: function() {
        if (this.metaWindow.minimized)
            this.actor.add_style_class_name('minimized');
        else
            this.actor.remove_style_class_name('minimized');

        if (global.display.focus_window == this.metaWindow)
            this.actor.add_style_class_name('focused');
        else
            this.actor.remove_style_class_name('focused');
    },

    _updateVisibility: function() {
        let workspace = global.screen.get_active_workspace();
        this.actor.visible = this.metaWindow.located_on_workspace(workspace);
    },

    _updateIconGeometry: function() {
        let rect = new Meta.Rectangle();

        [rect.x, rect.y] = this.actor.get_transformed_position();
        [rect.width, rect.height] = this.actor.get_transformed_size();

        this.metaWindow.set_icon_geometry(rect);
    },

    _onDestroy: function() {
        global.window_manager.disconnect(this._switchWorkspaceId);
        global.display.disconnect(this._notifyFocusId);
        this._contextMenu.actor.destroy();
    }
});


const AppContextMenu = new Lang.Class({
    Name: 'AppContextMenu',
    Extends: PopupMenu.PopupMenu,

    _init: function(source, app) {
        this.parent(source, 0.5, St.Side.BOTTOM);

        this._app = app;

        this._minimizeItem = new PopupMenu.PopupMenuItem(_("Minimize all"));
        this._minimizeItem.connect('activate', Lang.bind(this, function() {
            this._app.get_windows().forEach(function(w) {
                w.minimize();
            });
        }));
        this.addMenuItem(this._minimizeItem);

        this._unminimizeItem = new PopupMenu.PopupMenuItem(_("Unminimize all"));
        this._unminimizeItem.connect('activate', Lang.bind(this, function() {
            this._app.get_windows().forEach(function(w) {
                w.unminimize();
            });
        }));
        this.addMenuItem(this._unminimizeItem);

        this._maximizeItem = new PopupMenu.PopupMenuItem(_("Maximize all"));
        this._maximizeItem.connect('activate', Lang.bind(this, function() {
            this._app.get_windows().forEach(function(w) {
                w.maximize(Meta.MaximizeFlags.HORIZONTAL |
                           Meta.MaximizeFlags.VERTICAL);
            });
        }));
        this.addMenuItem(this._maximizeItem);

        this._unmaximizeItem = new PopupMenu.PopupMenuItem(_("Unmaximize all"));
        this._unmaximizeItem.connect('activate', Lang.bind(this, function() {
            this._app.get_windows().forEach(function(w) {
                w.unmaximize(Meta.MaximizeFlags.HORIZONTAL |
                             Meta.MaximizeFlags.VERTICAL);
            });
        }));
        this.addMenuItem(this._unmaximizeItem);

        let item = new PopupMenu.PopupMenuItem(_("Close all"));
        item.connect('activate', Lang.bind(this, function() {
            this._app.get_windows().forEach(function(w) {
                w.delete(global.get_current_time());
            });
        }));
        this.addMenuItem(item);
    },

    open: function(animate) {
        let windows = this._app.get_windows();
        this._minimizeItem.actor.visible = windows.some(function(w) {
            return !w.minimized;
        });
        this._unminimizeItem.actor.visible = windows.some(function(w) {
            return w.minimized;
        });
        this._maximizeItem.actor.visible = windows.some(function(w) {
            return !(w.maximized_horizontally && w.maximized_vertically);
        });
        this._unmaximizeItem.actor.visible = windows.some(function(w) {
            return w.maximized_horizontally && w.maximized_vertically;
        });

        this.parent(animate);
    }
});

const AppButton = new Lang.Class({
    Name: 'AppButton',

    _init: function(app) {
        this.app = app;

        let stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
        this.actor = new St.Button({ style_class: 'window-button',
                                     x_fill: true,
                                     can_focus: true,
                                     button_mask: St.ButtonMask.ONE |
                                                  St.ButtonMask.THREE,
                                     child: stack });
        this.actor._delegate = this;

        this.actor.connect('allocation-changed',
                           Lang.bind(this, this._updateIconGeometry));

        this._singleWindowTitle = new St.Bin({ x_expand: true,
                                               x_align: St.Align.START });
        stack.add_actor(this._singleWindowTitle);

        this._multiWindowTitle = new St.BoxLayout({ x_expand: true });
        stack.add_actor(this._multiWindowTitle);

        this._icon = new St.Bin({ style_class: 'window-button-icon',
                                  child: app.create_icon_texture(ICON_TEXTURE_SIZE) });
        this._multiWindowTitle.add(this._icon);
        this._multiWindowTitle.add(new St.Label({ text: app.get_name() }));

        this._menuManager = new PopupMenu.PopupMenuManager(this);
        this._menu = new PopupMenu.PopupMenu(this.actor, 0.5, St.Side.BOTTOM);
        this._menu.actor.hide();
        this._menu.connect('activate', Lang.bind(this, this._onMenuActivate));
        this._menuManager.addMenu(this._menu);
        Main.uiGroup.add_actor(this._menu.actor);

        this._contextMenuManager = new PopupMenu.PopupMenuManager(this);
        this._appContextMenu = new AppContextMenu(this.actor, this.app);
        this._appContextMenu.actor.hide();
        this._contextMenuManager.addMenu(this._appContextMenu);
        Main.uiGroup.add_actor(this._appContextMenu.actor);

        this._textureCache = St.TextureCache.get_default();
        this._iconThemeChangedId =
            this._textureCache.connect('icon-theme-changed', Lang.bind(this,
                function() {
                    this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE);
                }));
        this.actor.connect('clicked', Lang.bind(this, this._onClicked));
        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
        this.actor.connect('popup-menu', Lang.bind(this, this._onPopupMenu));

        this._switchWorkspaceId =
            global.window_manager.connect('switch-workspace',
                                          Lang.bind(this, this._updateVisibility));
        this._updateVisibility();

        this._windowsChangedId =
            this.app.connect('windows-changed',
                             Lang.bind(this, this._windowsChanged));
        this._windowsChanged();

        this._windowTracker = Shell.WindowTracker.get_default();
        this._notifyFocusId =
            this._windowTracker.connect('notify::focus-app',
                                        Lang.bind(this, this._updateStyle));
        this._updateStyle();
    },

    _updateVisibility: function() {
        let workspace = global.screen.get_active_workspace();
        this.actor.visible = this.app.is_on_workspace(workspace);
    },

    _updateStyle: function() {
        if (this._windowTracker.focus_app == this.app)
            this.actor.add_style_class_name('focused');
        else
            this.actor.remove_style_class_name('focused');
    },

    _updateIconGeometry: function() {
        let rect = new Meta.Rectangle();

        [rect.x, rect.y] = this.actor.get_transformed_position();
        [rect.width, rect.height] = this.actor.get_transformed_size();

        let windows = this.app.get_windows();
        windows.forEach(function(w) {
            w.set_icon_geometry(rect);
        });
    },


    _getWindowList: function() {
        let workspace = global.screen.get_active_workspace();
        return this.app.get_windows().filter(function(win) {
            return win.located_on_workspace(workspace);
        });
    },

    _windowsChanged: function() {
        let windows = this._getWindowList();
        this._singleWindowTitle.visible = windows.length == 1;
        this._multiWindowTitle.visible = !this._singleWindowTitle.visible;

        if (this._singleWindowTitle.visible) {
            if (!this._windowTitle) {
                this.metaWindow = windows[0];
                this._windowTitle = new WindowTitle(this.metaWindow);
                this._singleWindowTitle.child = this._windowTitle.actor;
                this._windowContextMenu = new WindowContextMenu(this.actor, this.metaWindow);
                Main.uiGroup.add_actor(this._windowContextMenu.actor);
                this._windowContextMenu.actor.hide();
                this._contextMenuManager.addMenu(this._windowContextMenu);
            }
            this._contextMenu = this._windowContextMenu;
        } else {
            if (this._windowTitle) {
                this.metaWindow = null;
                this._singleWindowTitle.child = null;
                this._windowTitle = null;
                this._windowContextMenu.actor.destroy();
                this._windowContextMenu = null;
            }
            this._contextMenu = this._appContextMenu;
        }

    },

    _onClicked: function(actor, button) {
        let menuWasOpen = this._menu.isOpen;
        if (menuWasOpen)
            this._menu.close();

        let contextMenuWasOpen = this._contextMenu.isOpen;
        if (contextMenuWasOpen)
            this._contextMenu.close();

        if (button == 1) {
            if (menuWasOpen)
                return;

            let windows = this._getWindowList();
            if (windows.length == 1) {
                if (contextMenuWasOpen)
                    return;
                _minimizeOrActivateWindow(windows[0]);
            } else {
                this._menu.removeAll();

                for (let i = 0; i < windows.length; i++) {
                    let windowTitle = new WindowTitle(windows[i]);
                    let item = new PopupMenu.PopupBaseMenuItem();
                    item.actor.add_actor(windowTitle.actor);
                    item._window = windows[i];
                    this._menu.addMenuItem(item);
                }
                _openMenu(this._menu);
            }
        } else {
            if (contextMenuWasOpen)
                return;
            _openMenu(this._contextMenu);
        }
    },

    _onPopupMenu: function(actor) {
        if (this._menu.isOpen || this._contextMenu.isOpen)
            return;
        _openMenu(this._contextMenu);
    },


    _onMenuActivate: function(menu, child) {
        child._window.activate(global.get_current_time());
    },

    _onDestroy: function() {
        this._textureCache.disconnect(this._iconThemeChangedId);
        global.window_manager.disconnect(this._switchWorkspaceId);
        this._windowTracker.disconnect(this._notifyFocusId);
        this.app.disconnect(this._windowsChangedId);
        this._menu.actor.destroy();
    }
});


const TrayButton = new Lang.Class({
    Name: 'TrayButton',
    Extends: PanelMenu.Button,

    _init: function() {
        this.parent(0.0, _("Message Indicator"), true);
        this.actor.add_style_class_name('summary-source-counter');

        this._counterLabel = new St.Label({x_align: Clutter.ActorAlign.CENTER,
                                           x_expand: true,
                                           y_align: Clutter.ActorAlign.CENTER,
                                           y_expand: true});
        this.actor.add_actor(this._counterLabel);

        // disable all other events handlers (maybe unnecessary)
        //
        // TODO it seems that 'button-press-event' will hide messageTray too
        // quickly, are there any hidden operations for 'button-release-event'
        // by default? Anyway, current implementation works.
        this.menu = null;
        this._onButtonPress = function(){};
        this.actor.connect('button-release-event', Lang.bind(this,
            function() {
                if (Main.messageTray._trayState == MessageTray.State.HIDDEN)
                    Main.messageTray.toggle();
            }));
        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));

        this._trayItemCount = 0;
        Main.messageTray.getSources().forEach(Lang.bind(this,
            function(source) {
                this._sourceAdded(Main.messageTray, source);
            }));
        this._sourceAddedId =
            Main.messageTray.connect('source-added',
                                     Lang.bind(this, this._sourceAdded));
        this._sourceRemovedId =
            Main.messageTray.connect('source-removed',
                                     Lang.bind(this, this._sourceRemoved));
        this._updateVisibility();
    },

    _sourceAdded: function(tray, source) {
        this._trayItemCount++;
        this._updateVisibility();
    },

    _sourceRemoved: function(source) {
        this._trayItemCount--;
        this.actor.checked = false;
        this._updateVisibility();
    },

    _updateVisibility: function() {
        this._counterLabel.text = this._trayItemCount.toString();
        this.actor.visible = this._trayItemCount > 0;
    },

    _onDestroy: function() {
        Main.messageTray.getSources().forEach(Lang.bind(this,
            function(source) {
                if (!source._windowListDestroyId)
                    return;
                source.disconnect(source._windowListDestroyId)
                delete source._windowListDestroyId;
            }));
        Main.messageTray.disconnect(this._sourceAddedId);
        Main.messageTray.disconnect(this._sourceRemovedId);
    }
});

const WorkspaceIndicator = new Lang.Class({
    Name: 'WindowList.WorkspaceIndicator',
    Extends: PanelMenu.Button,

    _init: function(){
        this.parent(0.0, _("Workspace Indicator"));
        this.actor.add_style_class_name('window-list-workspace-indicator');

        this._currentWorkspace = global.screen.get_active_workspace().index();
        this.statusLabel = new St.Label({
            x_align: Clutter.ActorAlign.CENTER,
            x_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
            y_expand: true,
            text: this._getStatusText()
        });

        this.actor.add_actor(this.statusLabel);

        this.workspacesItems = [];

        this._screenSignals = [];
        this._screenSignals.push(global.screen.connect('notify::n-workspaces', Lang.bind(this,this._updateMenu)));
        this._screenSignals.push(global.screen.connect_after('workspace-switched', Lang.bind(this,this._updateIndicator)));

        this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
        this._updateMenu();

        this._settings = new Gio.Settings({ schema: 'org.gnome.desktop.wm.preferences' });
        this._settingsChangedId = this._settings.connect('changed::workspace-names', Lang.bind(this, this._updateMenu));
    },

    destroy: function() {
        for (let i = 0; i < this._screenSignals.length; i++)
            global.screen.disconnect(this._screenSignals[i]);

        if (this._settingsChangedId) {
            this._settings.disconnect(this._settingsChangedId);
            this._settingsChangedId = 0;
        }

        this.parent();
    },

    _updateIndicator: function() {
        this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
        this._currentWorkspace = global.screen.get_active_workspace().index();
        this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);

        this.statusLabel.set_text(this._getStatusText());
    },

    _getStatusText: function() {
        let current = global.screen.get_active_workspace().index();
        let total = global.screen.n_workspaces;

        return '%d / %d'.format(current + 1, total);
    },

    _updateMenu: function() {
        this.menu.removeAll();
        this.workspacesItems = [];
        this._currentWorkspace = global.screen.get_active_workspace().index();

        for(let i = 0; i < global.screen.n_workspaces; i++) {
            let name = Meta.prefs_get_workspace_name(i);
            let item = new PopupMenu.PopupMenuItem(name);
            item.workspaceId = i;

            item.connect('activate', Lang.bind(this, function(item, event) {
                this._activate(item.workspaceId);
            }));

            if (i == this._currentWorkspace)
                item.setOrnament(PopupMenu.Ornament.DOT);

            this.menu.addMenuItem(item);
            this.workspacesItems[i] = item;
        }

        this.statusLabel.set_text(this._getStatusText());
    },

    _activate: function(index) {
        if(index >= 0 && index < global.screen.n_workspaces) {
            let metaWorkspace = global.screen.get_workspace_by_index(index);
            metaWorkspace.activate(global.get_current_time());
        }
    },

    _onScrollEvent: function(actor, event) {
        let direction = event.get_scroll_direction();
        let diff = 0;
        if (direction == Clutter.ScrollDirection.DOWN) {
            diff = 1;
        } else if (direction == Clutter.ScrollDirection.UP) {
            diff = -1;
        } else {
            return;
        }

        let newIndex = this._currentWorkspace + diff;
        this._activate(newIndex);
    },
});

// Carl:
//
// The original WindowList is actually the new bottom panel. To add window list
// into main panel, only 'this._windowList' is preserved and 'WindowList' now is
// in effect 'this._windowList'.
const WindowList = new Lang.Class({
    Name: 'WindowList',

    _init: function() {
        let layout = new Clutter.BoxLayout({ homogeneous: true });
        this._windowList = new St.Widget({ style_class: 'window-list',
                                     layout_manager: layout,
                                     x_align: Clutter.ActorAlign.START,
                                     x_expand: true,
                                     y_expand: true });
        // make WindowList "real window list".
        this.actor = this._windowList;

        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));

        this._windowList.connect('style-changed', Lang.bind(this,
            function() {
                let node = this._windowList.get_theme_node();
                let spacing = node.get_length('spacing');
                this._windowList.layout_manager.spacing = spacing;
            }));
        this._windowList.connect('notify::allocation', Lang.bind(this,
            function() {
                if (this._groupingMode != GroupingMode.AUTO || this._grouped)
                    return;

                let allocation = this._windowList.allocation;
                let width = allocation.x2 - allocation.x1;
                let [, natWidth] = this._windowList.get_preferred_width(-1);
                if (width < natWidth) {
                    this._grouped = true;
                    Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
                                   Lang.bind(this, this._populateWindowList));
                }
            }));

        this._appSystem = Shell.AppSystem.get_default();
        this._appStateChangedId =
            this._appSystem.connect('app-state-changed',
                                    Lang.bind(this, this._onAppStateChanged));

        this._monitorsChangedId =
            Main.layoutManager.connect('monitors-changed',
                                       Lang.bind(this, this._updatePosition));
        this._updatePosition();

        this._keyboardVisiblechangedId =
            Main.layoutManager.connect('keyboard-visible-changed',
                Lang.bind(this, function(o, state) {
                    Main.layoutManager.keyboardBox.visible = state;
                    Main.layoutManager.uiGroup.set_child_above_sibling(Main.layoutManager.panelBox,
                                                         Main.layoutManager.keyboardBox);
                    this._updateKeyboardAnchor();
                }));

        this._workspaceSignals = new Hash.Map();
        this._nWorkspacesChangedId =
            global.screen.connect('notify::n-workspaces',
                                  Lang.bind(this, this._onWorkspacesChanged));
        this._onWorkspacesChanged();

        this._overviewShowingId =
            Main.overview.connect('showing', Lang.bind(this, function() {
                Main.layoutManager.panelBox.hide();
                this._updateKeyboardAnchor();
                this._updateMessageTrayAnchor();
            }));
        this._overviewShownId =
            Main.overview.connect('shown', Lang.bind(this, function() {
                this.actor.hide();
            }));

        this._overviewHidingId =
            Main.overview.connect('hiding', Lang.bind(this, function() {
            }));

        this._overviewHiddenId =
            Main.overview.connect('hidden', Lang.bind(this, function() {
                this.actor.show();
                this._updateKeyboardAnchor();
                Main.layoutManager.hideKeyboard();
                this._updateMessageTrayAnchor();

                Main.layoutManager.uiGroup.set_child_above_sibling(Main.layoutManager.panelBox,
                                                                   Main.layoutManager.trayBox);
                Main.layoutManager.panelBox.show();
            }));

        this._updateMessageTrayAnchor();

        this._fullscreenChangedId =
            global.screen.connect('in-fullscreen-changed', Lang.bind(this, function() {
                this._updateMessageTrayAnchor();
            }));

        this._dragBeginId =
            Main.xdndHandler.connect('drag-begin',
                                     Lang.bind(this, this._onDragBegin));
        this._dragEndId =
            Main.xdndHandler.connect('drag-end',
                                     Lang.bind(this, this._onDragEnd));
        this._dragMonitor = {
            dragMotion: Lang.bind(this, this._onDragMotion)
        };

        this._dndTimeoutId = 0;
        this._dndWindow = null;

        this._settings = Convenience.getSettings();
        this._groupingModeChangedId =
            this._settings.connect('changed::grouping-mode',
                                   Lang.bind(this, this._groupingModeChanged));
        this._groupingModeChanged();
    },

    _groupingModeChanged: function() {
        this._groupingMode = this._settings.get_enum('grouping-mode');
        this._grouped = this._groupingMode == GroupingMode.ALWAYS;
        this._populateWindowList();
    },

    _populateWindowList: function() {
        this._windowList.destroy_all_children();

        if (!this._grouped) {
            let windows = Meta.get_window_actors(global.screen);
            for (let i = 0; i < windows.length; i++)
                this._onWindowAdded(null, windows[i].metaWindow);
        } else {
            let apps = this._appSystem.get_running();
            for (let i = 0; i < apps.length; i++)
                this._addApp(apps[i]);
        }
    },

    _updatePosition: function() {
        let monitor = Main.layoutManager.primaryMonitor;
        let panelBox = Main.layoutManager.panelBox;
        panelBox.width = monitor.width;
        panelBox.set_position(monitor.x, monitor.y + monitor.height - panelBox.height);
    },

    _updateKeyboardAnchor: function() {
        if (!Main.keyboard.actor)
            return;

        let anchorY = Main.overview.visible ? 0 : Main.layoutManager.panelBox.height;
        Main.keyboard.actor.anchor_y = anchorY;
    },

    _updateMessageTrayAnchor: function() {
        let anchorY;

        if (Main.overview.visible
           || Main.layoutManager.findMonitorForActor(Main.layoutManager.panelBox).inFullscreen){
            anchorY = 0;
        }
        else {
            anchorY = Main.layoutManager.panelBox.height;
        }
        
        Main.messageTray.actor.anchor_y = anchorY;
        Main.messageTray._notificationWidget.anchor_y = anchorY;
    },

    _onAppStateChanged: function(appSys, app) {
        if (!this._grouped)
            return;

        if (app.state == Shell.AppState.RUNNING)
            this._addApp(app);
        else if (app.state == Shell.AppState.STOPPED)
            this._removeApp(app);
    },

    _addApp: function(app) {
        let button = new AppButton(app);
        this._windowList.layout_manager.pack(button.actor,
                                             true, true, true,
                                             Clutter.BoxAlignment.START,
                                             Clutter.BoxAlignment.START);
    },

    _removeApp: function(app) {
        let children = this._windowList.get_children();
        for (let i = 0; i < children.length; i++) {
            if (children[i]._delegate.app == app) {
                children[i].destroy();
                return;
            }
        }
    },

    _onWindowAdded: function(ws, win) {
        if (!Shell.WindowTracker.get_default().is_window_interesting(win))
            return;

        if (this._grouped)
            return;

        let button = new WindowButton(win);
        this._windowList.layout_manager.pack(button.actor,
                                             true, true, true,
                                             Clutter.BoxAlignment.START,
                                             Clutter.BoxAlignment.START);
    },

    _onWindowRemoved: function(ws, win) {
        if (this._grouped) {
            if (this._groupingMode == GroupingMode.AUTO) {
                this._grouped = false;
                this._populateWindowList();
            }

            return;
        }

        let children = this._windowList.get_children();
        for (let i = 0; i < children.length; i++) {
            if (children[i]._delegate.metaWindow == win) {
                children[i].destroy();
                return;
            }
        }
    },

    _onWorkspacesChanged: function() {
        let numWorkspaces = global.screen.n_workspaces;
        for (let i = 0; i < numWorkspaces; i++) {
            let workspace = global.screen.get_workspace_by_index(i);
            if (this._workspaceSignals.has(workspace))
                continue;

            let signals = { windowAddedId: 0, windowRemovedId: 0 };
            signals._windowAddedId =
                workspace.connect_after('window-added',
                                        Lang.bind(this, this._onWindowAdded));
            signals._windowRemovedId =
                workspace.connect('window-removed',
                                  Lang.bind(this, this._onWindowRemoved));
            this._workspaceSignals.set(workspace, signals);
        }
    },

    _disconnectWorkspaceSignals: function() {
        let numWorkspaces = global.screen.n_workspaces;
        for (let i = 0; i < numWorkspaces; i++) {
            let workspace = global.screen.get_workspace_by_index(i);
            let signals = this._workspaceSignals.delete(workspace)[1];
            workspace.disconnect(signals._windowAddedId);
            workspace.disconnect(signals._windowRemovedId);
        }
    },

    _onDragBegin: function() {
        DND.addDragMonitor(this._dragMonitor);
    },

    _onDragEnd: function() {
        DND.removeDragMonitor(this._dragMonitor);
        this._removeActivateTimeout();
    },

    _onDragMotion: function(dragEvent) {
        if (Main.overview.visible ||
            !this.actor.contains(dragEvent.targetActor)) {
            this._removeActivateTimeout();
            return DND.DragMotionResult.CONTINUE;
        }

        let hoveredWindow = null;
        if (dragEvent.targetActor._delegate)
            hoveredWindow = dragEvent.targetActor._delegate.metaWindow;

        if (!hoveredWindow ||
            this._dndWindow == hoveredWindow)
            return DND.DragMotionResult.CONTINUE;

        this._removeActivateTimeout();

        this._dndWindow = hoveredWindow;
        this._dndTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
                                              DND_ACTIVATE_TIMEOUT,
                                              Lang.bind(this, this._activateWindow));

        return DND.DragMotionResult.CONTINUE;
    },

    _removeActivateTimeout: function() {
        if (this._dndTimeoutId)
            GLib.source_remove (this._dndTimeoutId);
        this._dndTimeoutId = 0;
        this._dndWindow = null;
    },

    _activateWindow: function() {
        let [x, y] = global.get_pointer();
        let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);

        if (this._dndWindow && this.actor.contains(pickedActor))
            this._dndWindow.activate(global.get_current_time());
        this._dndWindow = null;
        this._dndTimeoutId = 0;

        return false;
    },

    _onDestroy: function() {
        this._appSystem.disconnect(this._appStateChangedId);
        this._appStateChangedId = 0;

        Main.layoutManager.disconnect(this._monitorsChangedId);
        this._monitorsChangedId = 0;

        this._disconnectWorkspaceSignals();
        global.screen.disconnect(this._nWorkspacesChangedId);
        this._nWorkspacesChangedId = 0;

        Main.layoutManager.disconnect(this._keyboardVisiblechangedId);
        this._keyboardVisiblechangedId = 0;
        Main.layoutManager.hideKeyboard();

        Main.messageTray.actor.anchor_y = 0;
        Main.messageTray._notificationWidget.anchor_y = 0;

        Main.overview.disconnect(this._overviewShowingId);
        Main.overview.disconnect(this._overviewShownId);
        Main.overview.disconnect(this._overviewHidingId);
        Main.overview.disconnect(this._overviewHiddenId);

        global.screen.disconnect(this._fullscreenChangedId);

        this._settings.disconnect(this._groupingModeChangedId);

        let windows = Meta.get_window_actors(global.screen);
        for (let i = 0; i < windows.length; i++)
            windows[i].metaWindow.set_icon_geometry(null);
    }
});

// Function for extending leftbox borrowed from "windowlist@o2net.cl". It's an
// adapted version of Panel.Panel._allocate;
function allocate(actor, box, flags) {
    let allocWidth = box.x2 - box.x1;
    let allocHeight = box.y2 - box.y1;

    let [leftMinWidth, leftNaturalWidth] = Main.panel._leftBox.get_preferred_width(-1);
    let [centerMinWidth, centerNaturalWidth] = Main.panel._centerBox.get_preferred_width(-1);
    let [rightMinWidth, rightNaturalWidth] = Main.panel._rightBox.get_preferred_width(-1);

    let sideWidth = allocWidth - rightNaturalWidth - centerNaturalWidth;

    // Adjust left/center/right boxes Y position
    //
    // After moving the panel to the bottom side, the top border will be added
    // (originally it's bottom border). However all 3 box groups of the main
    // panel are absolutely positioned starting from the top edge of main panel,
    // this offset yields the bottom pixel line is not clickable. This fix it.
    //
    // NOTE: we can set y1 as (MainLayoutManager.primaryMonitor.height -
    // allocHeight), which seems to be easier, however the solution below is
    // more generic and versatile.
    let node = Main.panel.actor.get_theme_node();
    let borderTopWidth = node.get_length("border-top");
    let y_offset = borderTopWidth;

    let childBox = new Clutter.ActorBox();

    childBox.y1 = y_offset;
    childBox.y2 = allocHeight + y_offset;
    if (Main.panel.actor.get_text_direction() == Clutter.TextDirection.RTL) {
    childBox.x1 = allocWidth - Math.min(Math.floor(sideWidth), leftNaturalWidth);
	childBox.x2 = allocWidth;
    } else {
	childBox.x1 = 0;
	childBox.x2 = Math.min(Math.floor(sideWidth), leftNaturalWidth);
    }
    Main.panel._leftBox.allocate(childBox, flags);

    childBox.y1 = y_offset;
    childBox.y2 = allocHeight + y_offset;
    if (Main.panel.actor.get_text_direction() == Clutter.TextDirection.RTL) {
	childBox.x1 = rightNaturalWidth;
	childBox.x2 = childBox.x1 + centerNaturalWidth;
    } else {
	childBox.x1 = allocWidth - centerNaturalWidth - rightNaturalWidth;
	childBox.x2 = childBox.x1 + centerNaturalWidth;
    }
    Main.panel._centerBox.allocate(childBox, flags);

    childBox.y1 = y_offset;
    childBox.y2 = allocHeight + y_offset;
    if (Main.panel.actor.get_text_direction() == Clutter.TextDirection.RTL) {
	childBox.x1 = 0;
	childBox.x2 = rightNaturalWidth;
    } else {
	childBox.x1 = allocWidth - rightNaturalWidth;
	childBox.x2 = allocWidth;
    }
    Main.panel._rightBox.allocate(childBox, flags);

    let [cornerMinWidth, cornerWidth] = Main.panel._leftCorner.actor.get_preferred_width(-1);
    let [cornerMinHeight, cornerHeight] = Main.panel._leftCorner.actor.get_preferred_width(-1);
    childBox.x1 = 0;
    childBox.x2 = cornerWidth;
    childBox.y1 = allocHeight;
    childBox.y2 = allocHeight + cornerHeight;
    Main.panel._leftCorner.actor.allocate(childBox, flags);

    let [cornerMinWidth, cornerWidth] = Main.panel._rightCorner.actor.get_preferred_width(-1);
    let [cornerMinHeight, cornerHeight] = Main.panel._rightCorner.actor.get_preferred_width(-1);
    childBox.x1 = allocWidth - cornerWidth;
    childBox.x2 = allocWidth;
    childBox.y1 = allocHeight;
    childBox.y2 = allocHeight + cornerHeight;
    Main.panel._rightCorner.actor.allocate(childBox, flags);
}

let panelConnectId;
let workspaceIndicator;
let messageButton;
let windowList;

function init() {

    // In SLE-Classic, we have no app menu anymore (removed from
    // mode/classic.json) and need to tell apps to include it themselves. But to
    // preserve the previous value of 'Gtk/ShellShowsAppMenu' s.t. it can be
    // restored. (The restored code is in Main._sessionLoaded())
    //
    // TODO due to the persistent nature of this settings, there is no way we
    // can have a in-memory volatile settings. Fix this in settings daemon???
    // 
    let xsetting = new Gio.Settings({ schema: 'org.gnome.settings-daemon.plugins.xsettings' });
    let valueObj = xsetting.get_value('overrides').deep_unpack();
    if (valueObj['Gtk/ShellShowsAppMenu/Old']){
        // previous session is SLE-Classic, do nothing
    }
    else if (valueObj['Gtk/ShellShowsAppMenu']){
        valueObj['Gtk/ShellShowsAppMenu/Old'] = valueObj['Gtk/ShellShowsAppMenu'];
    }
    else {
        valueObj['Gtk/ShellShowsAppMenu/Old'] = GLib.Variant.new('i', 1);
    }
    valueObj['Gtk/ShellShowsAppMenu'] = GLib.Variant.new('i', 0);
    xsetting.set_value('overrides', GLib.Variant.new('a{sv}', valueObj));

    // change LookingGlass's positioning
    imports.ui.lookingGlass.LookingGlass.prototype._resize = function() {
        let primary = Main.layoutManager.primaryMonitor;
        let myWidth = primary.width * 0.7;
        let availableHeight = primary.height - Main.layoutManager.keyboardBox.height;
        let myHeight = Math.min(primary.height * 0.7, availableHeight * 0.9);
        this.actor.x = primary.x + (primary.width - myWidth) / 2;
        // Changes: remove the inclusion of the panel height
        this._hiddenY = primary.y - myHeight - 4; // -4 to hide the top corners
        this._targetY = this._hiddenY + myHeight;
        this.actor.y = this._hiddenY;
        this.actor.width = myWidth;
        this.actor.height = myHeight;
        this._objInspector.actor.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8));
        this._objInspector.actor.set_position(this.actor.x + Math.floor(myWidth * 0.1),
                                              this._targetY + Math.floor(myHeight * 0.1));
    };


    SLETweaks.init();
}

function enable() {
    // enable other SLE tweaks
    SLETweaks.enable();

    workspaceIndicator = new WorkspaceIndicator();
    messageButton = new TrayButton();
    windowList = new WindowList();

    // TODO manual position settings are not optimal, a quick fix. Please use
    // mode JSON and sessionMode.
    Main.panel.addToStatusArea('message-indicator', messageButton, 0, 'right');
    Main.panel.addToStatusArea('workspace-indicator', workspaceIndicator, 1, 'right');
    Main.panel.menuManager.addMenu(workspaceIndicator.menu);
    Main.panel._leftBox.add_actor(windowList.actor);

    // Expand the letfbox
    panelConnectId = Main.panel.actor.connect('allocate', allocate);

    // reordering traybox and panel s.t. the visibility order will be correct.
    Main.layoutManager.uiGroup.set_child_above_sibling(Main.layoutManager.panelBox,
                                                       Main.layoutManager.trayBox);

    Main.layoutManager._updateBoxes();
}


function disable(){
    Main.layoutManager.uiGroup.set_child_above_sibling(Main.layoutManager.trayBox,
                                                       Main.layoutManager.panelBox);

    Main.panel.menuManager.removeMenu(workspaceIndicator.menu);
    workspaceIndicator.destroy();
    workspaceIndicator = null;

    messageButton.destroy();
    messageButton = null;

    Main.panel._leftBox.remove_actor(windowList.actor);
    windowList.actor.destroy();
    Main.panel.actor.disconnect(panelConnectId);
    panelConnectId = null;
    windowList = null;

    SLETweaks.disable();


    Main.layoutManager._updateBoxes();
}
