# Copyright (C) 2009, 2013, 2014 Red Hat, Inc.
# Copyright (C) 2009 Cole Robinson <crobinso@redhat.com>
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.

from gi.repository import Gtk

from virtinst import log

from .asyncjob import vmmAsyncJob


####################################################################
# Build toolbar shutdown button menu (manager and details toolbar) #
####################################################################


class _VMMenu(Gtk.Menu):
    def __init__(self, src, current_vm_cb, show_open=True):
        Gtk.Menu.__init__(self)
        self._parent = src
        self._current_vm_cb = current_vm_cb
        self._show_open = show_open

        self._init_state()

    def _add_action(self, label, widgetname, cb):
        item = Gtk.MenuItem.new_with_mnemonic(label)

        item.vmm_widget_name = widgetname
        if cb:

            def _cb(_menuitem):
                _vm = self._current_vm_cb()
                if _vm:
                    return cb(self._parent, _vm)

            item.connect("activate", _cb)

        self.add(item)
        return item

    def _init_state(self):
        raise NotImplementedError()

    def update_widget_states(self, vm):
        raise NotImplementedError()


class VMShutdownMenu(_VMMenu):
    """
    Shutdown submenu for reboot, forceoff, reset, etc.
    """

    def _init_state(self):
        self._add_action(_("_Reboot"), "reboot", VMActionUI.reboot)
        self._add_action(_("_Shut Down"), "shutdown", VMActionUI.shutdown)
        self._add_action(_("F_orce Reset"), "reset", VMActionUI.reset)
        self._add_action(_("_Force Off"), "destroy", VMActionUI.destroy)
        self.add(Gtk.SeparatorMenuItem())
        self._add_action(_("Sa_ve"), "save", VMActionUI.save)

        self.get_accessible().set_name("vmm-shutdown-menu")
        self.show_all()

    def update_widget_states(self, vm):
        statemap = {
            "reboot": bool(vm and vm.is_stoppable()),
            "shutdown": bool(vm and vm.is_stoppable()),
            "reset": bool(vm and vm.is_stoppable()),
            "destroy": bool(vm and vm.is_destroyable()),
            "save": bool(vm and vm.is_destroyable()),
        }

        for child in self.get_children():
            name = getattr(child, "vmm_widget_name", None)
            if name in statemap:
                child.set_sensitive(statemap[name])


class VMActionMenu(_VMMenu):
    """
    VM submenu for run, pause, shutdown, clone, etc
    """

    def _init_state(self):
        self._add_action(_("_Run"), "run", VMActionUI.run)
        self._add_action(_("_Pause"), "suspend", VMActionUI.suspend)
        self._add_action(_("R_esume"), "resume", VMActionUI.resume)
        s = self._add_action(_("_Shut Down"), "shutdown", None)
        s.set_submenu(VMShutdownMenu(self._parent, self._current_vm_cb))

        self.add(Gtk.SeparatorMenuItem())
        self._add_action(_("Clone..."), "clone", VMActionUI.clone)
        self._add_action(_("Migrate..."), "migrate", VMActionUI.migrate)
        self._add_action(_("_Delete"), "delete", VMActionUI.delete)

        if self._show_open:
            self.add(Gtk.SeparatorMenuItem())
            self._add_action(_("_Open"), "show", VMActionUI.show)

        self.get_accessible().set_name("vm-action-menu")
        self.show_all()

    def update_widget_states(self, vm):
        statemap = {
            "run": bool(vm and vm.is_runable()),
            "shutdown": bool(vm and vm.is_stoppable()),
            "suspend": bool(vm and vm.is_stoppable()),
            "resume": bool(vm and vm.is_paused()),
            "migrate": bool(vm and vm.is_stoppable()),
            "clone": bool(vm and vm.is_cloneable()),
        }
        vismap = {
            "suspend": bool(vm and not vm.is_paused()),
            "resume": bool(vm and vm.is_paused()),
        }

        for child in self.get_children():
            name = getattr(child, "vmm_widget_name", None)
            if child.get_submenu():
                child.get_submenu().update_widget_states(vm)
            if name in statemap:
                child.set_sensitive(statemap[name])
            if name in vismap:
                child.set_visible(vismap[name])

    def change_run_text(self, text):
        for child in self.get_children():
            if getattr(child, "vmm_widget_name", None) == "run":
                child.get_child().set_label(text)


