/*
 * Copyright (C) 2012 Thiago Bellini <hackedbellini@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Most of this code was forked from media-player-indicator:
 *   https://extensions.gnome.org/extension/55/media-player-indicator/
 *
 */

const GObject = imports.gi.GObject;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;

const Gettext = imports.gettext.domain('gnome-shell-notifications-alert');
const _ = Gettext.gettext;

const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Lib = Me.imports.lib;

const SETTING_BLACKLIST = 'application-list';
const SETTING_FILTER_TYPE = 'filter';

const Columns = {
  APPINFO: 0,
  DISPLAY_NAME: 1,
  ICON: 2,
};

let settings;
let boolSettings;
let intSettings;
let colorSettings;

function _createBoolSetting(setting) {
  let hbox = new Gtk.Box({orientation: Gtk.Orientation.HORIZONTAL});

  let settingLabel = new Gtk.Label({label: boolSettings[setting].label,
                                    xalign: 0});

  let settingSwitch = new Gtk.Switch({active: settings.get_boolean(setting)});
  settingSwitch.connect('notify::active', function(button) {
    settings.set_boolean(setting, button.active);
  });

  if (boolSettings[setting].help) {
    settingLabel.set_tooltip_text(boolSettings[setting].help);
    settingSwitch.set_tooltip_text(boolSettings[setting].help);
  }

  hbox.pack_start(settingLabel, true, true, 0);
  hbox.add(settingSwitch);

  return hbox;
}

function _createIntSetting(setting) {
  let hbox = new Gtk.Box({orientation: Gtk.Orientation.HORIZONTAL});

  let settingLabel = new Gtk.Label({label: intSettings[setting].label,
                                    xalign: 0});

  let spinButton = Gtk.SpinButton.new_with_range(
    intSettings[setting].min,
    intSettings[setting].max,
    intSettings[setting].step)
  spinButton.set_value(settings.get_int(setting));
  spinButton.connect('notify::value', function(spin) {
    settings.set_int(setting, spin.get_value_as_int());
  });

  if (intSettings[setting].help) {
    settingLabel.set_tooltip_text(intSettings[setting].help);
    spinButton.set_tooltip_text(intSettings[setting].help);
  }

  hbox.pack_start(settingLabel, true, true, 0);
  hbox.add(spinButton);

  return hbox;
}

function _createColorSetting(setting) {
  let hbox = new Gtk.Box({orientation: Gtk.Orientation.HORIZONTAL});

  let settingLabel = new Gtk.Label({label: colorSettings[setting].label,
                                    xalign: 0});

  let color = Lib.getColorByHexadecimal(settings.get_string(setting));
  let colorButton = new Gtk.ColorButton();
  colorButton.set_color(color);
  colorButton.connect('notify::color', function(button) {
    let hex = Lib.getHexadecimalByColor(button.get_color());
    settings.set_string(setting, hex);
  });

  if (colorSettings[setting].help) {
    settingLabel.set_tooltip_text(colorSettings[setting].help);
    colorButton.set_tooltip_text(colorSettings[setting].help);
  }

  hbox.pack_start(settingLabel, true, true, 0);
  hbox.add(colorButton);

  return hbox;
}

function _createFilterListSetting() {
  let settingLabel = new Gtk.Label({label: _("Filter List"), xalign: 0});
  let widget = new Widget();
  let blbox = new Gtk.Grid({column_spacing: 5, row_spacing: 5, margin: 0});
  blbox.attach(settingLabel,0,0,1,1);
  blbox.attach(widget,0,1,1,1);
  return blbox;
}

function _createFilterTypeSetting() {
  let hbox = new Gtk.Box({orientation: Gtk.Orientation.HORIZONTAL});
  let settingLabel = new Gtk.Label({label: _("Filter Type"), xalign: 0});

  let listStore = new Gtk.ListStore();
  listStore.set_column_types ([
    GObject.TYPE_STRING,
    GObject.TYPE_STRING]);

  listStore.insert_with_valuesv (-1,  [0, 1], [0, _("Blacklist")]);
  listStore.insert_with_valuesv (-1,  [0, 1], [1, _("Whitelist")]);

  let filterComboBox = new Gtk.ComboBox({ model: listStore });
  filterComboBox.set_active (settings.get_int(SETTING_FILTER_TYPE));
  filterComboBox.set_id_column(0);

  let rendererText = new Gtk.CellRendererText();
  filterComboBox.pack_start (rendererText, false);
  filterComboBox.add_attribute (rendererText, "text", 1);

  filterComboBox.connect('changed', function(entry) {
    let id = filterComboBox.get_active_id();
    if (id == null)
        return;
    settings.set_int(SETTING_FILTER_TYPE, id);
  });

  hbox.pack_start(settingLabel, true, true, 0);
  hbox.add(filterComboBox);
  return hbox;
}

