# Copyright (C) 2007, 2013-2014 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.

import sys
import textwrap
import traceback

from gi.repository import Gtk

from virtinst import log

from .baseclass import vmmGObject


def _launch_dialog(dialog, primary_text, secondary_text, title,
                   widget=None, modal=True):
    def fix_text(t):
        if not t:
            return t
        if len(t) > 512:
            t = t[:512] + "..."
        retlines = []
        for line in t.splitlines():
            if not line:
                retlines.append("")
            else:
                retlines.extend(textwrap.wrap(line, 80))
        return "\n".join(retlines)

    primary_text = fix_text(primary_text)
    secondary_text = fix_text(secondary_text)

    dialog.set_property("text", primary_text)
    dialog.format_secondary_text(secondary_text or None)
    dialog.set_title(title)

    if widget:
        dialog.get_content_area().add(widget)

    res = False
    if modal:
        res = dialog.run()
        res = bool(res in [Gtk.ResponseType.YES, Gtk.ResponseType.OK])
        dialog.destroy()
    else:
        def response_destroy(src, ignore):
            src.destroy()
        dialog.connect("response", response_destroy)
        dialog.show()

    return res


class vmmErrorDialog(vmmGObject):
    # singleton instance for non-UI classes
    @classmethod
    def get_instance(cls):
        if not cls._instance:
            cls._instance = vmmErrorDialog(None)
        return cls._instance

    def __init__(self, parent):
        vmmGObject.__init__(self)
        self._parent = parent
        self._simple = None

        self._modal_default = False

    def _cleanup(self):
        pass  # pragma: no cover

    def set_modal_default(self, val):
        self._modal_default = val
    def get_parent(self):
        return self._parent

    def show_err(self, summary, details=None, title="",
                 modal=None, debug=True,
                 dialog_type=Gtk.MessageType.ERROR,
                 buttons=Gtk.ButtonsType.CLOSE,
                 text2=None):
        if modal is None:
            modal = self._modal_default

        if details is None:
            details = summary
            if sys.exc_info()[0] is not None:
                details += "\n\n" + "".join(traceback.format_exc()).strip()
        else:
            details = str(details)

        if debug:
            debugmsg = "error dialog message:\nsummary=%s" % summary
            if details and details != summary:
                det = details
                if details.startswith(summary):
                    det = details[len(summary):].strip()
                debugmsg += "\ndetails=%s" % det
            log.debug(debugmsg)

        # Make sure we have consistent details for error dialogs
        if (dialog_type == Gtk.MessageType.ERROR and summary not in details):
            details = summary + "\n\n" + details

        dialog = _errorDialog(parent=self.get_parent(),
                              flags=0,
                              message_type=dialog_type,
                              buttons=buttons)

        return dialog.show_dialog(primary_text=summary,
                                  secondary_text=text2,
                                  details=details, title=title,
                                  modal=modal)


    ###################################
    # Simple one shot message dialogs #
    ###################################

    def _simple_dialog(self, dialog_type, buttons, text1,
                       text2, title, widget=None, modal=True):

        dialog = Gtk.MessageDialog(self.get_parent(),
                                   flags=Gtk.DialogFlags.DESTROY_WITH_PARENT,
                                   message_type=dialog_type,
                                   buttons=buttons)
        if self._simple:
            self._simple.destroy()
        self._simple = dialog
        self._simple.get_accessible().set_name("vmm dialog")

        return _launch_dialog(self._simple,
                              text1, text2 or "", title or "",
                              widget=widget,
                              modal=modal)

    def val_err(self, text1, text2=None, title=_("Input Error"), modal=True):
        logtext = _("Validation Error: %s") % text1
        if text2:
            logtext += " %s" % text2

        if isinstance(text1, Exception) or isinstance(text2, Exception):
            log.exception(logtext)
        else:
            self._logtrace(logtext)

        dtype = Gtk.MessageType.ERROR
        buttons = Gtk.ButtonsType.OK
        self._simple_dialog(dtype, buttons,
                            str(text1),
                            text2 and str(text2) or "",
                            str(title), None, modal)
        return False

    def show_info(self, text1, text2=None, title="", widget=None, modal=True,
                  buttons=Gtk.ButtonsType.OK):
        dtype = Gtk.MessageType.INFO
        self._simple_dialog(dtype, buttons, text1, text2, title, widget, modal)
        return False

    def yes_no(self, text1, text2=None, title=None):
        dtype = Gtk.MessageType.WARNING
        buttons = Gtk.ButtonsType.YES_NO
        return self._simple_dialog(dtype, buttons, text1, text2, title)

    def ok_cancel(self, text1, text2=None, title=None):
        dtype = Gtk.MessageType.WARNING
        buttons = Gtk.ButtonsType.OK_CANCEL
        return self._simple_dialog(dtype, buttons, text1, text2, title)

    def confirm_unapplied_changes(self):
        """
        Helper function for confirming whether to apply unapplied changes
        """
        return self.chkbox_helper(
                self.config.get_confirm_unapplied,
                self.config.set_confirm_unapplied,
                text1=(_("There are unapplied changes. "
                         "Would you like to apply them now?")),
                chktext=_("Don't warn me again."),
                default=False)


    ##########################################
    # One shot dialog with a checkbox prompt #
    ##########################################

    def warn_chkbox(self, text1, text2=None, chktext=None, buttons=None):
        dtype = Gtk.MessageType.WARNING
        buttons = buttons or Gtk.ButtonsType.OK_CANCEL
        chkbox = _errorDialog(parent=self.get_parent(),
                              flags=0,
                              message_type=dtype,
                              buttons=buttons)
        return chkbox.show_dialog(primary_text=text1,
                                  secondary_text=text2,
                                  chktext=chktext)

    def err_chkbox(self, text1, text2=None, chktext=None, buttons=None):
        dtype = Gtk.MessageType.ERROR
        buttons = buttons or Gtk.ButtonsType.OK
        chkbox = _errorDialog(parent=self.get_parent(),
                              flags=0,
                              message_type=dtype,
                              buttons=buttons)
        return chkbox.show_dialog(primary_text=text1,
                                  secondary_text=text2,
                                  chktext=chktext)

    def chkbox_helper(self, getcb, setcb, text1, text2=None,
                      default=True,
                      chktext=_("Don't ask me again")):
        """
        Helper to prompt user about proceeding with an operation
        Returns True if the 'yes' or 'ok' button was selected, False otherwise

        @default: What value to return if getcb tells us not to prompt
        """
        do_prompt = getcb()
        if not do_prompt:
            return default

        # pylint: disable=unpacking-non-sequence
        res = self.warn_chkbox(text1=text1, text2=text2,
                               chktext=chktext,
                               buttons=Gtk.ButtonsType.YES_NO)
        response, skip_prompt = res
        setcb(not skip_prompt)

        return response

    def browse_local(self, conn, dialog_name, start_folder=None,
                     _type=None, dialog_type=None,
                     browse_reason=None,
                     choose_button=None, default_name=None):
        """
        Helper function for launching a filechooser

        @dialog_name: String to use in the title bar of the filechooser.
        @conn: vmmConnection used by calling class
        @start_folder: Folder the filechooser is viewing at startup
        @_type: File extension to filter by (e.g. "iso", "png")
        @dialog_type: Maps to FileChooserDialog 'action'
        @browse_reason: The vmmConfig.CONFIG_DIR* reason we are browsing.
            If set, this will override the 'folder' parameter with the gsettings
            value, and store the user chosen path.
        """
        import os

        # Initial setup
        overwrite_confirm = False
        dialog_type = dialog_type or Gtk.FileChooserAction.OPEN

        if dialog_type == Gtk.FileChooserAction.SAVE:
            if choose_button is None:
                choose_button = Gtk.STOCK_SAVE
                overwrite_confirm = True

        if choose_button is None:
            choose_button = Gtk.STOCK_OPEN

        fcdialog = Gtk.FileChooserDialog(title=dialog_name,
                                    parent=self.get_parent(),
                                    action=dialog_type,
                                    buttons=(Gtk.STOCK_CANCEL,
                                             Gtk.ResponseType.CANCEL,
                                             choose_button,
                                             Gtk.ResponseType.ACCEPT))
        fcdialog.set_default_response(Gtk.ResponseType.ACCEPT)

        if default_name:
            fcdialog.set_current_name(default_name)

        fcdialog.set_do_overwrite_confirmation(overwrite_confirm)

        # Set file match pattern (ex. *.png)
        if _type is not None:
            pattern = _type
            name = None
            if isinstance(_type, tuple):
                pattern = _type[0]
                name = _type[1]

            f = Gtk.FileFilter()
            f.add_pattern("*." + pattern)
            if name:
                f.set_name(name)
            fcdialog.set_filter(f)

        # Set initial dialog folder
        if browse_reason:
            start_folder = self.config.get_default_directory(
                conn, browse_reason)

        if start_folder is not None:
            if os.access(start_folder, os.R_OK):
                fcdialog.set_current_folder(start_folder)

        # Run the dialog and parse the response
        ret = None
        if fcdialog.run() == Gtk.ResponseType.ACCEPT:
            ret = fcdialog.get_filename()
        fcdialog.destroy()

        # Store the chosen directory in gsettings if necessary
        if ret and browse_reason and not ret.startswith("/dev"):
            self.config.set_default_directory(
                os.path.dirname(ret), browse_reason)
        return ret


