# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.

import logging
from enum import IntEnum

from gi.repository import Gtk
from nbxmpp.util import is_error_result
from nbxmpp.const import AdHocAction

from gajim.common import app
from gajim.common.i18n import _

from gajim.common.modules import dataforms

from gajim.gtk.dataform import DataFormWidget
from gajim.gtk.util import ensure_not_destroyed
from gajim.gtk.util import find_widget
from gajim.gtk.util import MultiLineLabel

log = logging.getLogger('gajim.gtk.adhoc')


class Page(IntEnum):
    REQUEST = 0
    EXECUTE = 1
    COMMANDS = 2
    STAGE = 3
    COMPLETED = 4
    ERROR = 5


class AdHocCommand(Gtk.Assistant):
    def __init__(self, account, jid=None):
        Gtk.Assistant.__init__(self)

        self._con = app.connections[account]
        self._account = account
        self._destroyed = False

        self.set_application(app.app)
        self.set_resizable(True)
        self.set_position(Gtk.WindowPosition.CENTER)

        self.set_default_size(600, 400)
        self.get_style_context().add_class('dialog-margin')

        self._add_page(Request())
        self._add_page(ExecuteCommand())
        self._add_page(Commands())
        self._add_page(Stage())
        self._add_page(Completed())
        self._add_page(Error())

        self.connect('prepare', self._on_page_change)
        self.connect('cancel', self._on_cancel)
        self.connect('close', self._on_cancel)
        self.connect('destroy', self._on_destroy)

        self._remove_sidebar()

        self._buttons = {}
        self._add_custom_buttons()

        self.show()
        self._con.get_module('AdHocCommands').request_command_list(
            jid, callback=self._received_command_list)

    def _add_custom_buttons(self):
        action_area = find_widget('action_area', self)
        for button in list(action_area.get_children()):
            self.remove_action_widget(button)

        cancel = Gtk.Button(label=_('Cancel'))
        cancel.connect('clicked', self._abort)
        cancel.get_style_context().add_class('destructive-action')
        self._buttons['cancel'] = cancel
        self.add_action_widget(cancel)

        complete = Gtk.Button(label=_('Finish'))
        complete.action = AdHocAction.COMPLETE
        complete.connect('clicked', self._execute_action)
        self._buttons['complete'] = complete
        self.add_action_widget(complete)

        commands = Gtk.Button(label=_('Commands'))
        commands.connect('clicked',
                         lambda *args: self.set_current_page(Page.COMMANDS))
        self._buttons['commands'] = commands
        self.add_action_widget(commands)

        next_ = Gtk.Button(label=_('Next'))
        next_.action = AdHocAction.NEXT
        next_.connect('clicked', self._execute_action)
        self._buttons['next'] = next_
        self.add_action_widget(next_)

        prev = Gtk.Button(label=_('Previous'))
        prev.action = AdHocAction.PREV
        prev.connect('clicked', self._execute_action)
        self._buttons['prev'] = prev
        self.add_action_widget(prev)

        execute = Gtk.Button(label=_('Execute'))
        execute.action = AdHocAction.EXECUTE
        execute.get_style_context().add_class('suggested-action')
        execute.connect('clicked', self._execute_action)
        self._buttons['execute'] = execute
        self.add_action_widget(execute)

    def _set_button_visibility(self, page):
        for button in self._buttons.values():
            button.hide()

        if page == Page.COMMANDS:
            self._buttons['execute'].show()

        elif page == Page.STAGE:
            self._buttons['cancel'].show()
            stage_page = self.get_nth_page(page)
            if not stage_page.actions:
                self._buttons['complete'].show()
            else:
                for action in stage_page.actions:
                    button = self._buttons.get(action.value)
                    if button is not None:
                        button.show()

        elif page in (Page.ERROR, Page.COMPLETED):
            self._buttons['commands'].show()

    def _add_page(self, page):
        self.append_page(page)
        self.set_page_type(page, page.type_)
        self.set_page_title(page, page.title)
        self.set_page_complete(page, page.complete)

    def execute_action(self):
        self._execute_action(self._buttons['execute'])

    def _execute_action(self, button):
        action = button.action
        current_page = self.get_current_page()
        dataform = None
        if action == AdHocAction.EXECUTE:
            command = self.get_nth_page(current_page).get_selected_command()
        else:
            command, dataform = self.get_nth_page(current_page).stage_data
            if action == AdHocAction.PREV:
                dataform = None

        self.set_current_page(Page.EXECUTE)
        if current_page == Page.STAGE:
            self.get_nth_page(current_page).clear()
        self._con.get_module('AdHocCommands').execute_command(
            command,
            action=action,
            dataform=dataform,
            callback=self._received_stage)

    def _abort(self, *args):
        if self.get_current_page() == Page.STAGE:
            command = self.get_nth_page(Page.STAGE).stage_data[0]
            self._con.get_module('AdHocCommands').execute_command(
                command, AdHocAction.CANCEL)
            self.set_current_page(Page.COMMANDS)

    @ensure_not_destroyed
    def _received_command_list(self, commands):
        if is_error_result(commands):
            self.set_current_page(Page.ERROR)
            return
        self.get_nth_page(Page.COMMANDS).add_commands(commands)
        self.set_current_page(Page.COMMANDS)

    @ensure_not_destroyed
    def _received_stage(self, stage):
        if is_error_result(stage):
            self.get_nth_page(Page.ERROR).set_text(str(stage))
            self.set_current_page(Page.ERROR)
            return

        page = Page.STAGE
        if stage.is_completed:
            page = Page.COMPLETED

        stage_page = self.get_nth_page(page)
        stage_page.process_stage(stage)
        self.set_current_page(page)

    def set_stage_complete(self, is_valid):
        self._buttons['next'].set_sensitive(is_valid)
        self._buttons['complete'].set_sensitive(is_valid)

    def _remove_sidebar(self):
        main_box = self.get_children()[0]
        sidebar = main_box.get_children()[0]
        main_box.remove(sidebar)

    def _on_page_change(self, _assistant, _page):
        self._set_button_visibility(self.get_current_page())

    def _on_error(self, error_text):
        log.info('Show Error page')
        page = self.get_nth_page(Page.ERROR)
        page.set_text(error_text)
        self.set_current_page(Page.ERROR)

    def _on_cancel(self, _widget):
        self.destroy()

    def _on_destroy(self, *args):
        self._destroyed = True


