const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;

const CtrlAltTab = imports.ui.ctrlAltTab;
const Lang = imports.lang;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
const OverviewControls = imports.ui.overviewControls;
const Tweener = imports.ui.tweener;

const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
    'bluetooth-applet': 'bluetooth',
    'gnome-volume-control-applet': 'volume', // renamed to gnome-sound-applet
                                             // when moved to control center
    'gnome-sound-applet': 'volume',
    'nm-applet': 'network',
    'gnome-power-manager': 'battery',
    'keyboard': 'keyboard',
    'a11y-keyboard': 'a11y',
    'kbd-scrolllock': 'keyboard',
    'kbd-numlock': 'keyboard',
    'kbd-capslock': 'keyboard',
    'ibus-ui-gtk': 'keyboard'
};

// Offset of the original position from the bottom-right corner
const CONCEALED_WIDTH = 3;
const REVEAL_ANIMATION_TIME = 0.2;
const TEMP_REVEAL_TIME = 2;

const BARRIER_THRESHOLD = 70;
const BARRIER_TIMEOUT = 1000;

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

    _init: function() {
        this.actor = new St.Widget({ clip_to_allocation: true,
                                     layout_manager: new Clutter.BinLayout() });
        let constraint = new Layout.MonitorConstraint({ primary: true,
                                                        work_area: true });
        this.actor.add_constraint(constraint);

        this._slideLayout = new OverviewControls.SlideLayout();
        this._slideLayout.translationX = 0;
        this._slideLayout.slideDirection = OverviewControls.SlideDirection.LEFT;

        this._slider = new St.Widget({ style_class: 'legacy-tray',
                                       x_expand: true, y_expand: true,
                                       x_align: Clutter.ActorAlign.START,
                                       y_align: Clutter.ActorAlign.END,
                                       layout_manager: this._slideLayout });
        this.actor.add_actor(this._slider);
        this._slider.connect('notify::allocation', Lang.bind(this, this._syncBarrier));

        this._box = new St.BoxLayout();
        this._slider.add_actor(this._box);

        this._concealHandle = new St.Button({ style_class: 'legacy-tray-handle',
                                              /* translators: 'Hide' is a verb */
                                              accessible_name: _("Hide tray"),
                                              can_focus: true });
        this._concealHandle.child = new St.Icon({ icon_name: 'go-previous-symbolic' });
        this._box.add_child(this._concealHandle);

        this._iconBox = new St.BoxLayout({ style_class: 'legacy-tray-icon-box' });
        this._box.add_actor(this._iconBox);

        this._revealHandle = new St.Button({ style_class: 'legacy-tray-handle' });
        this._revealHandle.child = new St.Icon({ icon_name: 'go-next-symbolic' });
        this._box.add_child(this._revealHandle);

        this._revealHandle.bind_property('visible',
                                         this._concealHandle, 'visible',
                                         GObject.BindingFlags.BIDIRECTIONAL |
                                         GObject.BindingFlags.INVERT_BOOLEAN);
        this._revealHandle.connect('notify::visible',
                                   Lang.bind(this, this._sync));
        this._revealHandle.connect('notify::hover',
                                   Lang.bind(this ,this._sync));
        this._revealHandle.connect('clicked', Lang.bind(this,
            function() {
                this._concealHandle.show();
            }));
        this._concealHandle.connect('clicked', Lang.bind(this,
            function() {
                this._revealHandle.show();
            }));

        this._horizontalBarrier = null;
        this._pressureBarrier = new Layout.PressureBarrier(BARRIER_THRESHOLD,
                                                           BARRIER_TIMEOUT,
                                                           Shell.ActionMode.NORMAL);
        this._pressureBarrier.connect('trigger', Lang.bind(this, function() {
            this._concealHandle.show();
        }));

        Main.layoutManager.addChrome(this.actor, { affectsInputRegion: false });
        Main.layoutManager.trackChrome(this._slider, { affectsInputRegion: true });
        Main.uiGroup.set_child_below_sibling(this.actor, Main.layoutManager.modalDialogGroup);
        Main.ctrlAltTabManager.addGroup(this.actor,
                                        _("Status Icons"), 'focus-legacy-systray-symbolic',
                                        { sortGroup: CtrlAltTab.SortGroup.BOTTOM });

        this._trayManager = new Shell.TrayManager();
        this._trayIconAddedId = this._trayManager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded));
        this._trayIconRemovedId = this._trayManager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
        this._trayManager.manage_screen(global.screen, this.actor);

        Main.overview.connect('showing', Lang.bind(this,
            function() {
                Tweener.removeTweens(this._slider);
                Tweener.addTween(this._slider, { opacity: 0,
                                                 time: Overview.ANIMATION_TIME,
                                                 transition: 'easeOutQuad' });
            }));
        Main.overview.connect('shown', Lang.bind(this, this._sync));
        Main.overview.connect('hiding', Lang.bind(this,
            function() {
                this._sync();
                Tweener.removeTweens(this._slider);
                Tweener.addTween(this._slider, { opacity: 255,
                                                 time: Overview.ANIMATION_TIME,
                                                 transition: 'easeOutQuad' });
            }));

        Main.layoutManager.connect('monitors-changed',
                                   Lang.bind(this, this._sync));
        global.screen.connect('in-fullscreen-changed',
                              Lang.bind(this, this._sync));
        Main.sessionMode.connect('updated', Lang.bind(this, this._sync));

        this._sync();
    },

    _onTrayIconAdded: function(tm, icon) {
        let wmClass = icon.wm_class ? icon.wm_class.toLowerCase() : '';
        if (STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass] !== undefined)
            return;

        let button = new St.Button({ child: icon,
                                     style_class: 'legacy-tray-icon',
                                     button_mask: St.ButtonMask.ONE |
                                                  St.ButtonMask.TWO |
                                                  St.ButtonMask.THREE,
                                     can_focus: true,
                                     x_fill: true, y_fill: true });

        let app = Shell.WindowTracker.get_default().get_app_from_pid(icon.pid);
        if (!app)
            app = Shell.AppSystem.get_default().lookup_startup_wmclass(wmClass);
        if (!app)
            app = Shell.AppSystem.get_default().lookup_desktop_wmclass(wmClass);
        if (app)
            button.accessible_name = app.get_name();
        else
            button.accessible_name = icon.title;

        button.connect('clicked',
            function() {
                icon.click(Clutter.get_current_event());
            });
        button.connect('key-press-event',
            function() {
                icon.click(Clutter.get_current_event());
                return Clutter.EVENT_PROPAGATE;
            });
        button.connect('key-focus-in', Lang.bind(this,
            function() {
                this._concealHandle.show();
            }));

        this._iconBox.add_actor(button);

        if (!this._concealHandle.visible) {
            this._concealHandle.show();
            GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, TEMP_REVEAL_TIME,
                Lang.bind(this, function() {
                    this._concealHandle.hide();
                    return GLib.SOURCE_REMOVE;
                }));
        }
    },

    _onTrayIconRemoved: function(tm, icon) {
        if (!this.actor.contains(icon))
            return;

        icon.get_parent().destroy();
        this._sync();
    },

    _syncBarrier: function() {
        let rtl = (this._slider.get_text_direction() == Clutter.TextDirection.RTL);
        let [x, y] = this._slider.get_transformed_position();
        let [w, h] = this._slider.get_transformed_size();

        let x1 = Math.round(x);
        if (rtl)
            x1 += Math.round(w);

        let x2 = x1;
        let y1 = Math.round(y);
        let y2 = y1 + Math.round(h);

        if (this._horizontalBarrier &&
            this._horizontalBarrier.x1 == x1 &&
            this._horizontalBarrier.y1 == y1 &&
            this._horizontalBarrier.x2 == x2 &&
            this._horizontalBarrier.y2 == y2)
            return;

        this._unsetBarrier();

        let directions = (rtl ? Meta.BarrierDirection.NEGATIVE_X : Meta.BarrierDirection.POSITIVE_X);
        this._horizontalBarrier = new Meta.Barrier({ display: global.display,
                                                     x1: x1, x2: x2,
                                                     y1: y1, y2: y2,
                                                     directions: directions });
        this._pressureBarrier.addBarrier(this._horizontalBarrier);
    },

    _unsetBarrier: function() {
        if (this._horizontalBarrier == null)
            return;

        this._pressureBarrier.removeBarrier(this._horizontalBarrier);
        this._horizontalBarrier.destroy();
        this._horizontalBarrier = null;
    },

    _sync: function() {
        // FIXME: we no longer treat tray icons as notifications
        let allowed = Main.sessionMode.hasNotifications;
        let hasIcons = this._iconBox.get_n_children() > 0;
        let inOverview = Main.overview.visible && !Main.overview.animationInProgress;
        let inFullscreen = Main.layoutManager.primaryMonitor.inFullscreen;
        this.actor.visible = allowed && hasIcons && !inOverview && !inFullscreen;

        if (!hasIcons)
            this._concealHandle.hide();

        let targetSlide;
        if (this._concealHandle.visible) {
            targetSlide = 1.0;
        } else if (!hasIcons) {
            targetSlide = 0.0;
        } else {
            let [, boxWidth] = this._box.get_preferred_width(-1);
            let [, handleWidth] = this._revealHandle.get_preferred_width(-1);

            if (this._revealHandle.hover)
                targetSlide = handleWidth / boxWidth;
            else
                targetSlide = CONCEALED_WIDTH / boxWidth;
        }

        if (this.actor.visible) {
            Tweener.addTween(this._slideLayout,
                             { slideX: targetSlide,
                               time: REVEAL_ANIMATION_TIME,
                               transition: 'easeOutQuad' });
        } else {
            this._slideLayout.slideX = targetSlide;
            this._unsetBarrier();
        }
    }
});
