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

const Big = imports.gi.Big;
const Clutter = imports.gi.Clutter;
const Pango = imports.gi.Pango;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Shell = imports.gi.Shell;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const DND = imports.ui.dnd;
const Main = imports.ui.main;
const GenericDisplay = imports.ui.genericDisplay;

const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';

const PLACES_VSPACING = 8;
const PLACES_ICON_SIZE = 16;

/**
 * An entry in the places menu.
 * @name: String title
 * @iconFactory: A JavaScript callback which will create an icon texture
 * @onActivate: A JavaScript callback to launch the entry
 */
function PlaceDisplay(name, iconFactory, onActivate) {
    this._init(name, iconFactory, onActivate);
}

PlaceDisplay.prototype = {
    _init : function(name, iconFactory, onActivate) {
        this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
                                   reactive: true,
                                   spacing: 4 });
        this.actor.connect('button-release-event', Lang.bind(this, function (b, e) {
            onActivate(this);
            Main.overview.hide();
        }));
        let text = new Clutter.Text({ font_name: "Sans 14px",
                                      ellipsize: Pango.EllipsizeMode.END,
                                      color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
                                      text: name });
        let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
        this._icon = iconFactory();
        iconBox.append(this._icon, Big.BoxPackFlags.NONE);
        this.actor.append(iconBox, Big.BoxPackFlags.NONE);
        this.actor.append(text, Big.BoxPackFlags.EXPAND);

        this._iconFactory = iconFactory;
        this._onActivate = onActivate;

        this.actor._delegate = this;
        let draggable = DND.makeDraggable(this.actor);
    },

    getDragActorSource: function() {
        return this._icon;
    },

    getDragActor: function(stageX, stageY) {
        return this._iconFactory();
    },

    //// Drag and drop methods ////

    shellWorkspaceLaunch : function() {
        this._onActivate();
    }
};
Signals.addSignalMethods(PlaceDisplay.prototype);

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