class Request(Gtk.Box):

    type_ = Gtk.AssistantPageType.CUSTOM
    title = _('Request Command List')
    complete = False

    def __init__(self):
        super().__init__(orientation=Gtk.Orientation.VERTICAL)
        self.set_spacing(18)
        spinner = Gtk.Spinner()
        self.pack_start(spinner, True, True, 0)
        spinner.start()
        self.show_all()


class ExecuteCommand(Request):

    type_ = Gtk.AssistantPageType.CUSTOM
    title = _('Executing…')
    complete = False


class Commands(Gtk.Box):

    type_ = Gtk.AssistantPageType.CUSTOM
    title = _('Command List')
    complete = True

    def __init__(self):
        super().__init__(orientation=Gtk.Orientation.VERTICAL)
        self.set_spacing(18)
        self._commands = {}
        self._scrolled = Gtk.ScrolledWindow()
        self._scrolled.get_style_context().add_class('adhoc-scrolled')
        self._scrolled.set_max_content_height(400)
        self._scrolled.set_max_content_width(400)
        self._scrolled.set_policy(Gtk.PolicyType.NEVER,
                                  Gtk.PolicyType.AUTOMATIC)
        self._treeview = Gtk.TreeView()
        self._treeview.get_style_context().add_class('adhoc-treeview')
        self._store = Gtk.ListStore(str, str)
        self._treeview.set_model(self._store)
        column = Gtk.TreeViewColumn(_('Commands'))
        column.set_expand(True)
        self._treeview.append_column(column)
        renderer = Gtk.CellRendererText()
        column.pack_start(renderer, True)
        column.add_attribute(renderer, 'text', 0)

        self._treeview.connect('row-activated', self._on_row_activate)
        self._treeview.set_search_equal_func(self._search_func)

        self._scrolled.add(self._treeview)
        self.pack_start(self._scrolled, True, True, 0)
        self.show_all()

    @staticmethod
    def _search_func(model, _column, search_text, iter_):
        return search_text.lower() not in model[iter_][0].lower()

    def _on_row_activate(self, _tree_view, _path, _column):
        self.get_toplevel().execute_action()

    def add_commands(self, commands):
        self._store.clear()
        self._commands = {}
        for command in commands:
            key = '%s:%s' % (command.jid, command.node)
            self._commands[key] = command
            self._store.append((command.name, key))

    def get_selected_command(self):
        model, treeiter = self._treeview.get_selection().get_selected()
        key = model[treeiter][1]
        return self._commands[key]


