/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */

const Big = imports.gi.Big;
const Clutter = imports.gi.Clutter;
const Gdk = imports.gi.Gdk;
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Pango = imports.gi.Pango;
const Shell = imports.gi.Shell;

const AppIcon = imports.ui.appIcon;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;

const POPUP_BG_COLOR = new Clutter.Color();
POPUP_BG_COLOR.from_pixel(0x00000080);
const POPUP_APPICON_BORDER_COLOR = new Clutter.Color();
POPUP_APPICON_BORDER_COLOR.from_pixel(0xffffffff);

const POPUP_APPS_BOX_SPACING = 8;
const POPUP_ICON_SIZE = 48;

const POPUP_POINTER_SELECTION_THRESHOLD = 3;

function AltTabPopup() {
    this._init();
}

AltTabPopup.prototype = {
    _init : function() {
        this.actor = new Big.Box({ background_color : POPUP_BG_COLOR,
                                   corner_radius: POPUP_APPS_BOX_SPACING,
                                   padding: POPUP_APPS_BOX_SPACING,
                                   spacing: POPUP_APPS_BOX_SPACING,
                                   orientation: Big.BoxOrientation.VERTICAL,
                                   reactive: true });
        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));

        this._appsBox = new Big.Box({ spacing: POPUP_APPS_BOX_SPACING,
                                      orientation: Big.BoxOrientation.HORIZONTAL });
        let gcenterbox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
                                       x_align: Big.BoxAlignment.CENTER });
        gcenterbox.append(this._appsBox, Big.BoxPackFlags.NONE);
        this.actor.append(gcenterbox, Big.BoxPackFlags.NONE);

        this._icons = [];
        this._currentWindows = [];
        this._haveModal = false;
        this._selected = 0;
        this._highlightedWindow = null;
        this._toplevels = global.window_group.get_children();

        global.stage.add_actor(this.actor);
    },

    _addIcon : function(appIcon) {
        appIcon.connect('activate', Lang.bind(this, this._appClicked));
        appIcon.connect('activate-window', Lang.bind(this, this._windowClicked));
        appIcon.connect('highlight-window', Lang.bind(this, this._windowHovered));
        appIcon.connect('menu-popped-up', Lang.bind(this, this._menuPoppedUp));
        appIcon.connect('menu-popped-down', Lang.bind(this, this._menuPoppedDown));

        appIcon.actor.connect('enter-event', Lang.bind(this, this._iconEntered));

        // FIXME?
        appIcon.actor.border = 2;
        appIcon.highlight_border_color = POPUP_APPICON_BORDER_COLOR;

        this._icons.push(appIcon);
        this._currentWindows.push(appIcon.windows[0]);

        this._appsBox.append(appIcon.actor, Big.BoxPackFlags.NONE);
    },

    show : function(initialSelection) {
        let appMonitor = Shell.AppMonitor.get_default();
        let apps = appMonitor.get_running_apps ("");

        if (!apps.length)
            return false;

        if (!Main.pushModal(this.actor))
            return false;
        this._haveModal = true;

        this._keyPressEventId = global.stage.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
        this._keyReleaseEventId = global.stage.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));

        this._motionEventId = this.actor.connect('motion-event', Lang.bind(this, this._mouseMoved));
        this._mouseActive = false;
        this._mouseMovement = 0;

        // Contruct the AppIcons, sort by time, add to the popup
        let icons = [];
        for (let i = 0; i < apps.length; i++)
            icons.push(new AppIcon.AppIcon(apps[i], AppIcon.MenuType.BELOW, false));
        icons.sort(Lang.bind(this, this._sortAppIcon));
        for (let i = 0; i < icons.length; i++)
            this._addIcon(icons[i]);

        // Need to specify explicit width and height because the
        // window_group may not actually cover the whole screen
        this._lightbox = new Lightbox.Lightbox(global.window_group,
                                               global.screen_width,
                                               global.screen_height);

        this.actor.show_all();

        let primary = global.get_primary_monitor();
        this.actor.x = primary.x + Math.floor((primary.width - this.actor.width) / 2);
        this.actor.y = primary.y + Math.floor((primary.height - this.actor.height) / 2);

        this._updateSelection(initialSelection);

        // There's a race condition; if the user released Alt before
        // we got the grab, then we won't be notified. (See
        // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
        // details.) So we check now. (Have to do this after calling
        // _updateSelection.)
        let [screen, x, y, mods] = Gdk.Display.get_default().get_pointer();
        if (!(mods & Gdk.ModifierType.MOD1_MASK)) {
            this._finish();
            return false;
        }

        return true;
    },

    _hasVisibleWindows : function(appIcon) {
        for (let i = 0; i < appIcon.windows.length; i++) {
            if (appIcon.windows[i].showing_on_its_workspace())
                return true;
        }
        return false;
    },

    _sortAppIcon : function(appIcon1, appIcon2) {
        let vis1 = this._hasVisibleWindows(appIcon1);
        let vis2 = this._hasVisibleWindows(appIcon2);

        if (vis1 && !vis2) {
            return -1;
        } else if (vis2 && !vis1) {
            return 1;
        } else {
            // The app's most-recently-used window is first
            // in its list
            return (appIcon2.windows[0].get_user_time() -
                    appIcon1.windows[0].get_user_time());
        }
    },

    _keyPressEvent : function(actor, event) {
        let keysym = event.get_key_symbol();
        let backwards = (event.get_state() & Clutter.ModifierType.SHIFT_MASK);

        if (keysym == Clutter.Tab)
            this._updateSelection(backwards ? -1 : 1);
        else if (keysym == Clutter.Left)
            this._updateSelection(-1);
        else if (keysym == Clutter.Right)
            this._updateSelection(1);
        else if (keysym == Clutter.grave)
            this._updateWindowSelection(backwards ? -1 : 1);
        else if (keysym == Clutter.Up)
            this._updateWindowSelection(-1);
        else if (keysym == Clutter.Down)
            this._updateWindowSelection(1);
        else if (keysym == Clutter.Escape)
            this.destroy();

        return true;
    },

    _keyReleaseEvent : function(actor, event) {
        let keysym = event.get_key_symbol();

        if (keysym == Clutter.Alt_L || keysym == Clutter.Alt_R)
            this._finish();

        return true;
    },

    _appClicked : function(icon) {
        Main.activateWindow(icon.windows[0]);
        this.destroy();
    },

    _windowClicked : function(icon, window) {
        if (window)
            Main.activateWindow(window);
        this.destroy();
    },

    _windowHovered : function(icon, window) {
        if (window)
            this._highlightWindow(window);
    },

    _mouseMoved : function(actor, event) {
        if (++this._mouseMovement < POPUP_POINTER_SELECTION_THRESHOLD)
            return;

        this.actor.disconnect(this._motionEventId);
        this._mouseActive = true;

        actor = event.get_source();
        while (actor) {
            if (actor._delegate instanceof AppIcon.AppIcon) {
                this._iconEntered(actor, event);
                return;
            }
            actor = actor.get_parent();
        }
    },

    _iconEntered : function(actor, event) {
        let index = this._icons.indexOf(actor._delegate);
        if (this._mouseActive)
            this._updateSelection(index - this._selected);
    },

    _finish : function() {
        if (this._highlightedWindow)
            Main.activateWindow(this._highlightedWindow);
        this.destroy();
    },

    destroy : function() {
        this.actor.destroy();
    },

    _onDestroy : function() {
        if (this._haveModal)
            Main.popModal(this.actor);

        if (this._lightbox)
            this._lightbox.destroy();

        if (this._keyPressEventId)
            global.stage.disconnect(this._keyPressEventId);
        if (this._keyReleaseEventId)
            global.stage.disconnect(this._keyReleaseEventId);
    },

    _updateSelection : function(delta) {
        this._icons[this._selected].setHighlight(false);
        if (delta != 0 && this._selectedMenu)
                this._selectedMenu.popdown();

        this._selected = (this._selected + this._icons.length + delta) % this._icons.length;
        this._icons[this._selected].setHighlight(true);

        this._highlightWindow(this._currentWindows[this._selected]);
    },

    _menuPoppedUp : function(icon, menu) {
        this._selectedMenu = menu;
    },

    _menuPoppedDown : function(icon, menu) {
        this._selectedMenu = null;
    },

    _updateWindowSelection : function(delta) {
        let icon = this._icons[this._selected];

        if (!this._selectedMenu)
            icon.popupMenu();
        if (!this._selectedMenu)
            return;

        let next = 0;
        for (let i = 0; i < icon.windows.length; i++) {
            if (icon.windows[i] == this._highlightedWindow) {
                next = (i + icon.windows.length + delta) % icon.windows.length;
                break;
            }
        }
        this._selectedMenu.selectWindow(icon.windows[next]);
    },

    _highlightWindow : function(metaWin) {
        this._highlightedWindow = metaWin;
        this._currentWindows[this._selected] = metaWin;
        this._lightbox.highlight(this._highlightedWindow.get_compositor_private());
    }
};