Places.prototype = {
    _init : function() {
        // Places is divided semi-arbitrarily into left and right; a grid would
        // look better in that there would be an even number of items left+right,
        // but it seems like we want some sort of differentiation between actions
        // like "Connect to server..." and regular folders
        this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
                                   spacing: 4 });
        this._leftBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
        this.actor.append(this._leftBox, Big.BoxPackFlags.EXPAND);
        this._rightBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
        this.actor.append(this._rightBox, Big.BoxPackFlags.EXPAND);

        // Subdivide left into actions and devices
        this._actionsBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
                                         spacing: PLACES_VSPACING });
        this._leftBox.append(this._actionsBox, Big.BoxPackFlags.NONE);
        this._devBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
                                      spacing: PLACES_VSPACING });
        this._leftBox.append(this._devBox, Big.BoxPackFlags.NONE);

        // Right is bookmarks
        this._dirsBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
                                      spacing: PLACES_VSPACING });
        this._rightBox.append(this._dirsBox, Big.BoxPackFlags.NONE);

        let gconf = Shell.GConf.get_default();
        gconf.watch_directory(NAUTILUS_PREFS_DIR);

        let homeFile = Gio.file_new_for_path (GLib.get_home_dir());
        let homeUri = homeFile.get_uri();
        let homeLabel = Shell.util_get_label_for_uri (homeUri);
        let homeIcon = Shell.util_get_icon_for_uri (homeUri);
        let home = new PlaceDisplay(homeLabel,
            function() {
                return Shell.TextureCache.get_default().load_gicon(homeIcon, PLACES_ICON_SIZE);
            },
            function() {
                Gio.app_info_launch_default_for_uri(homeUri, Main.createAppLaunchContext());
            });

        this._actionsBox.append(home.actor, Big.BoxPackFlags.NONE);

        let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
        let desktopFile = Gio.file_new_for_path (desktopPath);
        let desktopUri = desktopFile.get_uri();
        let desktopLabel = Shell.util_get_label_for_uri (desktopUri);
        let desktopIcon = Shell.util_get_icon_for_uri (desktopUri);
        this._desktopMenu = new PlaceDisplay(desktopLabel,
            function() {
                return Shell.TextureCache.get_default().load_gicon(desktopIcon, PLACES_ICON_SIZE);
            },
            function() {
                Gio.app_info_launch_default_for_uri(desktopUri, Main.createAppLaunchContext());
            });
        this._actionsBox.append(this._desktopMenu.actor, Big.BoxPackFlags.NONE);
        this._updateDesktopMenuVisibility();
        gconf.connect('changed::' + DESKTOP_IS_HOME_KEY, Lang.bind(this, this._updateDesktopMenuVisibility));

        /*
        * Show devices, code more or less ported from nautilus-places-sidebar.c
        */
        this._volumeMonitor = Gio.VolumeMonitor.get();
        this._volumeMonitor.connect('volume-added', Lang.bind(this, this._updateDevices));
        this._volumeMonitor.connect('volume-removed',Lang.bind(this, this._updateDevices));
        this._volumeMonitor.connect('volume-changed', Lang.bind(this, this._updateDevices));
        this._volumeMonitor.connect('mount-added', Lang.bind(this, this._updateDevices));
        this._volumeMonitor.connect('mount-removed', Lang.bind(this, this._updateDevices));
        this._volumeMonitor.connect('mount-changed', Lang.bind(this, this._updateDevices));
        this._volumeMonitor.connect('drive-connected', Lang.bind(this, this._updateDevices));
        this._volumeMonitor.connect('drive-disconnected', Lang.bind(this, this._updateDevices));
        this._volumeMonitor.connect('drive-changed', Lang.bind(this, this._updateDevices));
        this._updateDevices();

        let networkApp = null;
        try {
            networkApp = Shell.AppSystem.get_default().load_from_desktop_file('gnome-network-scheme.desktop');
        } catch(e) {
            try {
                networkApp = Shell.AppSystem.get_default().load_from_desktop_file('network-scheme.desktop');
            } catch(e) {
                log("Cannot create \"Network\" item, .desktop file not found or corrupt.");
            }
        }

        if (networkApp != null) {
            let network = new PlaceDisplay(networkApp.get_name(),
                function() {
                    return networkApp.create_icon_texture(PLACES_ICON_SIZE);
                },
                function () {
                    networkApp.launch();
                });
            this._actionsBox.append(network.actor, Big.BoxPackFlags.NONE);
        }

        let connect = new PlaceDisplay('Connect to...',
            function () {
                return Shell.TextureCache.get_default().load_icon_name("applications-internet", PLACES_ICON_SIZE);
            },
            function () {
                new Shell.Process({ args: ['nautilus-connect-server'] }).run();
            });
        this._actionsBox.append(connect.actor, Big.BoxPackFlags.NONE);

        this._bookmarksPath = GLib.build_filenamev([GLib.get_home_dir(), ".gtk-bookmarks"]);
        this._bookmarksFile = Gio.file_new_for_path(this._bookmarksPath);
        let monitor = this._bookmarksFile.monitor_file(Gio.FileMonitorFlags.NONE, null);
        this._bookmarkTimeoutId = 0;
        monitor.connect('changed', Lang.bind(this, function () {
            if (this._bookmarkTimeoutId > 0)
                return;
            /* Defensive event compression */
            this._bookmarkTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function () {
                this._bookmarkTimeoutId = 0;
                this._reloadBookmarks();
                return false;
            }));
        }));

        this._reloadBookmarks();
    },

    _reloadBookmarks: function() {

        this._dirsBox.remove_all();

        if (!GLib.file_test(this._bookmarksPath, GLib.FileTest.EXISTS))
          return;

        let [success, bookmarksContent, len] = GLib.file_get_contents(this._bookmarksPath);

        if (!success)
            return;

        let bookmarks = bookmarksContent.split('\n');

        let bookmarksToLabel = {};
        let bookmarksOrder = [];
        for (let i = 0; i < bookmarks.length; i++) {
            let bookmarkLine = bookmarks[i];
            let components = bookmarkLine.split(' ');
            let bookmark = components[0];
            if (bookmark in bookmarksToLabel)
                continue;
            let label = null;
            if (components.length > 1)
                label = components.slice(1).join(' ');
            bookmarksToLabel[bookmark] = label;
            bookmarksOrder.push(bookmark);
        }

        for (let i = 0; i < bookmarksOrder.length; i++) {
            let bookmark = bookmarksOrder[i];
            let label = bookmarksToLabel[bookmark];
            let file = Gio.file_new_for_uri(bookmark);
            if (!file.query_exists(null))
                continue;
            if (label == null)
                label = Shell.util_get_label_for_uri(bookmark);
            if (label == null)
                continue;
            let icon = Shell.util_get_icon_for_uri(bookmark);

            let item = new PlaceDisplay(label,
                function() {
                    return Shell.TextureCache.get_default().load_gicon(icon, PLACES_ICON_SIZE);
                },
                function() {
                    Gio.app_info_launch_default_for_uri(bookmark, Main.createAppLaunchContext());
                });
            this._dirsBox.append(item.actor, Big.BoxPackFlags.NONE);
        }
    },

    _updateDevices: function() {
        this._devBox.remove_all();

        /* first go through all connected drives */
        let drives = this._volumeMonitor.get_connected_drives();
        for (let i = 0; i < drives.length; i++) {
                let volumes = drives[i].get_volumes();
                for(let j = 0; j < volumes.length; j++) {
                        let mount = volumes[j].get_mount();
                        if(mount != null) {
                                this._addMount(mount);
                        }
                }
        }

        /* add all volumes that is not associated with a drive */
        let volumes = this._volumeMonitor.get_volumes();
        for(let i = 0; i < volumes.length; i++) {
                if(volumes[i].get_drive() != null)
                        continue;

                let mount = volumes[i].get_mount();
                if(mount != null) {
                        this._addMount(mount);
                }
        }

        /* add mounts that have no volume (/etc/mtab mounts, ftp, sftp,...) */
        let mounts = this._volumeMonitor.get_mounts();
        for(let i = 0; i < mounts.length; i++) {
                if(mounts[i].is_shadowed())
                        continue;

                if(mounts[i].get_volume())
                        continue;

                this._addMount(mounts[i]);
        }
    },

    _addMount: function(mount) {
        let mountLabel = mount.get_name();
        let mountIcon = mount.get_icon();
        let root = mount.get_root();
        let mountUri = root.get_uri();
        let devItem = new PlaceDisplay(mountLabel,
               function() {
                        return Shell.TextureCache.get_default().load_gicon(mountIcon, PLACES_ICON_SIZE);
               },
               function() {
                        Gio.app_info_launch_default_for_uri(mountUri, Main.createAppLaunchContext());
               });
        this._devBox.append(devItem.actor, Big.BoxPackFlags.NONE);
    },

    _updateDesktopMenuVisibility: function() {
        let gconf = Shell.GConf.get_default();
        let desktopIsHome = gconf.get_boolean(DESKTOP_IS_HOME_KEY);
        if (desktopIsHome)
            this._desktopMenu.actor.hide();
        else
            this._desktopMenu.actor.show();
    }
};
Signals.addSignalMethods(Places.prototype);