#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Faraday Penetration Test IDE
Copyright (C) 2016  Infobyte LLC (http://www.infobytesec.com/)
See the file 'doc/LICENSE' for the license information

"""
from __future__ import absolute_import
from past.builtins import basestring


import webbrowser
import gi  # pylint: disable=import-error
import os
from faraday.client.start_client import FARADAY_CLIENT_BASE
gi.require_version('Gtk', '3.0')

from faraday.client.persistence.server.server import ResourceDoesNotExist
from gi.repository import Gtk, GdkPixbuf, Gdk  # pylint: disable=import-error
from faraday.config.configuration import getInstanceConfiguration
from faraday.client.persistence.server.server import is_authenticated, login_user, get_user_info, check_server_url
from faraday.client.model import guiapi
from faraday.client.gui.gtk.decorators import scrollable

from faraday.client.gui.gtk.compatibility import CompatibleScrolledWindow as GtkScrolledWindow
from faraday.client.plugins import fplugin_utils

CONF = getInstanceConfiguration()


class PreferenceWindowDialog(Gtk.Dialog):
    """Sets up a preference dialog with basically nothing more than a
    label, a text entry to input your Faraday server IP and a couple of buttons.
    Takes a callback function to the mainapp so that it can refresh the
    workspace list and information"""

    def __init__(self, reload_ws_callback, connect_to_couch, parent):
        """Initializes the simple preferences dialog. If force is set to
        True, user will NOT be able to cancel the dialog and app_exit_callback
        must NOT be None"""

        Gtk.Dialog.__init__(self, title="Preferences")
        self.parent = parent
        self.set_modal(True)
        self.set_size_request(400, 100)
        self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
        self.set_transient_for(parent)
        self.reloadWorkspaces = reload_ws_callback
        self.connectCouchCallback = connect_to_couch

        main_box = self.get_content_area()

        ip_label = Gtk.Label()
        ip_label.set_text("Faraday Server IP or URL")
        main_box.pack_start(ip_label, True, False, 10)

        couch_uri = CONF.getServerURI()
        self.ip_entry = Gtk.Entry()
        text = couch_uri if couch_uri else "http://127.0.0.1:5050"
        self.ip_entry.set_text(text)
        main_box.pack_start(self.ip_entry, True, False, 10)

        button_box = Gtk.Box(spacing=6)
        main_box.pack_end(button_box, False, True, 10)

        OK_button = Gtk.Button.new_with_label("OK")
        OK_button.connect("clicked", self.on_click_ok)
        button_box.pack_start(OK_button, False, True, 10)
        cancel_button = Gtk.Button.new_with_label("Cancel")

        self.connect("key_press_event", key_reactions)
        cancel_button.connect("clicked", self.on_click_cancel)

        button_box.pack_end(cancel_button, False, True, 10)
        self.show_all()

    def on_click_ok(self, button=None):
        """Button is useless, only there because GTK likes it. Takes the
        repourl (Couch IP) from self.ip_entry and connect to it if possible.
        """
        repourl = self.ip_entry.get_text()

        if not check_server_url(repourl):
            errorDialog(self, "Could not connect to Faraday Server.",
                        ("Are you sure it is running and that the URL is correct?"))
            return False


        credentials_ok = self.credentialsOK(repourl)
        couch_connection_ok = self.connectCouchCallback(repourl, parent=self)
        if credentials_ok and couch_connection_ok:
            self.destroy()

    def credentialsOK(self, repourl):
        """Pops up a dialog (if necessary) to set up Faraday
        credentials. Dialog is a LoginDialog which emits a signal marked
        by 42 when the user clicks its button. The run method returns 42
        on that click. Function will listen for that 42 at most three times.
        It's a boolean function, return True if auth ok, False if not.
        Number 42 was chosen for obvious reasons :) """

        if is_authenticated(repourl, CONF.getDBSessionCookies()):
            return True

        # if that didn't work...
        loginDialog = LoginDialog(self)
        return loginDialog.run(3, repourl, self)


    def on_click_cancel(self, button=None):
        self.destroy()


class ForcePreferenceWindowDialog(PreferenceWindowDialog):
    """A _forced_ version of the preference window, which means
    the user won't be able to exit it by any key combo or any cancel
    button. The cancel button now should redirect to a callback to
    exit the application.
    """

    def __init__(self, reload_ws_callback, connect_to_couch, parent,
                 exit_faraday_callback):
        """Inits just the same as preference window dialog, but
        disconnect from key_reactions and connect to strict_key_reaction.
        Also connect destroy with the OK function: if the user manages
        to close the dialog, that'd be just as pressing OK"""
        PreferenceWindowDialog.__init__(self, reload_ws_callback,
                                        connect_to_couch,
                                        parent)

        self.set_deletable(False)
        self.exit_faraday = exit_faraday_callback
        self.disconnect_by_func(key_reactions)
        self.connect("key_press_event", strict_key_reactions)
        self.connect("delete_event", lambda _, __: True)

    def on_click_cancel(self, button=None):
        """Override on_click_cancel to make it exit Faraday."""
        self.exit_faraday(parent=self)


class LoginDialog(Gtk.Dialog):
    """A simple login dialog with a user and password"""
    def __init__(self, parent):
        Gtk.Dialog.__init__(self,
                            flags=Gtk.DialogFlags.MODAL,
                            buttons=("OK", Gtk.ResponseType.OK,
                                     "Cancel", Gtk.ResponseType.CANCEL))

        self.set_default_response(Gtk.ResponseType.OK)

        self.set_keep_above(True)

        self.set_transient_for(parent)
        content_area = self.get_content_area()

        instructions = Gtk.Label.new("Credentials needed to proceed."
                                     "You've got 3 tries.")
        instructions.set_line_wrap(True)
        instructions.set_max_width_chars(38)
        content_area.pack_start(instructions, True, True, 10)

        userBox = Gtk.Box()
        user_label = Gtk.Label()
        user_label.set_text("User:")
        self.user_entry = Gtk.Entry()
        self.user_entry.set_width_chars(24)
        self.user_entry.set_activates_default(True)
        userBox.pack_start(user_label, True, True, 3)
        userBox.pack_start(self.user_entry, False, False, 5)
        content_area.pack_start(userBox, True, True, 10)

        passwordBox = Gtk.Box()
        password_label = Gtk.Label()
        password_label.set_text("Password:")
        self.password_entry = Gtk.Entry()
        self.password_entry.set_visibility(False)
        self.password_entry.set_width_chars(24)
        self.password_entry.set_activates_default(True)
        passwordBox.pack_start(password_label, True, True, 3)
        passwordBox.pack_start(self.password_entry, False, False, 5)
        content_area.pack_start(passwordBox, True, True, 10)

        self.show_all()

    def getUser(self):
        if self.user_entry.get_text() is not None:
            res = self.user_entry.get_text()
        else:
            res = ""
        return res

    def getPassword(self):
        if self.password_entry.get_text() is not None:
            res = self.password_entry.get_text()
        else:
            res = ""
        return res

    def run(self, attempts, url, parent):
        for attempt in range(attempts):
            run = Gtk.Dialog.run(self)

            if run == Gtk.ResponseType.OK:
                newUser = self.getUser()
                newPass = self.getPassword()
                session_cookie = login_user(url, newUser, newPass)
                if not session_cookie:
                    if attempt != attempts-1:
                        errorDialog(self, ("Invalid credentials!. You "
                                           "have " +
                                           str(attempts-1-attempt) +
                                           " attempt(s) left."))
                else:

                    CONF.setDBUser(newUser)
                    CONF.setDBSessionCookies(session_cookie)

                    user_info = get_user_info()

                    self.destroy()

                    return True

            if run in [Gtk.ResponseType.CANCEL, -4]:
                # run returns -4 when escape key pressed
                self.exit()
                return False
        else:
            errorDialog(self, ("Invalid credentials after " +
                                 str(attempts) + " tries. " +
                                 "Check your credentials and try again."))
            self.exit()
            return False

    def exit(self):
        self.destroy()


