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

const Signals = imports.signals;
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;

const AppDisplay = imports.ui.appDisplay;
const AppFavorites = imports.ui.appFavorites;
const DND = imports.ui.dnd;
const IconGrid = imports.ui.iconGrid;
const Main = imports.ui.main;
const Workspace = imports.ui.workspace;


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

RemoveFavoriteIcon.prototype = {
    _init: function() {
        this.actor = new St.Bin({ style_class: 'remove-favorite' });
        this._iconActor = null;
        this.icon = new IconGrid.BaseIcon(_("Remove"),
                                           { setSizeManually: true,
                                             createIcon: Lang.bind(this, this._createIcon) });
        this.actor.set_child(this.icon.actor);
        this.actor._delegate = this;
    },

    _createIcon: function(size) {
        this._iconActor = new St.Icon({ icon_name: 'user-trash',
                                        style_class: 'remove-favorite-icon',
                                        icon_size: size });
        return this._iconActor;
    },

    setHover: function(hovered) {
        this.actor.set_hover(hovered);
        if (this._iconActor)
            this._iconActor.set_hover(hovered);
    },

    // Rely on the dragged item being a favorite
    handleDragOver: function(source, actor, x, y, time) {
        return DND.DragMotionResult.MOVE_DROP;
    },

    acceptDrop: function(source, actor, x, y, time) {
        let app = null;
        if (source instanceof AppDisplay.AppWellIcon) {
            let appSystem = Shell.AppSystem.get_default();
            app = appSystem.get_app(source.getId());
        } else if (source instanceof Workspace.WindowClone) {
            let tracker = Shell.WindowTracker.get_default();
            app = tracker.get_window_app(source.metaWindow);
        }

        let id = app.get_id();

        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
            function () {
                AppFavorites.getAppFavorites().removeFavorite(id);
                return false;
            }));

        return true;
    }
};


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