/*
   Shell-extensions handlers
*/

function init() {
  Lib.initTranslations(Me);
  settings = Lib.getSettings(Me);

  colorSettings = {
    color: {
      label: _("Alert color"),
      help: _("The color used to paint the message on user's menu")
    },
    backgroundcolor: {
      label: _("Alert background color"),
      help: _("The background color used to paint the message on user's menu")
    },
  };

  intSettings = {
    blinkrate: {
      label: _("Blink rate (in ms)"),
      help: _("The rate that the alert blinks, in ms. 0 means no blink (default: 800)"),
      min: 0,
      max: 10000,
      step: 1
    },
  };

  boolSettings = {
    usecolor: {
      label: _("Use alert color"),
      help: _("Use the alert color for alert blinks (default: ON)")
    },
    usebackgroundcolor: {
      label: _("Use alert background color"),
      help: _("Use the alert background color for alert blinks (default: OFF)")
    },
    chatonly: {
      label: _("Only alert for chat notifications"),
      help: _("Only chat notifications (like Empathy ones) will get alerted (default: OFF)")
    },
    force: {
      label: _("Force alerting even when notifications are set to OFF"),
      help: _("Alert even if you set notifications to OFF on user menu (default: OFF)")
    },
  };
}


/*
   Blacklist widget
*/
const Widget = new GObject.Class({
  Name: 'NotificationsAlert.Prefs.BlackListWidget',
  GTypeName: 'NotificationsAlertBlackListPrefsWidget',
  Extends: Gtk.Grid,

  _init: function(params) {
    this.parent(params);
    this.set_orientation(Gtk.Orientation.VERTICAL);

    Lib.initTranslations(Me);
    this._settings = Lib.getSettings(Me);

    this._store = new Gtk.ListStore();
    this._store.set_column_types([Gio.AppInfo, GObject.TYPE_STRING, Gio.Icon]);

    let scrolled = new Gtk.ScrolledWindow({ shadow_type: Gtk.ShadowType.IN});
    scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
    scrolled.set_min_content_height(150);
    this.add(scrolled);

    this._treeView = new Gtk.TreeView({ model: this._store,
                                        hexpand: true, vexpand: true });
    this._treeView.get_selection().set_mode(Gtk.SelectionMode.SINGLE);

    let appColumn = new Gtk.TreeViewColumn({ expand: true, sort_column_id: Columns.DISPLAY_NAME,
                                             title: _("Application") });
    let iconRenderer = new Gtk.CellRendererPixbuf;
    appColumn.pack_start(iconRenderer, false);
    appColumn.add_attribute(iconRenderer, "gicon", Columns.ICON);
    let nameRenderer = new Gtk.CellRendererText;
    appColumn.pack_start(nameRenderer, true);
    appColumn.add_attribute(nameRenderer, "text", Columns.DISPLAY_NAME);
    this._treeView.append_column(appColumn);

    scrolled.add(this._treeView);

    let toolbar = new Gtk.Toolbar({ icon_size: Gtk.IconSize.SMALL_TOOLBAR });
    toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR);
    this.add(toolbar);

    let newButton = new Gtk.ToolButton({ icon_name: 'bookmark-new-symbolic',
                                         label: _("Add Rule"),
                                         is_important: true });
    newButton.connect('clicked', Lang.bind(this, this._createNew));
    toolbar.add(newButton);

    let delButton = new Gtk.ToolButton({ icon_name: 'edit-delete-symbolic'  });
      delButton.connect('clicked', Lang.bind(this, this._deleteSelected));
      toolbar.add(delButton);

    this._changedPermitted = true;
    this._refresh();
  },

  _createNew: function() {
    let dialog = new Gtk.Dialog({ title: _("Blacklist app"),
                                  transient_for: this.get_toplevel(),
                                  use_header_bar: true,
                                  modal: true });
    dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL);
    let addButton = dialog.add_button(_("Add"), Gtk.ResponseType.OK);
    dialog.set_default_response(Gtk.ResponseType.OK);

    dialog._appChooser = new Gtk.AppChooserWidget({ show_all: true });

    let lbl = new Gtk.Label({label: _("Choose an application to blacklist:"),
                             xalign: 0.5});
    let hbox = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL,
                            margin: 5});
    hbox.pack_start(lbl, false, true, 0);
    hbox.pack_start(dialog._appChooser, true, true, 0);
    dialog.get_content_area().pack_start(hbox, true, true, 0);

    dialog.connect('response', Lang.bind(this, function(dialog, id) {
      if (id != Gtk.ResponseType.OK) {
        dialog.destroy();
        return;
      }

      let appInfo = dialog._appChooser.get_app_info();
      if (!appInfo) return;

      if (this._checkId( appInfo.get_id())){
        dialog.destroy();
        return;
      }

      this._changedPermitted = false;
      this._appendItem(appInfo.get_id());
      this._changedPermitted = true;

      let iter = this._store.append();

      this._store.set(iter,
        [Columns.APPINFO, Columns.ICON, Columns.DISPLAY_NAME],
        [appInfo, appInfo.get_icon(), appInfo.get_display_name()]);

      dialog.destroy();
      }));

    dialog.show_all();
  },

  _deleteSelected: function() {
    let [any, model, iter] = this._treeView.get_selection().get_selected();

    if (any) {
      let appInfo = this._store.get_value(iter, Columns.APPINFO);

      this._changedPermitted = false;
      this._removeItem(appInfo.get_id());
      this._changedPermitted = true;
      this._store.remove(iter);
    }
  },

  _refresh: function() {
    if (!this._changedPermitted)
        // Ignore this notification, model is being modified outside
        return;

    this._store.clear();

    let currentItems = this._settings.get_strv(SETTING_BLACKLIST);
    let validItems = [ ];
    for (let i = 0; i < currentItems.length; i++) {
      let id = currentItems[i];
      let appInfo = Gio.DesktopAppInfo.new(id);
      if (!appInfo)
        continue;
      validItems.push(currentItems[i]);

      let iter = this._store.append();
      this._store.set(iter,
          [Columns.APPINFO, Columns.ICON, Columns.DISPLAY_NAME],
          [appInfo, appInfo.get_icon(), appInfo.get_display_name()]);
    }

    if (validItems.length != currentItems.length) // some items were filtered out
        this._settings.set_strv(SETTING_BLACKLIST, validItems);
  },

  _checkId: function(id) {
    let items = this._settings.get_strv(SETTING_BLACKLIST);
    return (items.indexOf(id) != -1);
  },

  _appendItem: function(id) {
    let currentItems = this._settings.get_strv(SETTING_BLACKLIST);
    currentItems.push(id);
    this._settings.set_strv(SETTING_BLACKLIST, currentItems);
  },

  _removeItem: function(id) {
    let currentItems = this._settings.get_strv(SETTING_BLACKLIST);
    let index = currentItems.indexOf(id);

    if (index < 0)
      return;
    currentItems.splice(index, 1);
    this._settings.set_strv(SETTING_BLACKLIST, currentItems);
  }
});

function buildPrefsWidget() {
  let frame = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL,
                           border_width: 10});
  let vbox = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL,
                          margin: 20, margin_top: 10, spacing: 5});
  let setting;

  // Add all color settings
  for (setting in colorSettings) {
    let hbox = _createColorSetting(setting);
    vbox.add(hbox);
  }
  // Add all bool settings
  for (setting in boolSettings) {
    let hbox = _createBoolSetting(setting);
    vbox.add(hbox);
  }
  // Add all int settings
  for (setting in intSettings) {
    let hbox = _createIntSetting(setting);
    vbox.add(hbox);
  }

  // Add filter type setting
  let filterType = _createFilterTypeSetting();
  vbox.add(filterType);

  // Add filter list
  let blbox = _createFilterListSetting();
  vbox.add(blbox);

  frame.add(vbox);
  frame.show_all();

  return frame;
}