class _errorDialog (Gtk.MessageDialog):
    """
    Custom error dialog with optional check boxes or details drop down
    """
    def __init__(self, *args, **kwargs):
        Gtk.MessageDialog.__init__(self, *args, **kwargs)

        self.set_title("")
        for child in self.get_message_area().get_children():
            if hasattr(child, "set_max_width_chars"):
                child.set_max_width_chars(40)

        self.get_accessible().set_name("vmm dialog")

        self.chk_vbox = None
        self.init_chkbox()

        self.buffer = None
        self.buf_expander = None
        self.init_details()

    def init_chkbox(self):
        # Init check items
        self.chk_vbox = Gtk.VBox(False, False)
        self.chk_vbox.set_spacing(0)

        self.chk_vbox.show_all()
        self.vbox.pack_start(  # pylint: disable=no-member
            self.chk_vbox, False, False, 0)

    def init_details(self):
        # Init details buffer
        self.buffer = Gtk.TextBuffer()
        self.buf_expander = Gtk.Expander.new(_("Details"))
        sw = Gtk.ScrolledWindow()
        sw.set_shadow_type(Gtk.ShadowType.IN)
        sw.set_size_request(400, 240)
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        details = Gtk.TextView.new_with_buffer(self.buffer)
        details.set_editable(False)
        details.set_overwrite(False)
        details.set_cursor_visible(False)
        details.set_wrap_mode(Gtk.WrapMode.WORD)
        details.set_border_width(6)
        sw.add(details)
        self.buf_expander.add(sw)
        self.vbox.pack_start(  # pylint: disable=no-member
            self.buf_expander, False, False, 0)
        self.buf_expander.show_all()

    def show_dialog(self, primary_text, secondary_text="",
                    title="", details="", chktext="",
                    modal=True):
        chkbox = None
        res = None

        # Hide starting widgets
        self.hide()
        self.buf_expander.hide()
        for c in self.chk_vbox.get_children():
            self.chk_vbox.remove(c)  # pragma: no cover

        if details:
            self.buffer.set_text(details)
            title = title or ""
            self.buf_expander.show()

        if chktext:
            chkbox = Gtk.CheckButton(chktext)
            self.chk_vbox.add(chkbox)
            chkbox.show()

        res = _launch_dialog(self,
                             primary_text, secondary_text or "",
                             title,
                             modal=modal)

        if chktext:
            res = [res, chkbox.get_active()]

        return res