class ForceLoginDialog(LoginDialog):
    """A simple login dialog with a user and password"""
    def __init__(self, parent, exit_faraday_callback):
        LoginDialog.__init__(self, parent)

        self.set_deletable(False)

        self.exit_faraday = exit_faraday_callback

    def exit(self, button=None):
        """Override destroy to make it exit Faraday."""
        self.exit_faraday()


class NewWorkspaceDialog(Gtk.Window):
    """Sets up the New Workspace Dialog, where the user can set a name,
    a description and a type for a new workspace. Also checks that the
    those attributes don't correspond to an existing workspace"""

    def __init__(self, create_ws_callback,  workspace_manager, sidebar, parent,
                 title=None):

        Gtk.Window.__init__(self, title="Create New Workspace")
        self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
        self.set_transient_for(parent)
        self.set_modal(True)
        self.connect("key_press_event", key_reactions)
        self.set_size_request(200, 200)
        self.create_ws_callback = create_ws_callback
        self.sidebar = sidebar
        self.workspace_manager = workspace_manager
        self.title = title

        self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)

        name_box = self.create_name_box()
        description_box = self.create_description_box()
        button_box = self.create_button_box()

        self.main_box.pack_start(name_box, False, False, 10)
        self.main_box.pack_start(description_box, False, False, 10)
        self.main_box.pack_end(button_box, False, False, 10)

        self.add(self.main_box)

    def create_name_box(self):
        """Return a box with a Name label left of an entry."""
        name_box = Gtk.Box(spacing=6)
        name_label = Gtk.Label()
        name_label.set_text("Name: ")
        self.name_entry = Gtk.Entry()
        if self.title is not None:
            self.name_entry.set_text(self.title)
        name_box.pack_start(name_label, False, False, 10)
        name_box.pack_end(self.name_entry, True, True, 10)
        return name_box

    def create_description_box(self):
        """Return a box with a Description label left of an entry."""
        description_box = Gtk.Box(spacing=6)
        description_label = Gtk.Label()
        description_label.set_text("Description: ")
        self.description_entry = Gtk.Entry()
        description_box.pack_start(description_label, False, False, 10)
        description_box.pack_end(self.description_entry, True, True, 10)
        return description_box

    def create_button_box(self):
        """Return a box with OK and cancel buttons."""
        button_box = Gtk.Box(spacing=6)
        OK_button = Gtk.Button.new_with_label("OK")
        OK_button.connect("clicked", self.on_click_ok)
        cancel_button = Gtk.Button.new_with_label("Cancel")
        cancel_button.connect("clicked", self.on_click_cancel)
        button_box.pack_start(OK_button, False, False, 10)
        button_box.pack_end(cancel_button, False, False, 10)
        return button_box

    def on_click_ok(self, button=None):
        """Check if the name provided for the WS is valid. If so,
        create it and add it to the sidebar. If not, show error.
        """
        ws_name = self.name_entry.get_text()
        if self.workspace_manager.isWorkspaceNameValid(ws_name):
            ws_desc = self.description_entry.get_text()
            creation_ok = self.create_ws_callback(ws_name, ws_desc)
            if creation_ok:
                self.sidebar.add_workspace(ws_name)
                self.destroy()
        else:
            errorDialog(self, "Invalid workspace name",
                        "A workspace must be named with "
                        "all lowercase letters (a-z), digi"
                        "ts(0-9) or any of the _$()+-/ "
                        "characters. The name has to start"
                        " with a lowercase letter")

    def on_click_cancel(self, button):
        self.destroy()


class ForceNewWorkspaceDialog(NewWorkspaceDialog):
    """A very similar class to new workspace dialog, but this one forces
    the user to do so."""

    def __init__(self, parent, create_ws_callback, workspace_manager, sidebar,
                 exit_faraday_callback):
        """Init new workspace dialog, but make it so you can't press the
        cancel button or press scape."""
        NewWorkspaceDialog.__init__(self, create_ws_callback, workspace_manager,
                                    sidebar, parent)
        self.set_deletable(False)
        self.set_keep_above(True)
        self.disconnect_by_func(key_reactions)
        self.connect("key_press_event", strict_key_reactions)
        self.connect("delete_event", lambda _, __: True)
        self.exit_faraday = exit_faraday_callback
        explanation_message = self.create_explanation_message()
        self.main_box.pack_start(explanation_message, True, True, 6)
        self.main_box.reorder_child(explanation_message, 0)

    def on_click_cancel(self, button):
        """Override parent's class cancel callback so it exits faraday."""
        self.exit_faraday(parent=self)

    def create_explanation_message(self):
        """Returns a simple explanatory message inside a Label"""
        message = Gtk.Label()
        message.set_text("There are no workspaces available. You must "
                         "create one to continue using Faraday.")
        return message


