/*
 * Zorin Menu: The official applications menu for Zorin OS.
 *
 * Copyright (C) 2016-2018 Zorin OS Technologies Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Import Libraries
const Lang = imports.lang;
const St = imports.gi.St;
const PopupMenu = imports.ui.popupMenu;
const Gtk = imports.gi.Gtk;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Signals = imports.signals;
const GnomeSession = imports.misc.gnomeSession;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']);
const _ = Gettext.gettext;
const Widgets = Me.imports.widgets;

// User Home directories
const DEFAULT_DIRECTORIES = [
    GLib.UserDirectory.DIRECTORY_DOCUMENTS,
    GLib.UserDirectory.DIRECTORY_DOWNLOAD,
    GLib.UserDirectory.DIRECTORY_MUSIC,
    GLib.UserDirectory.DIRECTORY_PICTURES,
    GLib.UserDirectory.DIRECTORY_VIDEOS
];

// Session Buttons Section
var SessionButtonsSection = new Lang.Class({
    Name: 'SessionButtonsSection',
    Extends: PopupMenu.PopupBaseMenuItem,

    // Initialize the button
    _init: function() {
        this.parent({
            reactive: false,
            can_focus: false
        });
        this._session = new GnomeSession.SessionManager();

        // Add session buttons to section
        this._power = new Widgets.PowerButton(this._session);
        this.actor.add_child(this._power.actor, {
            expand: true,
            x_fill: false,
            y_align: St.Align.START
        });

        this._lock = new Widgets.LockButton(this._session);
        this.actor.add_child(this._lock.actor, {
            expand: true,
            x_fill: false,
            y_align: St.Align.START
        });

        this._logout = new Widgets.LogoutButton(this._session);
        this.actor.add_child(this._logout.actor, {
            expand: true,
            x_fill: false,
            y_align: St.Align.START
        });
        // Connect to button signals
        this._power.connect('activated', Lang.bind(this, this._activated));
        this._lock.connect('activated', Lang.bind(this, this._activated));
        this._logout.connect('activated', Lang.bind(this, this._activated));
    },

    // Emit signal if one of the buttons is activated
    _activated: function() {
        this.emit('activated');
    }
});
Signals.addSignalMethods(SessionButtonsSection.prototype);


// Places Shortcut Section
var PlacesSection = new Lang.Class({
    Name: 'PlacesSection',

    _init: function(session, accessible_name, icon_name) {

        this.actor = new St.BoxLayout({
            vertical: true
        });
        this._items = [];

        let homePath = GLib.get_home_dir();
        let placeInfo = new Widgets.PlaceInfo(Gio.File.new_for_path(homePath), _("Home"));
        let placeMenuItem = new Widgets.PlaceMenuItem(placeInfo);
        this._items.push(placeMenuItem);

        for (let i = 0; i < DEFAULT_DIRECTORIES.length; i++) {
            let path = GLib.get_user_special_dir(DEFAULT_DIRECTORIES[i]);
            if (path == null || path == homePath)
                continue;
            let placeInfo = new Widgets.PlaceInfo(Gio.File.new_for_path(path));
            let placeMenuItem = new Widgets.PlaceMenuItem(placeInfo);
            this._items.push(placeMenuItem);
        }

        this._items.forEach(function(item) {
            this.actor.add_child(item.actor);
            item.connect('activated', Lang.bind(this, this._activated));
        }, this);
    },

    // Emit signal if one of the buttons is activated
    _activated: function() {
        this.emit('activated');
    }
});
Signals.addSignalMethods(PlacesSection.prototype);

// Shortcuts Section
var ShortcutsSection = new Lang.Class({
    Name: 'ShortcutsSection',

    _init: function(session, accessible_name, icon_name) {

        this.actor = new St.BoxLayout({
            vertical: true
        });
        this._items = [];

        let software = new Widgets.ShortcutMenuItem(_("Software"), "gnome-software-symbolic", "gnome-software");
        this._items.push(software);

        let settings = new Widgets.ShortcutMenuItem(_("Settings"), "preferences-system-symbolic", "gnome-control-center");
        this._items.push(settings);

        let activities = new Widgets.ActivitiesMenuItem();
        this._items.push(activities);

        this._items.forEach(function(item) {
            this.actor.add_child(item.actor);
            item.connect('activated', Lang.bind(this, this._activated));
        }, this);
    },

    // Emit signal if one of the buttons is activated
    _activated: function() {
        this.emit('activated');
    }
});
Signals.addSignalMethods(ShortcutsSection.prototype);

// Base Categories Section
var BaseCategoriesSection = new Lang.Class({
    Name: 'BaseCategoriesSection',
    Abstract: true,

    _init: function(appsBackend) {
        this.actor = new St.Bin();
        this._appsBackend = appsBackend;
        this._categories = [];
        this._categoryButtons = new Map();
        this._load();
        this._reloadId = this._appsBackend.connect('reload', Lang.bind(this, this._reload));
        this.actor.connect('destroy', this._onDestroy.bind(this));
    },

    _load: function() {
        this._categories = this._appsBackend.getCategories();
        this._categories.forEach(this._addCategoryButton, this);
    },

    _clear: function() {
        this.emit('cleared');
    },

    _reload: function() {
        this._categories = [];
        this._clear();
        this._load();
    },

    _addCategoryButton: function(category) {
        this.emit('category-added', category);
    },

    _selected: function(actor, category_menu_id) {
        this.emit('selected', category_menu_id);
    },

    // Scroll to a specific button (menu item) in the categories scroll view
    _scrollToButton: function(button) {
        this.emit('scrolled', button);
    },

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

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

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

    _onDestroy: function() {
        this._appsBackend.disconnect(this._reloadId);
        this._categories = null;
        this._categoryButtons.clear();
        this.emit('destroy');
    },
    
    destroy: function() {
        this.actor.destroy();
    }
});
Signals.addSignalMethods(BaseCategoriesSection.prototype);

// Categories List Section
var CategoriesListSection = new Lang.Class({
    Name: 'CategoriesListSection',
    Extends: BaseCategoriesSection,

    // Initialize the button
    _init: function(appsBackend) {
        this._categoriesBox = new St.BoxLayout({ vertical: true });
        this.parent(appsBackend);
        this._scrollBox = new St.ScrollView({
                x_fill: true,
                y_fill: false,
                y_align: St.Align.START,
                style_class: 'apps-menu vfade left-scroll-area'
        });
        this._scrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
        this._scrollBox.add_actor(this._categoriesBox);
        this.actor.set_child(this._scrollBox);
    },

    _addCategoryButton: function(category) {
        let button = this._categoryButtons.get(category);
        if (!button) {
            button = new Widgets.CategoryMenuItem(category);
            this._categoryButtons.set(category, button);
            button.connect('selected', Lang.bind(this, this._selected));
            button.connect('scroll', Lang.bind(this, this._scrollToButton));
        }
        if (!button.actor.get_parent()) {
            this._categoriesBox.add_child(button.actor);
        }
    },

    // Clear the categories box
    _clear: function() {
        this._categoriesBox.remove_all_children();
    },

    // Scroll to a specific button (menu item) in the categories scroll view
    _scrollToButton: function(button) {
        if (button) {
            let scrollBoxAdj = this._scrollBox.get_vscroll_bar().get_adjustment();
            let scrollBoxAlloc = this._scrollBox.get_allocation_box();
            let currentScrollValue = scrollBoxAdj.get_value();
            let boxHeight = scrollBoxAlloc.y2 - scrollBoxAlloc.y1;
            let buttonAlloc = button.actor.get_allocation_box();
            let newScrollValue = currentScrollValue;
            if (currentScrollValue > buttonAlloc.y1 - 10)
                newScrollValue = buttonAlloc.y1 - 10;
            if (boxHeight + currentScrollValue < buttonAlloc.y2 + 10)
                newScrollValue = buttonAlloc.y2 - boxHeight + 10;
            if (newScrollValue != currentScrollValue)
                scrollBoxAdj.set_value(newScrollValue);
        }
    },

    grab_key_focus: function() {
        let item = this._categoriesBox.get_first_child();
        if (item) {
            item.grab_key_focus();
        }
    },

    show: function() {
        this.parent();
        let item = this._categoriesBox.get_first_child();
        if (item) {
            item.grab_key_focus();
        }
    }
});

// Base Apps Section
var BaseAppsSection = new Lang.Class({
    Name: 'BaseAppsSection',
    Abstract: true,

    _init: function(appsBackend) {
        this.actor = new St.Bin();
        this._appsBackend = appsBackend;
        this._appButtons = new Map();
        this._category = null;
        this._searchPattern = null;
        this._load();
        this._reloadId = this._appsBackend.connect('reload', Lang.bind(this, this._reload));
        this.actor.connect('destroy', this._onDestroy.bind(this));
    },

    _display: function(apps) {
        if (apps) {
            apps.forEach(this._addAppButton, this);
        }
    },

    _clear: function() {
        this.emit('cleared');
    },

    _load: function() {
        if (this._category) {
            this.selectCategory(this._category);
        } else if (this._searchPattern) {
            this.searchApps(this._searchPattern);
        } else {
            let apps = this._appsBackend.getAllApps();
            this._display(apps);
        }
    },

    _reload: function() {
        this._clear();
        this._load();
    },

    _addAppButton: function(app) {
        this.emit('app-added', app);
    },

    // Emit signal if one of the buttons is activated
    _activated: function() {
        this.emit('activated');
    },

    // Scroll to a specific button (menu item) in the apps scroll view
    _scrollToButton: function(button) {
        this.emit('scrolled', button);
    },

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

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

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

    selectCategory: function(category_menu_id) {
        if (category_menu_id) {
            this._category = category_menu_id;
            this._searchPattern = null;
            let apps = this._appsBackend.getAppsByCategory(this._category);
            this._clear();
            this._display(apps);
        }
    },

    searchApps: function(pattern) {
        if (pattern) {
            this._searchPattern = pattern;
            this._category = null;
            let apps = this._appsBackend.searchApps(this._searchPattern);
            this._clear();
            this._display(apps);
        }
    },

    reset: function() {
        this._category = null;
        this._searchPattern = null;
        this._reload();
    },

    _onDestroy: function() {
        this._appsBackend.disconnect(this._reloadId);
        this._appButtons.clear();
        this.emit('destroy');
    },
    
    destroy: function() {
        this.actor.destroy();
    }
});
Signals.addSignalMethods(BaseAppsSection.prototype);

// Apps List Section
var AppsListSection = new Lang.Class({
    Name: 'AppsListSection',
    Extends: BaseAppsSection,

    // Initialize the button
    _init: function(appsBackend) {
        this._appsBox = new St.BoxLayout({ vertical: true });
        this.parent(appsBackend);
        this._scrollBox = new St.ScrollView({
                x_fill: true,
                y_fill: false,
                y_align: St.Align.START,
                style_class: 'apps-menu vfade left-scroll-area'
        });
        this._scrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
        this._scrollBox.add_actor(this._appsBox);
        this.actor.set_child(this._scrollBox);
    },

    _addAppButton: function(app) {
        let button = this._appButtons.get(app);
        if (!button) {
            button = new Widgets.AppMenuItem(app);
            this._appButtons.set(app, button);
            button.connect('activated', Lang.bind(this, this._activated));
            button.connect('scroll', Lang.bind(this, this._scrollToButton));
        }
        if (!button.actor.get_parent()) {
            this._appsBox.add_child(button.actor);
        }
    },

    // Clear the categories box
    _clear: function() {
        this._appsBox.remove_all_children();
    },

    // Scroll to a specific button (menu item) in the apps scroll view
    _scrollToButton: function(button) {
        if (button) {
            let scrollBoxAdj = this._scrollBox.get_vscroll_bar().get_adjustment();
            let scrollBoxAlloc = this._scrollBox.get_allocation_box();
            let currentScrollValue = scrollBoxAdj.get_value();
            let boxHeight = scrollBoxAlloc.y2 - scrollBoxAlloc.y1;
            let buttonAlloc = button.actor.get_allocation_box();
            let newScrollValue = currentScrollValue;
            if (currentScrollValue > buttonAlloc.y1 - 10)
                newScrollValue = buttonAlloc.y1 - 10;
            if (boxHeight + currentScrollValue < buttonAlloc.y2 + 10)
                newScrollValue = buttonAlloc.y2 - boxHeight + 10;
            if (newScrollValue != currentScrollValue)
                scrollBoxAdj.set_value(newScrollValue);
        }
    },

    grab_key_focus: function() {
        let item = this._appsBox.get_first_child();
        if (item) {
            item.grab_key_focus();
        }
    },

    show: function() {
        this.parent();
        let item = this._appsBox.get_first_child();
        if (item) {
            item.grab_key_focus();
        }
    }
});
