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

const Clutter = imports.gi.Clutter;
const Shell = imports.gi.Shell;
const Gio = imports.gi.Gio;
const Signals = imports.signals;
const Mainloop = imports.mainloop;
const Tweener = imports.tweener.tweener;

const Panel = imports.ui.panel;
const Overlay = imports.ui.overlay;
const RunDialog = imports.ui.runDialog;
const WindowManager = imports.ui.windowManager;

const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);

let panel = null;
let overlay = null;
let overlayActive = false;
let runDialog = null;
let wm = null;

// The "FrameTicker" object is an object used to feed new frames to Tweener
// so it can update values and redraw. The default frame ticker for
// Tweener just uses a simple timeout at a fixed frame rate and has no idea
// of "catching up" by dropping frames.
//
// We substitute it with custom frame ticker here that connects Tweener to
// a Clutter.TimeLine. Now, Clutter.Timeline itself isn't a whole lot more
// sophisticated than a simple timeout at a fixed frame rate, but at least
// it knows how to drop frames. (See HippoAnimationManager for a more
// sophisticated view of continous time updates; even better is to pay
// attention to the vertical vblank and sync to that when possible.)
//
function ClutterFrameTicker() {
    this._init();
}

ClutterFrameTicker.prototype = {
    FRAME_RATE : 60,

    _init : function() {
        // We don't have a finite duration; tweener will tell us to stop
        // when we need to stop, so use 1000 seconds as "infinity"
        this._timeline = new Clutter.Timeline({ fps: this.FRAME_RATE,
                                                duration: 1000*1000 });
        this._currentTime = 0;
        this._frame = 0;

        let me = this;
        this._timeline.connect('new-frame',
            function(timeline, frame) {
                me._onNewFrame(frame);
            });
    },

    _onNewFrame : function(frame) {
        // Unfortunately the interface to to send a new frame to tweener
        // is a simple "next frame" and there is no provision for signaling
        // that frames have been skipped or just telling it the new time.
        // But what it actually does internally is just:
        //
        //  _currentTime += 1000/_ticker.FRAME_RATE;
        //
        // So by dynamically adjusting the value of FRAME_RATE we can trick
        // it into dealing with dropped frames.

        // If there is a lot of setup to start the animation, then
        // first frame number we get from clutter might be a long ways
        // into the animation (or the animation might even be done).
        // That looks bad, so we always start one frame into the
        // animation then only do frame dropping from there.
        let delta;
        if (this._frame == 0)
            delta = 1;
        else
            delta = frame - this._frame;

        if (delta == 0) // protect against divide-by-0 if we get a frame twice
            delta = 1;

        // currentTime is in milliseconds
        this._currentTime += (1000 * delta) / this.FRAME_RATE;
        this._frame = frame;
        this.emit('prepare-frame');
    },

    getTime : function() {
        return this._currentTime;
    },

    start : function() {
        this._timeline.start();
    },

    stop : function() {
        this._timeline.stop();
        this._frame = 0;
        this._currentTime = 0;
    }
};

Signals.addSignalMethods(ClutterFrameTicker.prototype);

function start() {
    let global = Shell.Global.get();

    Gio.DesktopAppInfo.set_desktop_env("GNOME");

    global.grab_dbus_service();
    global.start_task_panel();

    Tweener.setFrameTicker(new ClutterFrameTicker());

    // The background color really only matters if there is no desktop
    // window (say, nautilus) running. We set it mostly so things look good
    // when we are running inside Xephyr.
    global.stage.color = DEFAULT_BACKGROUND_COLOR;

    // Mutter currently hardcodes putting "Yessir. The compositor is running""
    // in the overlay. Clear that out.
    let children = global.overlay_group.get_children();
    for (let i = 0; i < children.length; i++)
        children[i].destroy();

    // metacity-clutter currently uses the same prefs as plain metacity,
    // which probably means we'll be starting out with multiple workspaces;
    // remove any unused ones
    let windows = global.get_windows();
    let maxWorkspace = 0;
    for (let i = 0; i < windows.length; i++) {
        let win = windows[i];

        if (!win.get_meta_window().is_on_all_workspaces() &&
            win.get_workspace() > maxWorkspace) {
            maxWorkspace = win.get_workspace();
        }
    }
    let screen = global.screen;
    if (screen.n_workspaces > maxWorkspace) {
        for (let w = screen.n_workspaces - 1; w > maxWorkspace; w--) {
            let workspace = screen.get_workspace_by_index(w);
            screen.remove_workspace(workspace, 0);
        }
    }

    global.connect('panel-run-dialog', function(panel) {
        // Make sure not more than one run dialog is shown.
        if (!runDialog) {
            runDialog = new RunDialog.RunDialog();
            let end_handler = function() {
                runDialog.destroy();
                runDialog = null;
            };
            runDialog.connect('run', end_handler);
            runDialog.connect('cancel', end_handler);
            if (!runDialog.show())
                end_handler();
        }
    });

    panel = new Panel.Panel();
    global.set_stage_input_area(0, 0, global.screen_width, Panel.PANEL_HEIGHT);

    overlay = new Overlay.Overlay();
    wm = new WindowManager.WindowManager();
    
    let display = global.screen.get_display();
    display.connect('overlay-key', function(display) {
        if (overlay.visible) {
            hide_overlay();
        } else {
            show_overlay();
        }
    });
}

// Used to go into a mode where all keyboard and mouse input goes to
// the stage. Returns true if we successfully grabbed the keyboard and
// went modal, false otherwise
function startModal() {
    let global = Shell.Global.get();

    if (!global.grab_keyboard())
        return false;

    global.set_stage_input_area(0, 0, global.screen_width, global.screen_height);

    return true;
}

function endModal() {
    let global = Shell.Global.get();

    global.ungrab_keyboard();
    global.set_stage_input_area(0, 0, global.screen_width, Panel.PANEL_HEIGHT);
}

function show_overlay() {
    if (startModal()) {
        overlayActive = true;
        overlay.show();
    }
}

function hide_overlay() {
    overlay.hide();
    overlayActive = false;
    endModal();
}