class PluginOptionsDialog(Gtk.Window):
    """The dialog where the user can see details about installed plugins.
    It is not the prettiest thing in the world but it works.
    Creating and displaying the models of each plugin settings is specially
    messy , there's more info in the appropiate methods"""

    def __init__(self, plugin_manager, parent):

        Gtk.Window.__init__(self, title="Plugins Options")
        self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
        self.set_transient_for(parent)
        self.set_modal(True)
        self.set_size_request(800, 300)
        self.plugin_manager = plugin_manager

        if plugin_manager is not None:
            self.plugin_settings = plugin_manager.getSettings()
        else:
            self.plugin_settings = {}

        plugin_info = self.createPluginInfo(plugin_manager)
        self.id_of_selected = plugin_info[0][1]  # default selected is first item in list
        plugin_list = self.createPluginListView(plugin_info)
        left_side_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        left_side_box.pack_start(plugin_list, True, True, 0)

        self.settings_view = None
        self.models = self.createPluginsSettingsModel()
        self.setSettingsView()

        buttonBox = Gtk.Box()
        OK_button = Gtk.Button.new_with_label("OK")
        cancel_button = Gtk.Button.new_with_label("Cancel")
        OK_button.connect("clicked", self.on_click_ok)
        cancel_button.connect("clicked", self.on_click_cancel)
        buttonBox.pack_start(OK_button, True, True, 10)
        buttonBox.pack_start(cancel_button, True, True, 10)

        left_side_box.pack_start(buttonBox, False, False, 10)

        infoBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        nameBox, versionBox, pluginVersionBox = [Gtk.Box() for i in range(3)]

        nameLabel, versionLabel, pluginVersionLabel = [Gtk.Label()
                                                       for i in range(3)]

        self.nameEntry, self.versionEntry, self.pluginVersionEntry = [
                Gtk.Label() for i in range(3)]

        nameLabel.set_text("Name: ")
        versionLabel.set_text("Version: ")
        pluginVersionLabel.set_text("Plugin version: ")

        nameBox.pack_start(nameLabel, False, False, 5)
        nameBox.pack_start(self.nameEntry, False, True, 5)
        versionBox.pack_start(versionLabel, False, False, 5)
        versionBox.pack_start(self.versionEntry, False, True, 5)
        pluginVersionBox.pack_start(pluginVersionLabel, False, False, 5)
        pluginVersionBox.pack_start(self.pluginVersionEntry, False, True, 5)

        infoBox.pack_start(nameBox, False, False, 5)
        infoBox.pack_start(versionBox, False, False, 5)
        infoBox.pack_start(pluginVersionBox, False, False, 5)

        self.pluginSpecsBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.pluginSpecsBox.pack_start(infoBox, False, False, 5)
        self.pluginSpecsBox.pack_start(self.settings_view, True, True, 0)

        self.mainBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        self.mainBox.pack_start(left_side_box, False, True, 10)
        self.mainBox.pack_end(self.pluginSpecsBox, True, True, 10)

        self.add(self.mainBox)

    def on_click_ok(self, button=None):
        """On click OK button update the plugins settings and then destroy"""
        if self.plugin_manager is not None:
            self.plugin_manager.updateSettings(self.plugin_settings)
        self.destroy()

    def on_click_cancel(self, button):
        """On click cancel button, destroy brutally. No mercy"""
        self.destroy()

    def createPluginInfo(self, plugin_manager):
        """Creates and return a TreeStore where the basic information about
        the plugins: the plugin ID, name, intended version of the tool
        and plugin version"""
        plugin_info = Gtk.TreeStore(str, str, str, str)

        for plugin_id, params in self.plugin_settings.items():
            plugin_info.append(None, [plugin_id,
                                      params["name"],
                                      params["version"],  # tool version
                                      params["plugin_version"]])

        # Sort it!
        sorted_plugin_info = Gtk.TreeModelSort(model=plugin_info)
        sorted_plugin_info.set_sort_column_id(1, Gtk.SortType.ASCENDING)
        return sorted_plugin_info

    @scrollable(width=300)
    def createPluginListView(self, plugin_info):
        """Creates the view for the left-hand side list of the dialog.
        It uses an instance of the plugin manager to get a list
        of all available plugins"""

        plugin_list_view = Gtk.TreeView(plugin_info)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Title", renderer, text=1)
        column.set_sort_column_id(1)
        plugin_list_view.append_column(column)

        selection = plugin_list_view.get_selection()
        selection.connect("changed", self.on_plugin_selection)

        return plugin_list_view

    def createPluginsSettingsModel(self):
        """Creates a dictionary with
        {plugin-name : [(setting-name, setting-value)]} structure. This is used
        to hold all the plugins settings models"""

        models = {}

        for plugin_id in self.plugin_settings.items():
            # iter through the plugins
            plugin_info = plugin_id[1]  # get dictionary associated to plugin
            store = Gtk.ListStore(str, str)  # create the store for that plugin

            # iter through settings dictionary
            for setting in plugin_info["settings"].items():
                setting_name = setting[0]
                setting_value = setting[1]
                store.append([setting_name, setting_value])

            models[plugin_id[1]["name"]] = store  # populate dict with store
        return models

    def createAdecuatePluginSettingView(self, store):
        """Create the adecuate plugin settings view. The first time this is
        executed, it will be none and it will tell the view which columns
        to display. After that, it will just change the model displayed"""
        self.active_store = store

        if self.settings_view is None:
            self.settings_view = Gtk.TreeView(store)
            renderer_text = Gtk.CellRendererText()
            column_text = Gtk.TreeViewColumn("Settings", renderer_text, text=0)
            self.settings_view.append_column(column_text)

            renderer_editable_text = Gtk.CellRendererText()
            renderer_editable_text.set_property("editable", True)
            renderer_editable_text.connect("edited", self.value_changed)
            column_editabletext = Gtk.TreeViewColumn("Value",
                                                     renderer_editable_text,
                                                     text=1)

            self.settings_view.append_column(column_editabletext)

        else:
            self.settings_view.set_model(store)

    def value_changed(self, widget, path, text):
        """Save new settings"""
        self.active_store[path][1] = text
        setting = self.active_store[path][0]
        settings = self.plugin_settings[self.name_of_selected]["settings"]
        settings[setting.strip()] = text.strip()

    def on_plugin_selection(self, selection):
        """When the user selects a plugin, it will change the text
        displeyed on the entries to their corresponding values"""

        # if the user searches for something that doesn't exists,
        # for example, the plugin 'jsaljfdlajs', this avoids
        # the program trying to get settings for that non-existing plugin
        try:
            model, treeiter = selection.get_selected()
            self.name_of_selected = model[treeiter][0]
            self.id_of_selected = model[treeiter][1]
            tool_version = model[treeiter][2]
            plugin_version = model[treeiter][3]

            self.setSettingsView()

            self.nameEntry.set_label(self.name_of_selected)

            if tool_version:
                self.versionEntry.set_label(tool_version)
            else:
                self.versionEntry.set_label("")

            if plugin_version:
                self.pluginVersionEntry.set_label(plugin_version)
            else:
                self.pluginVersionEntry.set_label("")
        except TypeError:
            pass

    def setSettingsView(self):
        """Makes the window match the selected plugin with the settings
        displayed"""

        adecuateModel = self.models[self.id_of_selected]
        self.createAdecuatePluginSettingView(adecuateModel)


class FaradayPluginsDialog(Gtk.Window):
    """The dialog where the user can see details about installed plugins.
    It is not the prettiest thing in the world but it works.
    Creating and displaying the models of each plugin settings is specially
    messy , there's more info in the appropiate methods"""

    def __init__(self, terminal, workspace_name, parent):

        Gtk.Window.__init__(self, title="Faraday Plugin")
        self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
        self.set_transient_for(parent)
        self.set_modal(True)
        self.set_size_request(800, 300)
        self._terminal = terminal
        self._workspace_name = workspace_name

        plugin_info = self.createPluginInfo()

        # self.id_of_selected = plugin_info[0][0]  # default selected is first item in list
        plugin_list = self.createPluginListView(plugin_info)
        left_side_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        left_side_box.pack_start(plugin_list, True, True, 0)

        buttonBox = Gtk.Box()
        append_button = Gtk.Button.new_with_label("Append")
        cancel_button = Gtk.Button.new_with_label("Cancel")
        append_button.connect("clicked", self.on_click_append)
        cancel_button.connect("clicked", self.on_click_cancel)
        buttonBox.pack_start(append_button, True, True, 10)
        buttonBox.pack_start(cancel_button, True, True, 10)

        left_side_box.pack_start(buttonBox, False, False, 10)

        infoBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        descriptionBox = Gtk.Box()

        descriptionLabel = Gtk.Label()

        self.descriptionEntry = Gtk.Label()

        descriptionLabel.set_text("Description: ")

        descriptionBox.pack_start(descriptionLabel, False, False, 5)
        descriptionBox.pack_start(self.descriptionEntry, False, True, 5)

        infoBox.pack_start(descriptionBox, False, False, 5)

        self.pluginSpecsBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.pluginSpecsBox.pack_start(infoBox, False, False, 5)

        self.mainBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        self.mainBox.pack_start(left_side_box, False, True, 10)
        self.mainBox.pack_end(self.pluginSpecsBox, True, True, 10)

        self.add(self.mainBox)

    def on_click_append(self, button=None):
        """On click OK button update the plugins settings and then destroy"""

        self.type_faraday_plugin_command(self.name_of_selected)

        self.destroy()

    def on_click_cancel(self, button):
        """On click cancel button, destroy brutally. No mercy"""
        self.destroy()

    def createPluginInfo(self):
        """Creates and return a TreeStore where the basic information about
        the plugins: the plugin ID, name, intended version of the tool
        and plugin version"""
        plugin_info = Gtk.TreeStore(str, str, str)

        for key, plugin_dic in fplugin_utils.get_available_plugins().items():
            plugin_info.append(None, [key,
                                      plugin_dic["description"],
                                      plugin_dic["prettyname"]
                                      ]
                               )

        # Sort it!
        sorted_plugin_info = Gtk.TreeModelSort(model=plugin_info)
        sorted_plugin_info.set_sort_column_id(2, Gtk.SortType.ASCENDING)
        return sorted_plugin_info

    @scrollable(width=300)
    def createPluginListView(self, plugin_info):
        """Creates the view for the left-hand side list of the dialog.
        It uses an instance of the plugin manager to get a list
        of all available plugins"""

        plugin_list_view = Gtk.TreeView(plugin_info)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Title", renderer, text=2)
        column.set_sort_column_id(1)
        plugin_list_view.append_column(column)

        selection = plugin_list_view.get_selection()
        selection.connect("changed", self.on_plugin_selection)

        return plugin_list_view

    def on_plugin_selection(self, selection):
        """When the user selects a plugin, it will change the text
        displeyed on the entries to their corresponding values"""

        # if the user searches for something that doesn't exists,
        # for example, the plugin 'jsaljfdlajs', this avoids
        # the program trying to get settings for that non-existing plugin
        try:
            model, treeiter = selection.get_selected()
            self.name_of_selected = model[treeiter][0]
            # self.id_of_selected = model[treeiter][1]
            description = model[treeiter][1]

            self.descriptionEntry.set_label(description)

        except TypeError:
            pass

    def type_faraday_plugin_command(self, plugin):

        command = fplugin_utils.build_faraday_plugin_command(plugin, self._workspace_name)
        fd = self._terminal.get_pty().get_fd()

        os.write(fd, command)