class Stage(Gtk.Box):

    type_ = Gtk.AssistantPageType.CUSTOM
    title = _('Settings')
    complete = True

    def __init__(self):
        super().__init__(orientation=Gtk.Orientation.VERTICAL)
        self.set_spacing(18)
        self._dataform_widget = None
        self._notes = []
        self._last_stage_data = None
        self.show_all()

    @property
    def stage_data(self):
        return self._last_stage_data, self._dataform_widget.get_submit_form()

    @property
    def actions(self):
        return self._last_stage_data.actions

    def clear(self):
        self._show_form(None)
        self._show_notes(None)
        self._last_stage_data = None

    def process_stage(self, stage_data):
        self._last_stage_data = stage_data
        self._show_notes(stage_data.notes)
        self._show_form(stage_data.data)

    def _show_form(self, form):
        if self._dataform_widget is not None:
            self.remove(self._dataform_widget)
            self._dataform_widget.destroy()
        if form is None:
            return
        form = dataforms.extend_form(node=form)
        self._dataform_widget = DataFormWidget(form)
        self._dataform_widget.connect('is-valid', self._on_is_valid)
        self._dataform_widget.validate()
        self._dataform_widget.show_all()
        self.add(self._dataform_widget)

    def _show_notes(self, notes):
        for note in self._notes:
            self.remove(note)
        self._notes = []

        if notes is None:
            return

        for note in notes:
            label = Gtk.Label(label=note.text)
            label.show()
            self._notes.append(label)
            self.add(label)

    def _on_is_valid(self, _widget, is_valid):
        self.get_toplevel().set_stage_complete(is_valid)


class Completed(Gtk.Box):

    type_ = Gtk.AssistantPageType.CUSTOM
    title = _('Finished')
    complete = True

    def __init__(self):
        super().__init__(orientation=Gtk.Orientation.VERTICAL)
        self.set_spacing(12)
        self._notes = []
        self._dataform_widget = None

        icon = Gtk.Image.new_from_icon_name('object-select-symbolic',
                                            Gtk.IconSize.DIALOG)
        icon.get_style_context().add_class('success-color')
        icon.show()

        label = Gtk.Label(label='Finished')
        label.show()

        self._icon_text = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        self._icon_text.set_spacing(12)
        self._icon_text.set_halign(Gtk.Align.CENTER)
        self._icon_text.add(icon)
        self._icon_text.add(label)
        self.add(self._icon_text)

        self.show_all()

    def process_stage(self, stage_data):
        self._show_notes(stage_data.notes)
        self._show_form(stage_data.data)
        self._show_icon_text(stage_data.data is None)

    def _show_icon_text(self, show):
        if show:
            self.set_valign(Gtk.Align.CENTER)
            self._icon_text.show_all()
        else:
            self.set_valign(Gtk.Align.FILL)
            self._icon_text.hide()

    def _show_form(self, form):
        if self._dataform_widget is not None:
            self.remove(self._dataform_widget)
            self._dataform_widget.destroy()
        if form is None:
            return

        form = dataforms.extend_form(node=form)

        self._dataform_widget = DataFormWidget(
            form, options={'read-only': True})
        self._dataform_widget.show_all()
        self.add(self._dataform_widget)

    def _show_notes(self, notes):
        for note in self._notes:
            self.remove(note)
        self._notes = []

        for note in notes:
            label = MultiLineLabel(label=note.text)
            label.set_justify(Gtk.Justification.CENTER)
            label.show()
            self._notes.append(label)
            self.add(label)


class Error(Gtk.Box):

    type_ = Gtk.AssistantPageType.CUSTOM
    title = _('Execution failed')
    complete = True

    def __init__(self):
        super().__init__(orientation=Gtk.Orientation.VERTICAL)
        self.set_spacing(12)
        self.set_homogeneous(True)

        icon = Gtk.Image.new_from_icon_name('dialog-error-symbolic',
                                            Gtk.IconSize.DIALOG)
        icon.get_style_context().add_class('error-color')
        icon.set_valign(Gtk.Align.END)
        self._label = Gtk.Label()
        self._label.get_style_context().add_class('bold16')
        self._label.set_valign(Gtk.Align.START)

        self.add(icon)
        self.add(self._label)
        self.show_all()

    def set_text(self, text):
        self._label.set_text(text)