#!/usr/bin/python3
# coding: utf-8
#
# A simple indicator applet displaying cpu and memory information
#
# Author: Alex Eftimie <alex@eftimie.ro>
# Fork Author: fossfreedom <foss.freedom@gmail.com>
# Original Homepage: http://launchpad.net/indicator-sysmonitor
# Fork Homepage: https://github.com/fossfreedom/indicator-sysmonitor
# License: GPL v3
#

import shutil
import re
import os
from gettext import gettext as _

from gi.repository import Gtk
from gi.repository import Gio

from sensors import SensorManager
from sensors import ISMError

VERSION = '0.8.3'


def raise_dialog(parent, flags, type_, buttons, msg, title):
    """It raise a dialog. It a blocking function."""
    dialog = Gtk.MessageDialog(
        parent, flags, type_, buttons, msg)
    dialog.set_title(title)
    dialog.run()
    dialog.destroy()


class SensorsListModel(object):
    """A TreeView showing the available sensors. It allows to
    add/edit/delete custom sensors."""

    def __init__(self, parent):
        self.ind_parent = parent
        self._list_store = Gtk.ListStore(str, str)
        self._tree_view = Gtk.TreeView(self._list_store)

        self.sensor_mgr = SensorManager()
        self.sensor_mgr.fill_liststore(self._list_store)

    def get_view(self):
        """It's called from Preference. It creates the view and returns it"""
        vbox = Gtk.VBox(False, 3)
        # create columns
        renderer = Gtk.CellRendererText()
        renderer.set_property('editable', False)
        column = Gtk.TreeViewColumn(_('Sensor'), renderer, text=0)
        self._tree_view.append_column(column)

        renderer = Gtk.CellRendererText()
        renderer.set_property('editable', False)
        column = Gtk.TreeViewColumn(_('Description'), renderer, text=1)
        self._tree_view.append_column(column)

        self._tree_view.expand_all()
        sw = Gtk.ScrolledWindow()
        sw.add_with_viewport(self._tree_view)
        vbox.pack_start(sw, True, True, 0)

        # add buttons
        hbox = Gtk.HBox()
        new_button = Gtk.Button.new_from_stock(Gtk.STOCK_NEW)
        new_button.connect('clicked', self._on_edit_sensor)
        hbox.pack_start(new_button, False, False, 0)

        edit_button = Gtk.Button.new_from_stock(Gtk.STOCK_EDIT)
        edit_button.connect('clicked', self._on_edit_sensor, False)
        hbox.pack_start(edit_button, False, False, 1)

        del_button = Gtk.Button.new_from_stock(Gtk.STOCK_DELETE)
        del_button.connect('clicked', self._on_del_sensor)
        hbox.pack_start(del_button, False, False, 2)

        add_button = Gtk.Button.new_from_stock(Gtk.STOCK_ADD)
        add_button.connect('clicked', self._on_add_sensor)
        hbox.pack_end(add_button, False, False, 3)
        vbox.pack_end(hbox, False, False, 1)

        frame = Gtk.Frame.new(_('Sensors'))
        frame.add(vbox)
        return frame

    def _get_selected_row(self):
        """Returns an iter for the selected rows in the view or None."""
        model, pathlist = self._tree_view.get_selection().get_selected_rows()
        if len(pathlist):
            path = pathlist.pop()
            return model.get_iter(path)
        return None

    def _on_add_sensor(self, evnt=None, data=None):
        tree_iter = self._get_selected_row()
        if tree_iter is None:
            return

        sensor = self._list_store.get_value(tree_iter, 0)
        self.ind_parent.custom_entry.insert_text(
            "{{{}}}".format(sensor), -1)

    def _on_edit_sensor(self, evnt=None, blank=True):
        """Raises a dialog with a form to add/edit a sensor"""
        name = desc = cmd = ""
        tree_iter = None
        if not blank:
            # edit, so get the info from the selected row
            tree_iter = self._get_selected_row()
            if tree_iter is None:
                return

            name = self._list_store.get_value(tree_iter, 0)
            desc = self._list_store.get_value(tree_iter, 1)
            cmd = self.sensor_mgr.get_command(name)

            if cmd is True:  # default sensor
                raise_dialog(
                    self.ind_parent,
                    Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
                    Gtk.MessageType.ERROR, Gtk.ButtonsType.OK,
                    _("Can not edit the default sensors."), _("Error"))
                return

        dialog = Gtk.Dialog(_("Edit Sensor"), self.ind_parent,
                            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
                            (Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT,
                             Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT))
        vbox = dialog.get_content_area()

        hbox = Gtk.HBox()
        label = Gtk.Label(_("Sensor"))
        sensor_entry = Gtk.Entry()
        sensor_entry.set_text(name)
        hbox.pack_start(label, False, False, 0)
        hbox.pack_end(sensor_entry, False, False, 1)
        vbox.pack_start(hbox, False, False, 0)

        hbox = Gtk.HBox()
        label = Gtk.Label(_("Description"))
        desc_entry = Gtk.Entry()
        desc_entry.set_text(desc)
        hbox.pack_start(label, False, False, 0)
        hbox.pack_end(desc_entry, False, False, 1)
        vbox.pack_start(hbox, False, False, 1)

        hbox = Gtk.HBox()
        label = Gtk.Label(_("Command"))
        cmd_entry = Gtk.Entry()

        cmd_entry.set_text(cmd)
        hbox.pack_start(label, False, False, 0)
        hbox.pack_end(cmd_entry, False, False, 1)
        vbox.pack_end(hbox, False, False, 2)

        dialog.show_all()
        response = dialog.run()

        if response == Gtk.ResponseType.ACCEPT:
            try:
                newname, desc, cmd = str(sensor_entry.get_text()), \
                                     str(desc_entry.get_text()), str(cmd_entry.get_text())

                if blank:
                    self.sensor_mgr.add(newname, desc, cmd)
                else:
                    self.sensor_mgr.edit(name, newname, desc, cmd)
                    self._list_store.remove(tree_iter)

                self._list_store.append([newname, desc])
                # issue 3: why we are doing a character replacement when clicking
                # new - who knows ... lets just comment this out
                # ctext = self.ind_parent.custom_entry.get_text()

                # self.ind_parent.custom_entry.set_text(
                #    ctext.replace(name, newname))

            except ISMError as ex:
                raise_dialog(
                    self.ind_parent,
                    Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
                    Gtk.MessageType.ERROR, Gtk.ButtonsType.OK,
                    ex, _("Error"))

        dialog.destroy()

    def _on_del_sensor(self, evnt=None, data=None):
        """Remove a custom sensor."""
        tree_iter = self._get_selected_row()
        if tree_iter is None:
            return

        name = self._list_store.get_value(tree_iter, 0)
        try:
            self.sensor_mgr.delete(name)
            self._list_store.remove(tree_iter)
            ctext = self.ind_parent.custom_entry.get_text()
            self.ind_parent.custom_entry.set_text(
                ctext.replace("{{{}}}".format(name), ""))

        except ISMError as ex:
            raise_dialog(
                self.ind_parent,
                Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
                Gtk.MessageType.ERROR, Gtk.ButtonsType.OK,
                ex, _("Error"))