class HostInfoDialog(Gtk.Window):
    """Sets the blueprints for a simple host info window. It will display
    basic information in labels as well as services in a treeview.

    While working in this class, keep in mind the distinction between
    selections (which are part of a model that holds data about an object as
    strings and ints) and the object per se, which are in the model folder and
    are totally alien to GTK.
    """
    def __init__(self, parent, active_ws_name, host):
        """Creates a window with the information about a given hosts.
        The parent is needed so the window can set transient for
        """
        window_title = "Host " + host.name + " information"
        Gtk.Window.__init__(self, title=window_title)

        self.set_transient_for(parent)
        self.set_size_request(1200, 500)
        self.set_modal(True)
        self.connect("key_press_event", key_reactions)

        self.host = host
        self.model = self.create_model(self.host)
        host_info = self.model[0]

        host_id = self.model[0][0]
        couch_url = CONF.getServerURI()
        base_url = couch_url + "/_ui/#/host/ws/"
        self.edit_url = base_url + active_ws_name + "/hid/" + host_id

        host_info_frame = self.create_host_info_frame(host_info)

        self.specific_info = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.specific_info_frame = self.create_scroll_frame(
                                       self.specific_info,
                                       "Service Information")

        self.vuln_info = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.vuln_info_frame = self.create_scroll_frame(
                                            self.vuln_info,
                                            "Vulnerability Information")

        main_tree = self.create_main_tree_view(self.model)
        vuln_list = self.create_vuln_list()

        button_box = self.create_button_box()

        main_box = Gtk.Box()

        info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        info_box.pack_start(host_info_frame, True, True, 10)
        info_box.pack_start(self.specific_info_frame, True, True, 10)
        info_box.pack_start(self.vuln_info_frame, True, True, 10)
        info_box.pack_start(button_box, False, False, 10)

        main_tree_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        main_tree_box.pack_start(main_tree, True, True, 10)
        main_tree_box.pack_start(Gtk.Box(), False, False, 10)

        vuln_list_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vuln_list_box.pack_start(vuln_list, True, True, 10)
        vuln_list_box.pack_start(Gtk.Box(), False, False, 10)

        main_box.pack_start(main_tree_box, False, False, 5)
        main_box.pack_start(vuln_list_box, False, False, 0)
        main_box.pack_start(info_box, True, True, 5)

        self.add(main_box)

    def create_button_box(self):
        """Creates an horizontal box to hold the buttons."""
        button_box = Gtk.Box()

        ok_button = Gtk.Button.new_with_label("OK")
        ok_button.connect("clicked", self.on_click_ok)

        html_edit_url = '<a href="' + self.edit_url + '"> Edit host </a>'
        edit_button = Gtk.Button()
        edit_label = Gtk.Label()
        edit_label.set_markup(html_edit_url)
        edit_button.add(edit_label)
        edit_button.connect("clicked", self.on_edit_host)

        button_box.pack_start(edit_button, True, True, 0)
        button_box.pack_start(ok_button, True, True, 0)
        return button_box

    def on_edit_host(self, button):
        """Tries to open self.edit_url (url which directs to the host in the
        web ui) in the default browser."""
        webbrowser.open(self.edit_url, new=2)

    def create_scroll_frame(self, inner_box, label_str):
        """Create a scrollable frame containing inner_box and with label_str
        as its title.
        """

        label = Gtk.Label()
        label.set_markup("<big>" + label_str + "</big>")

        scroll_box = GtkScrolledWindow(None, None)
        scroll_box.set_overlay_scrolling(False)
        scroll_box.set_policy(Gtk.PolicyType.AUTOMATIC,
                              Gtk.PolicyType.ALWAYS)

        scroll_box.add(inner_box)

        frame = Gtk.Frame()
        frame.set_label_widget(label)
        frame.add(scroll_box)

        return frame

    def create_host_info_frame(self, host_info):
        """Return a box where the basic information about the host
        lives in labels. It include names, OS, Owned status and vulnerability
        count.
        """
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        prop_names = self.get_properties_names("Host")
        self.show_info_in_box(host_info, prop_names, box)

        host_info_frame = self.create_scroll_frame(box, "Host Information")

        return host_info_frame

    @scrollable(width=250)
    def create_vuln_list(self):
        """Creates a simple view of vulnerabilities for the object
        and returns a box containing it. The vuln_list will be a value of the
        instance.
        """

        self.vuln_list = Gtk.TreeView()
        self.vuln_list.set_activate_on_single_click(True)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Vulnerabilities", renderer, text=1)
        column.set_sort_column_id(1)
        self.vuln_list.append_column(column)
        self.vuln_list.set_search_column(1)

        vuln_selection = self.vuln_list.get_selection()
        vuln_selection.connect("changed", self.on_vuln_selection)

        return self.vuln_list

    def create_model(self, host):
        """Return a model for the given host. It holds quite a bit of info.
        It has 15 columns holding the host ID and name as parent,
        all the information about the services.

        The model is difficult to draw because of its nested nature, but
        you can think of it like this, keeping in mind each node has
        several columns

          HOST
                   ------------> SERVICE1
                   ------------> SERVICE2

        And so on and so on, like Zizek says.
        """

        # those are 13 strings
        model = Gtk.TreeStore(str, str, str, str, str, str, str,
                              str, str, str, str, str, str)

        # GTK is very strict about how many columns the model has.
        # only the ID and the name are needed, but i still need to 'fill'
        # the other columns with dummy info

        display_str = host.getName() + " (" + str(host.getVulnsAmount()) + ")"
        owned_status = ("Yes" if host.isOwned() else "No")
        host_position = model.append(None, [str(host.getID()), host.getName(),
                                            host.getOS(), owned_status,
                                            str(host.getVulnsAmount()), "",
                                            "", "", "", "", "", "",
                                            display_str])

        # some convenient functions just to separate and clarify what
        # the code does. they are used in the nested for loop directly
        # below them.

        def lst_to_str(lst):
            """Convenient function to avoid this long line everywhere"""
            return ', '.join([str(word) for word in lst if word])

        def add_service_to_host_in_model(service, model):
            """Append a service to an host in the given
            model. Return None. Modifies the model"""
            display_str = service.getName() + " (" + str(service.getVulnsAmount()) + ")"
            model.append(host_position, [str(service.getID()),
                                         service.getName(),
                                         service.getDescription(),
                                         service.getProtocol(),
                                         service.getStatus(),
                                         lst_to_str(service.getPorts()),
                                         service.getVersion(),
                                         "Yes" if service.isOwned() else "No",
                                         "", "", "", "", display_str])

        services = host.getServices()
        for service in services:
            add_service_to_host_in_model(service, model)

        return model

    @scrollable(width=250)
    def create_main_tree_view(self, model):
        """Return a box containing the main tree (the one showing
        Host/Services) as its content.
        """
        view = Gtk.TreeView(model)
        view.set_activate_on_single_click(True)
        view.set_search_column(-1)
        view.set_enable_tree_lines(True)
        view.expand_all()

        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Host/Services",
                                    renderer, text=12)

        view.append_column(column)
        view.set_expander_column(column)
        selection = view.get_selection()
        selection.connect("changed", self.on_main_tree_selection)

        return view

    def on_main_tree_selection(self, tree_selection):
        """Fire up neccesary actions when selection on the main tree changes"""
        model, tree_iter = tree_selection.get_selected()

        if tree_iter is None:
            # NOTE: GTK returns "None" on the tree_iter when
            # selection is changed with interactive search and everything
            # explodes. Just return False if that's the case.
            return False

        object_info = model[tree_iter]

        iter_depth = model.iter_depth(tree_iter)
        object_type = {0: 'Host', 1: 'Service'}[iter_depth]

        if object_type == 'Host':
            self.set_vuln_model(self.create_vuln_model(self.host))
            self.clear(self.specific_info)
            self.clear(self.vuln_info)

        elif object_type == 'Service':
            self.clear(self.specific_info)
            self.clear(self.vuln_info)
            self.change_label_in_frame(self.specific_info_frame, object_type)
            prop_names = self.get_properties_names(object_type)
            self.show_info_in_box(object_info, prop_names, self.specific_info)
            actual_object = self.get_object(object_info, object_type)
            if not actual_object:
                return None
            vuln_model = self.create_vuln_model(actual_object)
            self.set_vuln_model(vuln_model)

    def on_vuln_selection(self, vuln_selection):
        """Fill the vuln_info box with the vulnerability selected.

        The try/except block is necesary 'cause GTK
        is silly (ie: doesn't behave like it would be best for me now)
        and will emit the selection changed signal if the model
        changes even if nothing is selected.
        """
        model, vuln_iter = vuln_selection.get_selected()
        if vuln_iter is None:
            # NOTE: for some reason, GTK returns "None" on the tree_iter when
            # selection is changed with interactive search and everything
            # explodes. Just return False if that's the case.
            return False

        selected = model[vuln_iter]
        vuln_type = selected[0]
        self.clear(self.vuln_info)
        self.change_label_in_frame(self.vuln_info_frame,
                                   vuln_type)
        prop_names = self.get_properties_names(vuln_type)
        self.show_info_in_box(selected, prop_names,
                              self.vuln_info)

    def set_vuln_model(self, model):
        """Sets the vulnerability view to show the given model"""
        self.vuln_list.set_model(model)

    def create_vuln_model(self, obj):
        """Return the model for the vulnerabilities of the obj object.
        It will be sorted alphabetically.
        """
        def params_to_string(params):  # XXX
            """Converts params to a string, in case it gets here as a list.
            It's pretty anoyting, but needed for backwards compatibility.
            """
            if isinstance(params, basestring):
                params_string = params
            elif isinstance(params, list):
                params_string = " ".join(params)
            elif params is None:
                params_string = ""
            else:  # just make sure that if params is anything else just crash
                raise TypeError
            return params_string

        # those are 16 strings
        model = Gtk.ListStore(str, str, str, str, str, str, str, str,
                              str, str, str, str, str, str, str, str)

        vulns = obj.getVulns()
        for vuln in vulns:
            _type = vuln.class_signature
            if _type == "Vulnerability":
                # again filling up the model with dumb strings
                # because gtk
                model.append([_type, vuln.getName(),
                              vuln.getDescription(),
                              vuln.getData(),
                              vuln.getSeverity(),
                              ', '.join([str(v) for v in vuln.getRefs() if v]),
                              vuln.getStatus(),
                              "", "", "", "", "", "", "", "", ""])

            elif _type == "VulnerabilityWeb":
                model.append([_type, vuln.getName(),
                              vuln.getDescription(),
                              vuln.getData(),
                              vuln.getSeverity(),
                              ", ".join([str(v) for v in vuln.getRefs() if v]),
                              vuln.getPath(),
                              vuln.getWebsite(),
                              vuln.getRequest(),
                              vuln.getResponse(),
                              vuln.getMethod(),
                              vuln.getPname(),
                              params_to_string(vuln.getParams()),
                              vuln.getQuery(),
                              vuln.getStatus(),
                              ""])
        # sort it!
        sorted_model = Gtk.TreeModelSort(model=model)
        sorted_model.set_sort_column_id(1, Gtk.SortType.ASCENDING)

        return sorted_model

    def change_label_in_frame(self, frame, string):
        """Changes the label in the given frame to 'string Information'"""
        label = frame.get_label_widget()
        label.set_markup("<big>" + string + " " + "Information" + "</big>")

    def show_info_in_box(self, object_info, property_names, box):
        """Appends several boxes vertically to the box. The appended boxes will
        all contain two labels, together forming something like this:
        '<b>property_name:</b> object_info'. It will also append a separator
        on top of each one of these boxes.

        It is important to notice that the first element of object_info
        is ignored. This is because of how the models in this class contain
        information. Thus, there'll be as many of this small boxes as
        len(property_names) minus one.
        """

        for index, prop_name in enumerate(property_names, start=1):
            if index != 1:
                # do not append to the first prop_name
                separator = Gtk.Separator.new(orientation=Gtk.Orientation.HORIZONTAL)
                box.pack_start(separator, False, True, 0)

            prop_box = Gtk.Box()
            prop_value = object_info[index]

            prop_label = Gtk.Label()
            prop_label.set_markup("<b> %s </b>" % (prop_name))
            prop_label.set_selectable(True)

            value_label = Gtk.Label(prop_value)
            value_label.set_selectable(True)
            prop_box.pack_start(prop_label, False, False, 0)
            prop_box.pack_start(value_label, False, False, 0)
            box.pack_start(prop_box, True, True, 0)

        box.show_all()

    def get_object(self, selected_object, object_type):
        """Take a selection as selected_object and an object_type
        and return the actual object, not the model's selection.
        """
        def safely(func):
            def safe_wrapper(*args, **kwargs):
                try:
                    return func(*args, **kwargs)
                except (IndexError, ValueError):
                    dialog = errorDialog(self, ("There has been a problem. "
                                                "The object you clicked on "
                                                "does not exist anymore."))
                    self.destroy() # exit
            return safe_wrapper

        object_id = selected_object[0]
        object_ = None
        if object_type == 'Service':
            object_ = safely(self.host.getService)(object_id)

        return object_

    def get_properties_names(self, object_type):
        """Return a list with the property names for objects of type
        Service, Vulnerability and VulnerabilityWeb (passed as a
        string).
        """
        if object_type == "Host":
            property_names = ["Name: ", "OS: ", "Owned: ",
                              "Vulnerabilities: "]

        elif object_type == "Service":
            property_names = ["Name: ", "Description: ", "Protocol: ",
                              "Status: ", "Port: ", "Version: ", "Is Owned?: "]

        elif object_type == "Vulnerability":
            property_names = ["Name: ", "Description: ", "Data: ",
                              "Severity: ", "Refs: ", "Status: "]

        elif object_type == "VulnerabilityWeb":
            property_names = ["Name: ", "Description: ", "Data: ",
                              "Severity: ", "Refs: ", "Path: ",
                              "Website: ", "Request: ", "Response: ",
                              "Method: ", "Pname: ", "Params: ",
                              "Query: ", "Status: "]
        return property_names

    def clear(self, box):
        """Remove all the widgets from box."""

        def remove(widget, box):
            """Removes widget from box"""
            box.remove(widget)

        box.foreach(remove, box)

    def on_click_ok(self, button=None):
        self.destroy()