class VMActionUI:
    """
    Singleton object for handling VM actions, asking for confirmation,
    showing errors/progress dialogs, etc.
    """

    @staticmethod
    def save_cancel(asyncjob, vm):
        log.debug("Cancelling save job")
        if not vm:
            return  # pragma: no cover

        try:
            vm.abort_job()
        except Exception as e:
            log.exception("Error cancelling save job")
            asyncjob.show_warning(_("Error cancelling save job: %s") % str(e))
            return

        asyncjob.job_canceled = True  # pragma: no cover

    @staticmethod
    def save(src, vm):
        if not src.err.chkbox_helper(
            src.config.get_confirm_poweroff,
            src.config.set_confirm_poweroff,
            text1=_("Are you sure you want to save '%s'?") % vm.get_name(),
        ):
            return

        _cancel_cb = None
        if vm.supports_domain_job_info():
            _cancel_cb = (VMActionUI.save_cancel, vm)

        def cb(asyncjob):
            vm.save(meter=asyncjob.get_meter())

        def finish_cb(error, details):
            if error is not None:  # pragma: no cover
                error = _("Error saving domain: %s") % error
                src.err.show_err(error, details=details)

        progWin = vmmAsyncJob(
            cb,
            [],
            finish_cb,
            [],
            _("Saving Virtual Machine"),
            _("Saving virtual machine memory to disk "),
            src.topwin,
            cancel_cb=_cancel_cb,
        )
        progWin.run()

    @staticmethod
    def destroy(src, vm):
        if not src.err.chkbox_helper(
            src.config.get_confirm_forcepoweroff,
            src.config.set_confirm_forcepoweroff,
            text1=_("Are you sure you want to force poweroff '%s'?") % vm.get_name(),
            text2=_(
                "This will immediately poweroff the VM without "
                "shutting down the OS and may cause data loss."
            ),
        ):
            return

        log.debug("Destroying vm '%s'", vm.get_name())
        vmmAsyncJob.simple_async_noshow(vm.destroy, [], src, _("Error shutting down domain"))

    @staticmethod
    def suspend(src, vm):
        if not src.err.chkbox_helper(
            src.config.get_confirm_pause,
            src.config.set_confirm_pause,
            text1=_("Are you sure you want to pause '%s'?") % vm.get_name(),
        ):
            return

        log.debug("Pausing vm '%s'", vm.get_name())
        vmmAsyncJob.simple_async_noshow(vm.suspend, [], src, _("Error pausing domain"))

    @staticmethod
    def resume(src, vm):
        log.debug("Unpausing vm '%s'", vm.get_name())
        vmmAsyncJob.simple_async_noshow(vm.resume, [], src, _("Error unpausing domain"))

    @staticmethod
    def run(src, vm):
        log.debug("Starting vm '%s'", vm.get_name())

        if vm.has_managed_save():

            def errorcb(error, details):
                # This is run from the main thread
                res = src.err.show_err(
                    _("Error restoring domain: %s") % error,
                    details=details,
                    text2=_(
                        "The domain could not be restored. Would you like\n"
                        "to remove the saved state and perform a regular\n"
                        "start up?"
                    ),
                    dialog_type=Gtk.MessageType.WARNING,
                    buttons=Gtk.ButtonsType.YES_NO,
                    modal=True,
                )

                if not res:
                    return

                try:
                    vm.remove_saved_image()
                    VMActionUI.run(src, vm)
                except Exception as e:  # pragma: no cover
                    src.err.show_err(_("Error removing domain state: %s") % str(e))

            # VM will be restored, which can take some time, so show progress
            title = _("Restoring Virtual Machine")
            text = _("Restoring virtual machine memory from disk")
            vmmAsyncJob.simple_async(vm.startup, [], src, title, text, "", errorcb=errorcb)

        else:
            # Regular startup
            errorintro = _("Error starting domain")
            vmmAsyncJob.simple_async_noshow(vm.startup, [], src, errorintro)

    @staticmethod
    def shutdown(src, vm):
        if not src.err.chkbox_helper(
            src.config.get_confirm_poweroff,
            src.config.set_confirm_poweroff,
            text1=_("Are you sure you want to poweroff '%s'?") % vm.get_name(),
        ):
            return

        log.debug("Shutting down vm '%s'", vm.get_name())
        vmmAsyncJob.simple_async_noshow(vm.shutdown, [], src, _("Error shutting down domain"))

    @staticmethod
    def reboot(src, vm):
        if not src.err.chkbox_helper(
            src.config.get_confirm_poweroff,
            src.config.set_confirm_poweroff,
            text1=_("Are you sure you want to reboot '%s'?") % vm.get_name(),
        ):
            return

        log.debug("Rebooting vm '%s'", vm.get_name())
        vmmAsyncJob.simple_async_noshow(vm.reboot, [], src, _("Error rebooting domain"))

    @staticmethod
    def reset(src, vm):
        if not src.err.chkbox_helper(
            src.config.get_confirm_forcepoweroff,
            src.config.set_confirm_forcepoweroff,
            text1=_("Are you sure you want to force reset '%s'?") % vm.get_name(),
            text2=_(
                "This will immediately reset the VM without "
                "shutting down the OS and may cause data loss."
            ),
        ):
            return

        log.debug("Resetting vm '%s'", vm.get_name())
        vmmAsyncJob.simple_async_noshow(vm.reset, [], src, _("Error resetting domain"))

    @staticmethod
    def delete(src, vm):
        from .delete import vmmDeleteDialog

        vmmDeleteDialog.show_instance(src, vm)

    @staticmethod
    def migrate(src, vm):
        from .migrate import vmmMigrateDialog

        vmmMigrateDialog.show_instance(src, vm)

    @staticmethod
    def clone(src, vm):
        from .clone import vmmCloneVM

        vmmCloneVM.show_instance(src, vm)

    @staticmethod
    def show(src, vm):
        from .vmwindow import vmmVMWindow

        vmmVMWindow.get_instance(src, vm).show()