class Preferences(Gtk.Dialog):
    """It define the the Preferences Dialog and its operations."""
    AUTOSTART_DIR = '{}/.config/autostart' \
        .format(os.getenv("HOME"))
    AUTOSTART_PATH = '{}/.config/autostart/indicator-sysmonitor.desktop' \
        .format(os.getenv("HOME"))
    DESKTOP_PATH = '/usr/share/applications/indicator-sysmonitor.desktop'
    sensors_regex = re.compile("{.+?}")

    SETTINGS_FILE = os.getenv("HOME") + '/.cache/indicator-sysmonitor/preferences.json'
    settings = {}

    def __init__(self, parent):
        """It creates the widget of the dialogs"""
        Gtk.Dialog.__init__(self)
        self.ind_parent = parent
        self.custom_entry = None
        self.interval_entry = None
        self.sensor_mgr = SensorManager()
        self._create_content()
        self.set_data()
        self.show_all()

        # not implemented yet - just hide
        self.display_icon_checkbutton.set_visible(False)
        self.iconpath_button.set_visible(False)
        self.iconpath_entry.set_visible(False)

    def _create_content(self):
        """It creates the content for this dialog."""
        self.connect('delete-event', self.on_cancel)
        self.set_title(_('Preferences'))
        self.set_size_request(600, 600)
        self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)

        ui = Gtk.Builder()
        file_path = os.path.dirname(os.path.abspath(__file__))
        ui.add_from_file(file_path + '/preferences.ui')
        self.autostart_check = ui.get_object('autostart_check')
        self.autostart_check.set_active(self.get_autostart())
        version_label = ui.get_object('version_label')
        version_label.set_label(_('This is indicator-sysmonitor version: {}').format(VERSION))
        self.custom_entry = ui.get_object('custom_entry')
        self.interval_entry = ui.get_object('interval_entry')

        self.display_icon_checkbutton = ui.get_object('display_icon_checkbutton')
        self.iconpath_entry = ui.get_object('iconpath_entry')
        self.iconpath_button = ui.get_object('iconpath_button')

        sensors_list = SensorsListModel(self)
        vbox = ui.get_object('advanced_box')
        vbox.pack_start(sensors_list.get_view(), True, True, 3)

        # footer {{{
        vbox = self.get_content_area()
        notebook = ui.get_object('preferences_notebook')
        vbox.pack_start(notebook, True, True, 4)
        handlers = {
            "on_test": self.on_test,
            "on_save": self.on_save,
            "on_cancel": self.on_cancel
        }
        ui.connect_signals(handlers)
        buttons = ui.get_object('footer_buttonbox')
        vbox.pack_end(buttons, False, False, 5)
        # }}}
        
        self.set_resizable(False)

    def save_prefs(self):
            """It stores the current settings to the config file."""

            try:
                os.makedirs(os.path.dirname(Preferences.PREF_SETTINGS_FILE), exist_ok=True)
                with open(Preferences.PREF_SETTINGS_FILE, 'w') as f:
                    f.write(json.dumps(self.pref_settings))

            except Exception as ex:
                logging.exception(ex)
                logging.error('Writing settings failed')

    def load_settings(self):
            """It gets the settings from the config file and
            sets them to the correct vars"""
            try:
                with open(Preferences.PREF.SETTINGS_FILE, 'r') as f:
                    self.settings = json.load(f)

            except Exception as ex:
                logging.exception(ex)
                logging.error('Reading settings failed')

    def on_iconpath_button_clicked(self, *args):
        pass

    def on_display_icon_checkbutton_toggled(self, *args):
        if not self.display_icon_checkbutton.get_active():
            self.iconpath_entry.set_text('')
            self.iconpath_entry.set_sensitive(False)
            self.iconpath_button.set_sensitive(False)
        else:
            self.iconpath_entry.set_sensitive(True)
            self.iconpath_button.set_sensitive(True)

    def on_test(self, evnt=None, data=None):
        """The action of the test button."""
        try:
            self.update_parent()
        except Exception as ex:
            error_dialog = Gtk.MessageDialog(
                None, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR,
                Gtk.ButtonsType.CLOSE, ex)
            error_dialog.set_title("Error")
            error_dialog.run()
            error_dialog.destroy()
            return False

    def on_save(self, evnt=None, data=None):
        """The action of the save button."""
        try:
            self.update_parent()
        except Exception as ex:
            error_dialog = Gtk.MessageDialog(
                None, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR,
                Gtk.ButtonsType.CLOSE, ex)
            error_dialog.set_title("Error")
            error_dialog.run()
            error_dialog.destroy()
            return False

        self.ind_parent.save_settings()
        self.update_autostart()
        self.destroy()

    def on_cancel(self, evnt=None, data=None):
        """The action of the cancel button."""
        self.ind_parent.load_settings()
        self.destroy()

    def update_parent(self, evnt=None, data=None):
        """It gets the config info from the widgets and sets them to the vars.
        It does NOT update the config file."""
        custom_text = self.custom_entry.get_text()

        # check if the sensors are supported
        sensors = Preferences.sensors_regex.findall(custom_text)
        for sensor in sensors:
            sensor = sensor[1:-1]
            if not self.sensor_mgr.exists(sensor):
                raise ISMError(_("{{{}}} sensor not supported.").
                               format(sensor))
            # Check if the sensor is well-formed
            self.sensor_mgr.check(sensor)

        try:
            interval = int(self.interval_entry.get_text())
            if interval <= 0:
                raise ISMError(_("Interval value is not valid."))

        except ValueError:
            raise ISMError(_("Interval value is not valid."))

        self.sensor_mgr.set_custom_text(custom_text)
        self.sensor_mgr.set_interval(interval)
        # settings["custom_text"] = custom_text
        # settings["interval"] = interval
        # TODO: on_startup
        self.ind_parent.update_settings()
        self.ind_parent.update_indicator_guide()

    def set_data(self):
        """It sets the widgets with the config data."""
        self.custom_entry.set_text(self.sensor_mgr.get_custom_text())
        self.interval_entry.set_text(str(self.sensor_mgr.get_interval()))

    def update_autostart(self):
        autostart = self.autostart_check.get_active()
        if not autostart:
            try:
                os.remove(Preferences.AUTOSTART_PATH)
            except:
                pass
        else:
            try:
                if not os.path.exists(Preferences.AUTOSTART_DIR):
                    os.makedirs(Preferences.AUTOSTART_DIR)

                shutil.copy(Preferences.DESKTOP_PATH,
                            Preferences.AUTOSTART_PATH)
            except Exception as ex:
                logging.exception(ex)

    def get_autostart(self):
        return os.path.exists(Preferences.AUTOSTART_PATH)