class ConflictsDialog(Gtk.Window):
    """Blueprints for a beautiful, colorful, gtk-esque conflicts
    dialog. The user is confronted with two objects, one at the left,
    one at the right, and is able to edit any of the object's properties,
    choosing either one of them with a button"""

    def __init__(self, conflicts, parent):
        """Inits the window with its title and size, presents the
        user with the first conflict found. If there aren't conflict
        an empty window will be presented"""

        Gtk.Window.__init__(self, title="Conflicts")
        self.set_transient_for(parent)
        self.set_size_request(600, 400)
        self.set_modal(True)
        self.conflicts = conflicts
        self.conflict_n = 0
        self.current_conflict = self.conflicts[self.conflict_n]
        self.view = None

        self.views_box = Gtk.Box()

        # TODO: FIX THIS
        # this is the wrong way to do it, I'm creating a useless gtk.tree
        # so I can know the user's default color background
        # that not being bad enought, get_background_color is deprecated
        dumpy_tree = Gtk.TreeView()
        style = dumpy_tree.get_style_context()
        self.bg_color = style.get_background_color(Gtk.StateFlags.NORMAL)
        self.bg_color = self.bg_color.to_string()

        button_box = self.create_buttons()

        self.models = self.create_conflicts_models(conflicts)
        self.set_conflict_view(self.conflict_n)
        self.current_conflict_model = self.models[self.conflict_n]

        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        main_box.pack_start(self.views_box, True, True, 5)
        main_box.pack_start(button_box, False, True, 5)

        self.add(main_box)

    def update_current_conflict_model(self):
        self.current_conflict_model = self.models[self.conflict_n]

    def update_current_conflict(self):
        self.current_conflict = self.conflicts[self.conflict_n]

    def create_buttons(self):
        """Creates and connects the button for the window"""
        button_box = Gtk.Box()
        keep_right = Gtk.Button.new_with_label("Keep RIGHT")
        keep_left = Gtk.Button.new_with_label("Keep LEFT")
        quit = Gtk.Button.new_with_label("Quit")

        keep_right.connect("clicked", self.save, "right")
        keep_left.connect("clicked", self.save, "left")
        quit.connect("clicked", self.on_quit)

        space = Gtk.Box()
        button_box.pack_start(quit, False, False, 5)
        button_box.pack_start(space, True, True, 5)
        button_box.pack_start(keep_left, False, False, 5)
        button_box.pack_start(keep_right, False, False, 5)
        return button_box

    def _next_conflict_or_close(self):
        """Move to next conflict and update current conflict View and model."""
        if len(self.conflicts) - 1 > self.conflict_n:
            self.conflict_n += 1
            self.update_current_conflict()
            self.update_current_conflict_model()
            self.set_conflict_view(self.conflict_n)
        else:
            self.destroy()

    def save(self, button, keeper):
        """Saves information to Faraday. Keeper is needed to know if user
        wanted to keep left or right view"""
        current_conflict_type = self.current_conflict.getModelObjectType()

        # right is represented by column 2 of the model, left by column 1
        if keeper == "right":
            n = 2
        elif keeper == "left":
            n = 1

        solution = {}
        for row in self.current_conflict_model:
            solution[row[0].lower()] = self.uncook(row[n], row[4])

        try:
            guiapi.resolveConflict(self.current_conflict, solution)
            self._next_conflict_or_close()
            # if this isn't the last conflict...

        except ValueError:
            dialog = Gtk.MessageDialog(self, 0,
                                       Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK,
                                       ("You tried to set some invalid "
                                        "information. Make sure all True/False"
                                        " settings are either True or False, "
                                        "all values that should be numbers are"
                                        " numbers, and so on."))
            dialog.run()
            dialog.destroy()

        except ResourceDoesNotExist: # TODO: revert this hack to prevent exception when
                         # fixing conflict of non existent object
            dialog = Gtk.MessageDialog(self, 0,
                                       Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK,
                                       ("It seems like this conflict does not "
                                        "exist anymore. Most probably someone "
                                        "deleted the conflicting object from "
                                        "the DB \n"
                                        "Moving on to the next conflict."))

            guiapi.conflictMissing(self.current_conflict)
            dialog.run()
            dialog.destroy()
            self._next_conflict_or_close()

    def on_quit(self, button):
        """Exits the window"""
        self.destroy()

    def set_conflict_view(self, conflict_n):
        """Creates two views for the model corresponding to the conflict number
        n. If first conflict, self.view will be none. If user is past the first
        conflict, self.view will not be none"""

        @scrollable()
        def make_scrollable(view):
            """Just a function to wrap around a target _view_ so as to use
            the scrollable decorator.
            """
            return view

        def on_selection(selection, target):
            """Connected to the view of both the view and the secon view.
            Target should be either 'first' or 'second' for clarity.
            Whenever a view selection changes, change the selection
            of target accordingly.
            """
            target = self.view if target == 'first' else self.second_view
            original_selection = selection.get_selected()[1]
            target_selection = target.get_selection()
            if original_selection is not None:
                target_selection.select_iter(original_selection)

        if self.view is None:

            renderer = Gtk.CellRendererText()

            original_renderer = Gtk.CellRendererText()
            original_renderer.set_property("editable", True)
            original_renderer.connect("edited", self.value_changed, "original")

            conflict_renderer = Gtk.CellRendererText()
            conflict_renderer.set_property("editable", True)
            conflict_renderer.connect("edited", self.value_changed, "conflict")

            prop_column = Gtk.TreeViewColumn("", renderer, text=0,
                                             background=3)

            obj_column = Gtk.TreeViewColumn("ORIGINAL", original_renderer,
                                            text=1, background=3)

            prop2_column = Gtk.TreeViewColumn("", renderer, text=0,
                                              background=3)
            obj2_column = Gtk.TreeViewColumn("CONFLICTING", conflict_renderer,
                                             text=2, background=3)

            self.view = Gtk.TreeView(self.models[conflict_n])
            self.view.append_column(prop_column)
            self.view.append_column(obj_column)
            self.second_view = Gtk.TreeView(self.models[conflict_n])

            self.second_view.append_column(prop2_column)
            self.second_view.append_column(obj2_column)

            view_selection = self.view.get_selection()
            second_view_selection = self.second_view.get_selection()
            view_selection.connect("changed", on_selection, 'second')
            second_view_selection.connect("changed", on_selection, 'first')

            scrolled_view = make_scrollable(self.view)
            second_scrolled_view = make_scrollable(self.second_view)

            self.views_box.pack_start(scrolled_view, True, True, 5)
            self.views_box.pack_start(second_scrolled_view, True, True, 5)

        else:
            self.view.set_model(self.models[conflict_n])
            self.second_view.set_model(self.models[conflict_n])

    def value_changed(self, widget, path, text, which_changed):
        """Sets the model to keep the information which the user gave on
        Return Key"""
        active_store = self.current_conflict_model
        if which_changed == "original":
            active_store[path][1] = text
        elif which_changed == "conflict":
            active_store[path][2] = text

    def create_conflicts_models(self, conflicts):
        """ Creates a list of models, one for each conflict. Each model has
        five columns, as shown in an example with only two rows below:
        | PROPERTY | OBJECT 1 | OBJECT 2 | ROW COLOR | INPUT TYPE |
        -----------------------------------------------------------
        | NAME     |    A     |    B     |  RED      |  STRING    |
        | PORTS    | 5050, 20 | 5050, 20 | WHITE     |  LIST      |
        ===========================================================
        ROW COLOR and INPUT TYPE are never shown to the user.
        """

        models = []
        for conflict in conflicts:
            model = Gtk.ListStore(str, str, str, str, str)
            obj1 = conflict.getFirstObject()
            obj2 = conflict.getSecondObject()
            conflict_type = conflict.getModelObjectType()

            if conflict_type == "Service":
                self.fill_service_conflict_model(model, obj1, obj2)
            elif conflict_type == "Host":
                self.fill_host_conflict_model(model, obj1, obj2)
            elif conflict_type == "Vulnerability":
                self.fill_vuln_conflict_model(model, obj1, obj2)
            elif conflict_type == "VulnerabilityWeb":
                self.fill_webvuln_conflict_model(model, obj1, obj2)

            models.append(model)

        return models

    def fill_service_conflict_model(self, model, obj1, obj2):
        """
        Precondition: the model has 5 string columns, obj1 && obj2 are services
        Will get a model and two objects and return a
        model with all the appropiate information"""
        attr = []
        for obj in [obj1, obj2]:
            attr.append((obj.getName(),
                         obj.getDescription(),
                         obj.getProtocol(),
                         obj.getStatus(),
                         obj.getPorts(),
                         obj.getVersion(),
                         obj.isOwned()))

        props = ["Name", "Description", "Protocol", "Status", "Ports",
                 "Version", "Owned"]

        model = self.fill_model_from_props_and_attr(model, attr, props)
        return model

    def fill_host_conflict_model(self, model, obj1, obj2):
        """
        Precondition: the model has 5 string columns, obj1 && obj2 are hosts
        Will get a model and two objects and return a
        model with all the appropiate information"""
        attr = []
        for obj in [obj1, obj2]:
            attr.append((obj.getName(),
                         obj.getDescription(),
                         obj.getOS(),
                         obj.isOwned()))

        props = ["Name", "Description", "OS", "Owned"]
        model = self.fill_model_from_props_and_attr(model, attr, props)
        return model

    def fill_vuln_conflict_model(self, model, obj1, obj2):
        """
        Precondition: the model has 5 string columns, obj1 && obj2 are vulns
        Will get a model and two objects and return a
        model with all the appropiate information"""
        attr = []
        for obj in [obj1, obj2]:
            attr.append((obj.getName(),
                         obj.getDescription(),
                         obj.getData(),
                         obj.getSeverity(),
                         obj.getRefs(),
                         obj.getResolution(),
                         obj.getStatus()))

        props = ["Name", "Desc", "Data", "Severity", "Refs", "Resolution", "Status"]
        model = self.fill_model_from_props_and_attr(model, attr, props)
        return model

    def fill_webvuln_conflict_model(self, model, obj1, obj2):
        """
        Precondition: the model has 5 string columns, obj1 && obj2 are web vuln
        Will get a model and two objects and return a
        model with all the appropiate information"""
        attr = []
        for obj in [obj1, obj2]:
            attr.append((obj.getName(),
                         obj.getDescription(),
                         obj.getData(),
                         obj.getSeverity(),
                         obj.getRefs(),
                         obj.getPath(),
                         obj.getWebsite(),
                         obj.getRequest(),
                         obj.getResponse(),
                         obj.getMethod(),
                         obj.getPname(),
                         obj.getParams(),
                         obj.getQuery(),
                         obj.getStatus()))

        props = ["Name", "Desc", "Data", "Severity", "Refs", "Path",
                 "Website", "Request", "Response", "Method", "Pname",
                 "Params", "Query", "Status"]

        model = self.fill_model_from_props_and_attr(model, attr, props)
        return model

    def fill_model_from_props_and_attr(self, model, attr, props):
        """Preconditions: the model has 5 string columns,
        len(attr[0]) == len(attr[1]) == len(props),
        type(attr[0][i]) == type(attr[1][i]) for every i
        attr is a list with two tuples. the first tuple holds info about obj1,
        the second about obj2, for example:
        [(name_obj1, ports_obj1), (name_obj2, porst_obj2)]
        props is the list with names of such attributes, for example:
        ["Name: ", "Ports: "]

        Will return a model filled up with information as detailed in
        self.create_conflicts_models.
        """

        def decide_type(raw_prop):
            """Returns the name of a type of an object.
            Keep in mind, type(type("a")) is Type,
                          type(type("a").__name__) is Str
            """
            type_as_string = type(first_raw_prop).__name__
            return type_as_string

        def decide_bg():
            """Decides which background should the row have depending on
            the users default theme (light, dark, or unknown abomination)
            Pretty ugly, but it works"""
            color = self.bg_color.split("(")[1]
            color = color.split(",")
            color1 = int(color[0])
            color2 = int(color[1])
            color3 = int(color[2][:-1:])

            # that weird string formats from rgb to hexa
            default_bg = '#%02x%02x%02x' % (color1, color2, color3)

            if color1 > 200 and color2 > 200 and color3 > 200:
                return "pink" if first_prop != sec_prop else default_bg
            elif color1 < 100 and color2 < 100 and color3 < 100:
                return "darkred" if first_prop != sec_prop else default_bg
            else:
                # if your theme doesn't go for either dark or light
                # just use that color, screw highlights
                return '#%02x%02x%02x' % (color1, color2, color3)

        for index, prop in enumerate(props):
            # remember props is a list like [(obj1_prop1, obj1_prop2...),
            #                                (obj2_prop1, obj2, prop2...)]
            first_raw_prop = attr[0][index]
            sec_raw_prop = attr[1][index]
            first_prop = self.cook(first_raw_prop)
            sec_prop = self.cook(sec_raw_prop)

            model.append([prop, first_prop, sec_prop,
                          decide_bg(),
                          decide_type(first_raw_prop)])

        return model

    def cook(self, raw_prop):
        """We need to cook our properties: not all of them are strings by
        default, and Gtk's models refuse to deal with lists or dictionaries.
        Returns a string from a list, a bool, a float, or a string.
        DO NOT use for dictionaries"""

        if type(raw_prop) is list:
            cooked_prop = ",".join([str(p) for p in raw_prop])

        elif type(raw_prop) is bool:
            cooked_prop = str(raw_prop)

        elif type(raw_prop) is int or type(raw_prop) is float:
            cooked_prop = str(raw_prop)

        else:
            cooked_prop = raw_prop

        return cooked_prop

    def uncook(self, prop, original_type):
        """We need to get our raw information again: Gtk may like strings,
        but Faraday needs lists, booleans, floats, and such.
        Do not try to use for dictionaries.
        """

        # XXX: params is a weird stupid thing that can come up as a string,
        # a list, a nonetype, whatever. but we're making it _always_ be a string
        # across faraday, so just force it to be so
        if prop == "params":
            original_type = "str"

        if original_type == "list":
            if prop:
                prop = prop.replace(" ", "")
                raw_prop = prop.split(",")
            else:
                raw_prop = []

        elif original_type == "bool":
            prop = prop.replace(" ", "")
            if prop.lower() == "true":
                raw_prop = True
            elif prop.lower() == "false":
                raw_prop = False

        elif original_type == "int":
            raw_prop = int(prop)

        elif original_type == "float":
            raw_prop = float(prop)

        elif original_type in ["str", "unicode"]:
            raw_prop = prop
        else:
            raw_prop = prop
        return raw_prop