Dash.prototype = {
    _init : function() {
        this._menus = [];
        this._menuDisplays = [];
        this._maxHeight = -1;
        this._iconSize = 48;

        this._dragPlaceholder = null;
        this._dragPlaceholderPos = -1;
        this._favRemoveTarget = null;

        this._favorites = [];

        this._box = new St.BoxLayout({ name: 'dash',
                                       vertical: true,
                                       clip_to_allocation: true });
        this._box._delegate = this;

        this.actor = new St.Bin({ y_align: St.Align.START, child: this._box });
        this.actor.connect('notify::height', Lang.bind(this,
            function() {
                if (this._maxHeight != this.actor.height)
                    this._queueRedisplay();
                this._maxHeight = this.actor.height;
            }));

        this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay));

        this._tracker = Shell.WindowTracker.get_default();
        this._appSystem = Shell.AppSystem.get_default();

        this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay));
        AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay));
        this._tracker.connect('app-state-changed', Lang.bind(this, this._queueRedisplay));
    },

    show: function() {
        this._itemDragBeginId = Main.overview.connect('item-drag-begin',
            Lang.bind(this, this._onDragBegin));
        this._itemDragEndId = Main.overview.connect('item-drag-end',
            Lang.bind(this, this._onDragEnd));
        this._windowDragBeginId = Main.overview.connect('window-drag-begin',
            Lang.bind(this, this._onDragBegin));
        this._windowDragEndId = Main.overview.connect('window-drag-end',
            Lang.bind(this, this._onDragEnd));
    },

    hide: function() {
        Main.overview.disconnect(this._itemDragBeginId);
        Main.overview.disconnect(this._itemDragEndId);
        Main.overview.disconnect(this._windowDragBeginId);
        Main.overview.disconnect(this._windowDragEndId);
    },

    _onDragBegin: function() {
        this._dragMonitor = {
            dragMotion: Lang.bind(this, this._onDragMotion)
        };
        DND.addDragMonitor(this._dragMonitor);
    },

    _onDragEnd: function() {
        this._clearDragPlaceholder();
        if (this._favRemoveTarget) {
            this._favRemoveTarget.actor.destroy();
            this._favRemoveTarget = null;
        }
        DND.removeMonitor(this._dragMonitor);
    },

    _onDragMotion: function(dragEvent) {
        let app = null;
        if (dragEvent.source instanceof AppDisplay.AppWellIcon)
            app = this._appSystem.get_app(dragEvent.source.getId());
        else if (dragEvent.source instanceof Workspace.WindowClone)
            app = this._tracker.get_window_app(dragEvent.source.metaWindow);
        else
            return DND.DragMotionResult.CONTINUE;

        let id = app.get_id();

        let favorites = AppFavorites.getAppFavorites().getFavoriteMap();

        let srcIsFavorite = (id in favorites);

        if (srcIsFavorite && this._favRemoveTarget == null) {
                this._favRemoveTarget = new RemoveFavoriteIcon();
                this._favRemoveTarget.icon.setIconSize(this._iconSize);
                this._box.add(this._favRemoveTarget.actor);
        }

        let favRemoveHovered = false;
        if (this._favRemoveTarget)
            favRemoveHovered =
                this._favRemoveTarget.actor.contains(dragEvent.targetActor);

        if (!this._box.contains(dragEvent.targetActor) || favRemoveHovered)
            this._clearDragPlaceholder();

        if (this._favRemoveTarget)
            this._favRemoveTarget.setHover(favRemoveHovered);

        return DND.DragMotionResult.CONTINUE;
    },

    _appIdListToHash: function(apps) {
        let ids = {};
        for (let i = 0; i < apps.length; i++)
            ids[apps[i].get_id()] = apps[i];
        return ids;
    },

    _queueRedisplay: function () {
        Main.queueDeferredWork(this._workId);
    },

    _addApp: function(app) {
        let display = new AppDisplay.AppWellIcon(app);
        display._draggable.connect('drag-begin',
                                   Lang.bind(this, function() {
                                       display.actor.opacity = 50;
                                   }));
        display._draggable.connect('drag-end',
                                   Lang.bind(this, function() {
                                       display.actor.opacity = 255;
                                   }));
        this._box.add(display.actor);
    },

    _redisplay: function () {
        this._box.hide();
        this._box.remove_all();

        let favorites = AppFavorites.getAppFavorites().getFavoriteMap();

        /* hardcode here pending some design about how exactly desktop contexts behave */
        let contextId = '';

        let running = this._tracker.get_running_apps(contextId);

        for (let id in favorites) {
            let app = favorites[id];
            this._addApp(app);
        }

        for (let i = 0; i < running.length; i++) {
            let app = running[i];
            if (app.get_id() in favorites)
                continue;
            this._addApp(app);
        }

        let children = this._box.get_children();
        if (children.length == 0) {
            this._box.add_style_pseudo_class('empty');
        } else {
            this._box.remove_style_pseudo_class('empty');

            if (this._maxHeight > -1) {
                let iconSizes = [ 48, 32, 24, 22, 16 ];

                for (let i = 0; i < iconSizes.length; i++) {
                    let minHeight, natHeight;

                    this._iconSize = iconSizes[i];
                    for (let j = 0; j < children.length; j++)
                        children[j]._delegate.icon.setIconSize(this._iconSize);

                    [minHeight, natHeight] = this.actor.get_preferred_height(-1);

                    if (natHeight <= this._maxHeight)
                        break;
                }
            }
        }
        this._box.show();
    },

    _clearDragPlaceholder: function() {
        if (this._dragPlaceholder) {
            this._dragPlaceholder.destroy();
            this._dragPlaceholder = null;
            this._dragPlaceholderPos = -1;
        }
    },

    handleDragOver : function(source, actor, x, y, time) {
        let app = null;
        if (source instanceof AppDisplay.AppWellIcon)
            app = this._appSystem.get_app(source.getId());
        else if (source instanceof Workspace.WindowClone)
            app = this._tracker.get_window_app(source.metaWindow);

        // Don't allow favoriting of transient apps
        if (app == null || app.is_transient())
            return DND.DragMotionResult.NO_DROP;

        let favorites = AppFavorites.getAppFavorites().getFavorites();
        let numFavorites = favorites.length;

        let favPos = favorites.indexOf(app);

        let numChildren = this._box.get_children().length;
        let boxHeight = this._box.height;

        // Keep the placeholder out of the index calculation; assuming that
        // the remove target has the same size as "normal" items, we don't
        // need to do the same adjustment there.
        if (this._dragPlaceholder) {
            boxHeight -= this._dragPlaceholder.height;
            numChildren--;
        }

        let pos = Math.round(y * numChildren / boxHeight);

        if (pos != this._dragPlaceholderPos && pos <= numFavorites) {
            this._dragPlaceholderPos = pos;
            if (this._dragPlaceholder)
                this._dragPlaceholder.destroy();

            // Don't allow positioning before or after self
            if (favPos != -1 && (pos == favPos || pos == favPos + 1))
                return DND.DragMotionResult.CONTINUE;

            this._dragPlaceholder = new St.Bin({ style_class: 'dash-placeholder' });
            this._box.insert_actor(this._dragPlaceholder, pos);
        }

        let srcIsFavorite = (favPos != -1);

        if (srcIsFavorite)
            return DND.DragMotionResult.MOVE_DROP;

        return DND.DragMotionResult.COPY_DROP;
    },

    // Draggable target interface
    acceptDrop : function(source, actor, x, y, time) {
        let app = null;
        if (source instanceof AppDisplay.AppWellIcon) {
            app = this._appSystem.get_app(source.getId());
        } else if (source instanceof Workspace.WindowClone) {
            app = this._tracker.get_window_app(source.metaWindow);
        }

        // Don't allow favoriting of transient apps
        if (app == null || app.is_transient()) {
            return false;
        }

        let id = app.get_id();

        let favorites = AppFavorites.getAppFavorites().getFavoriteMap();

        let srcIsFavorite = (id in favorites);

        let favPos = 0;
        let children = this._box.get_children();
        for (let i = 0; i < this._dragPlaceholderPos; i++) {
            let childId = children[i]._delegate.app.get_id();
            if (childId == id)
                continue;
            if (childId in favorites)
                favPos++;
        }

        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
            function () {
                let appFavorites = AppFavorites.getAppFavorites();
                if (srcIsFavorite)
                    appFavorites.moveFavoriteToPos(id, favPos);
                else
                    appFavorites.addFavoriteAtPos(id, favPos);
                return false;
            }));

        return true;
    }
};

Signals.addSignalMethods(Dash.prototype);