class ForceChooseWorkspaceDialog(Gtk.Window):
    """A dialog to force the user to choose a workspace in case he suddenly
    finds himself without an active workspace.
    """

    def __init__(self, parent_window, workspaces_model, change_ws_callback):
        """Initializes a simple modal dialog which forces the user to choose
        a workspace from a list."""
        Gtk.Window.__init__(self, title="Choose a Workspace")
        self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
        self.set_deletable(False)
        self.set_transient_for(parent_window)
        self.set_modal(True)
        self.connect("key_press_event", strict_key_reactions)
        self.connect("delete_event", lambda _, __: True)

        self.change_ws_callback = change_ws_callback

        message = self.create_explanation_message()
        scroll_view = self.create_view(workspaces_model)
        button_box = self.create_button_box()

        content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        content_box.pack_start(message, True, True, 6)
        content_box.pack_start(scroll_view, True, True, 6)
        content_box.pack_start(button_box, True, True, 6)

        self.add(content_box)

    def create_button_box(self):
        button_box = Gtk.Box()
        OK_button = Gtk.Button.new_with_label("OK")
        OK_button.connect("clicked", self.on_click_ok)
        button_box.pack_start(OK_button, False, False, 6)
        button_box.pack_start(Gtk.Box(), True, True, 6)
        return button_box

    def create_explanation_message(self):
        """Returns a simple explanatory message inside a Label"""
        message = Gtk.Label()
        message.set_text("Your last workspace is not accessible. \n"
                         "You must select one of the below workspaces to "
                         "continue using Faraday. \n"
                         "If your problem persists, double check you are "
                         "logged in. You may want to start faraday with the "
                         "--login flag.")
        return message

    @scrollable(height=200)
    def create_view(self, workspace_model):
        """Returns and assigns to the instance a view listing all the
        workspaces names.
        """
        self.ws_view = Gtk.TreeView(workspace_model)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Workspaces", renderer, text=0)
        self.ws_view.append_column(column)
        return self.ws_view

    def on_click_ok(self, button=None):
        """On click ok, change the workspace in the app with out selection
        and destroy.
        """
        selection = self.ws_view.get_selection()
        model, iter_ = selection.get_selected()
        if model and iter_:
            ws_name = model[iter_][0]
            self.change_ws_callback(ws_name)
            self.destroy()


class NotificationsDialog(Gtk.Window):
    """Defines a simple notification dialog. It isn't much, really"""

    def __init__(self, view, callback, parent):
        Gtk.Window.__init__(self, title="Notifications")
        self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
        self.set_transient_for(parent)
        self.set_size_request(400, 200)
        self.set_modal(True)
        self.connect("key_press_event", key_reactions)
        self.destroy_notifications = callback

        scrolled_list = self.create_view_box(view)

        self.button = Gtk.Button()
        self.button.set_label("OK")
        self.button.connect("clicked", self.on_click_ok)

        self.mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.mainBox.pack_start(scrolled_list, True, True, 0)
        self.mainBox.pack_start(self.button, False, False, 0)

        self.add(self.mainBox)

    @scrollable(width=250, height=350)
    def create_view_box(self, view):
        return view

    def on_click_ok(self, button=None):
        self.destroy_notifications()
        self.destroy()


class aboutDialog(Gtk.AboutDialog):
    """The simple about dialog displayed when the user clicks on "about"
    ont the menu. Could be in application.py, but for consistency reasons
    its here"""
    def __init__(self, main_window):

        Gtk.AboutDialog.__init__(self, transient_for=main_window, modal=True)
        icons = os.path.join(FARADAY_CLIENT_BASE, "data", "images", "icons")
        faraday_icon = GdkPixbuf.Pixbuf.new_from_file(
            os.path.join(icons, "about.png"))
        self.set_logo(faraday_icon)
        self.set_program_name("Faraday")
        self.set_comments("Penetration Test IDE -"
                          " Infobyte LLC. - All rights reserved")
        faraday_website = "http://www.infobytesec.com/faraday.html"
        self.set_website(faraday_website)
        self.set_website_label("Learn more about Faraday")

class errorDialog(Gtk.MessageDialog):
    """A simple error dialog to show the user where things went wrong.
    Takes the parent window, (Gtk.Window or Gtk.Dialog, most probably)
    the error and explanation (strings, nothing fancy) as arguments"""

    def __init__(self, parent_window, error, explanation=None):
        Gtk.MessageDialog.__init__(self, parent_window, 0,
                                   Gtk.MessageType.ERROR,
                                   Gtk.ButtonsType.OK,
                                   error)
        if explanation is not None:
            self.format_secondary_text(explanation)
        self.run()
        self.destroy()


class ImportantErrorDialog(Gtk.Dialog):
    """Blueprints for an uncaught exception handler. Presents the
    traceback and has option to send error report to developers.
    """

    def __init__(self, parent_window, error):
        Gtk.Dialog.__init__(self, "Error!", parent_window, 0)
        self.add_button("Send report to developers...", 42)
        self.add_button("Ignore", 0)
        self.set_size_request(200, 200)

        textBuffer = Gtk.TextBuffer()
        textBuffer.set_text(error)

        text_view_box = self.create_text_view_box(textBuffer)

        content = self.get_content_area()

        content.pack_start(text_view_box, True, True, 0)
        self.show_all()

    @scrollable(width=200, height=200)
    def create_text_view_box(self, textBuffer):
        textView = Gtk.TextView()
        textView.set_editable(False)
        textView.set_buffer(textBuffer)
        return textView


def key_reactions(window, event):
    """Silly function to destroy a window on escape key, to use
    with all the dialogs that should be Gtk.Dialogs but are Gtk.Windows
    or with windows that are too complex for gtk dialogs but should behave
    as a dialog too"""
    key = Gdk.keyval_name(event.get_keyval()[1])
    if key == 'Escape':
        window.destroy()
        return True
    elif key == 'Return':
        window.on_click_ok()
        return True


def strict_key_reactions(window, event):
    """Similar to key_reactions, but will not let the user do anything but
    press return."""
    key = Gdk.keyval_name(event.get_keyval()[1])
    if key == 'Return':
        window.on_click_ok()
        return True
    else:
        return False


# I'm Py3
