
from functools import partial
import threading
import os
from typing import TYPE_CHECKING

from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import ObjectProperty, StringProperty, OptionProperty
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.uix.togglebutton import ToggleButton
from kivy.utils import platform
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.utils import platform

from electrum.base_wizard import BaseWizard
from electrum.util import is_valid_email


from . import EventsDialog
from ...i18n import _
from .password_dialog import PasswordDialog

if TYPE_CHECKING:
    from electrum.gui.kivy.main_window import ElectrumWindow


# global Variables

Builder.load_string('''
#:import Window kivy.core.window.Window
#:import _ electrum.gui.kivy.i18n._
#:import KIVY_GUI_PATH electrum.gui.kivy.KIVY_GUI_PATH


<WizardTextInput@TextInput>
    border: 4, 4, 4, 4
    font_size: '15sp'
    padding: '15dp', '15dp'
    background_color: (1, 1, 1, 1) if self.focus else (0.454, 0.698, 0.909, 1)
    foreground_color: (0.31, 0.31, 0.31, 1) if self.focus else (0.835, 0.909, 0.972, 1)
    hint_text_color: self.foreground_color
    background_active: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/create_act_text_active'
    background_normal: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/create_act_text_active'
    size_hint_y: None
    height: '48sp'

<WizardButton@Button>:
    root: None
    size_hint: 1, None
    height: '48sp'
    on_press: if self.root: self.root.dispatch('on_press', self)
    on_release: if self.root: self.root.dispatch('on_release', self)

<BigLabel@Label>
    color: .854, .925, .984, 1
    size_hint: 1, None
    text_size: self.width, None
    height: self.texture_size[1]
    bold: True

<-WizardDialog>
    text_color: .854, .925, .984, 1
    value: ''
    #auto_dismiss: False
    size_hint: None, None
    canvas.before:
        Color:
            rgba: .239, .588, .882, 1
        Rectangle:
            size: Window.size

    crcontent: crcontent
    # add electrum icon
    BoxLayout:
        orientation: 'vertical' if self.width < self.height else 'horizontal'
        padding:
            min(dp(27), self.width/32), min(dp(27), self.height/32),\
            min(dp(27), self.width/32), min(dp(27), self.height/32)
        spacing: '10dp'
        GridLayout:
            id: grid_logo
            cols: 1
            pos_hint: {'center_y': .5}
            size_hint: 1, None
            height: self.minimum_height
            Label:
                color: root.text_color
                text: 'ELECTRUM'
                size_hint: 1, None
                height: self.texture_size[1] if self.opacity else 0
                font_size: '33sp'
                font_name: f'{KIVY_GUI_PATH}/data/fonts/tron/Tr2n.ttf'
        GridLayout:
            cols: 1
            id: crcontent
            spacing: '1dp'
        Widget:
            size_hint: 1, 0.3
        GridLayout:
            rows: 1
            spacing: '12dp'
            size_hint: 1, None
            height: self.minimum_height
            WizardButton:
                id: back
                text: _('Back')
                root: root
            WizardButton:
                id: next
                text: _('Next')
                root: root
                disabled: root.value == ''


<WizardMultisigDialog>
    value: 'next'
    Widget
        size_hint: 1, 1
    Label:
        color: root.text_color
        size_hint: 1, None
        text_size: self.width, None
        height: self.texture_size[1]
        text: _("Choose the number of signatures needed to unlock funds in your wallet")
    Widget
        size_hint: 1, 1
    GridLayout:
        cols: 2
        spacing: '14dp'
        size_hint: 1, 1
        height: self.minimum_height
        Label:
            color: root.text_color
            text: _('From {} cosigners').format(n.value)
        Slider:
            id: n
            range: 2, 5
            step: 1
            value: 2
        Label:
            color: root.text_color
            text: _('Require {} signatures').format(m.value)
        Slider:
            id: m
            range: 1, n.value
            step: 1
            value: 2
    Widget
        size_hint: 1, 1
    Label:
        id: backup_warning_label
        color: root.text_color
        size_hint: 1, None
        text_size: self.width, None
        height: self.texture_size[1]
        opacity: int(m.value != n.value)
        text: _("Warning: to be able to restore a multisig wallet, " \
                "you should include the master public key for each cosigner " \
                "in all of your backups.")


<WizardChoiceDialog>
    message : ''
    Widget:
        size_hint: 1, 1
    Label:
        color: root.text_color
        size_hint: 1, None
        text_size: self.width, None
        height: self.texture_size[1]
        text: root.message
    Widget
        size_hint: 1, 1
    GridLayout:
        row_default_height: '48dp'
        id: choices
        cols: 1
        spacing: '14dp'
        size_hint: 1, None

<WizardConfirmDialog>
    message : ''
    Widget:
        size_hint: 1, 1
    Label:
        color: root.text_color
        size_hint: 1, None
        text_size: self.width, None
        height: self.texture_size[1]
        text: root.message
    Widget
        size_hint: 1, 1

<WizardTOSDialog>
    message : ''
    size_hint: 1, 1
    ScrollView:
        size_hint: 1, 1
        TextInput:
            color: root.text_color
            size_hint: 1, None
            text_size: self.width, None
            height: self.minimum_height
            text: root.message
            disabled: True

<WizardEmailDialog>
    Label:
        color: root.text_color
        size_hint: 1, None
        text_size: self.width, None
        height: self.texture_size[1]
        text: 'Please enter your email address'
    WizardTextInput:
        id: email
        on_text: Clock.schedule_once(root.on_text)
        multiline: False
        on_text_validate: Clock.schedule_once(root.on_enter)

<WizardKnownOTPDialog>
    message : ''
    message2: ''
    Widget:
        size_hint: 1, 1
    Label:
        color: root.text_color
        size_hint: 1, None
        text_size: self.width, None
        height: self.texture_size[1]
        text: root.message
    Widget
        size_hint: 1, 1
    WizardTextInput:
        id: otp
        on_text: Clock.schedule_once(root.on_text)
        multiline: False
        on_text_validate: Clock.schedule_once(root.on_enter)
    Widget
        size_hint: 1, 1
    Label:
        color: root.text_color
        size_hint: 1, None
        text_size: self.width, None
        height: self.texture_size[1]
        text: root.message2
    Widget
        size_hint: 1, 1
        height: '48sp'
    BoxLayout:
        orientation: 'horizontal'
        WizardButton:
            id: cb
            text: _('Request new secret')
            on_release: root.request_new_secret()
            size_hint: 1, None
        WizardButton:
            id: abort
            text: _('Abort creation')
            on_release: root.abort_wallet_creation()
            size_hint: 1, None


<WizardNewOTPDialog>
    message : ''
    message2 : ''
    Label:
        color: root.text_color
        size_hint: 1, None
        text_size: self.width, None
        height: self.texture_size[1]
        text: root.message
    QRCodeWidget:
        id: qr
        size_hint: 1, 1
    Label:
        color: root.text_color
        size_hint: 1, None
        text_size: self.width, None
        height: self.texture_size[1]
        text: root.message2
    WizardTextInput:
        id: otp
        on_text: Clock.schedule_once(root.on_text)
        multiline: False
        on_text_validate: Clock.schedule_once(root.on_enter)

<MButton@Button>:
    size_hint: 1, None
    height: '33dp'
    on_release:
        self.parent.update_amount(self.text)

<WordButton@Button>:
    size_hint: None, None
    padding: '5dp', '5dp'
    text_size: None, self.height
    width: self.texture_size[0]
    height: '30dp'
    on_release:
        if self.parent: self.parent.new_word(self.text)


<SeedButton@Button>:
    height: dp(100)
    border: 4, 4, 4, 4
    halign: 'justify'
    valign: 'top'
    font_size: '18dp'
    text_size: self.width - dp(24), self.height - dp(12)
    color: .1, .1, .1, 1
    background_normal: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/white_bg_round_top'
    background_down: self.background_normal
    size_hint_y: None


<SeedLabel@Label>:
    font_size: '12sp'
    text_size: self.width, None
    size_hint: 1, None
    height: self.texture_size[1]
    halign: 'justify'
    valign: 'middle'
    border: 4, 4, 4, 4

<SeedDialogHeader@GridLayout>
    text: ''
    options_dialog: None
    rows: 1
    size_hint: 1, None
    height: self.minimum_height
    BigLabel:
        size_hint: 9, None
        text: root.text
    IconButton:
        id: options_button
        height: '30dp'
        width: '30dp'
        size_hint: 1, None
        icon: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/gear'
        on_release:
            root.options_dialog() if root.options_dialog else None

<RestoreSeedDialog>
    message: ''
    word: ''
    SeedDialogHeader:
        id: seed_dialog_header
        text: 'ENTER YOUR SEED PHRASE'
        options_dialog: root.options_dialog
    GridLayout:
        cols: 1
        padding: 0, '12dp'
        spacing: '12dp'
        size_hint: 1, None
        height: self.minimum_height
        SeedButton:
            id: text_input_seed
            text: ''
            on_text: Clock.schedule_once(root.on_text)
        SeedLabel:
            text: root.message
        BoxLayout:
            id: suggestions
            height: '35dp'
            size_hint: 1, None
            new_word: root.on_word
        BoxLayout:
            id: line1
            update_amount: root.update_text
            size_hint: 1, None
            height: '30dp'
            MButton:
                text: 'Q'
            MButton:
                text: 'W'
            MButton:
                text: 'E'
            MButton:
                text: 'R'
            MButton:
                text: 'T'
            MButton:
                text: 'Y'
            MButton:
                text: 'U'
            MButton:
                text: 'I'
            MButton:
                text: 'O'
            MButton:
                text: 'P'
        BoxLayout:
            id: line2
            update_amount: root.update_text
            size_hint: 1, None
            height: '30dp'
            Widget:
                size_hint: 0.5, None
                height: '33dp'
            MButton:
                text: 'A'
            MButton:
                text: 'S'
            MButton:
                text: 'D'
            MButton:
                text: 'F'
            MButton:
                text: 'G'
            MButton:
                text: 'H'
            MButton:
                text: 'J'
            MButton:
                text: 'K'
            MButton:
                text: 'L'
            Widget:
                size_hint: 0.5, None
                height: '33dp'
        BoxLayout:
            id: line3
            update_amount: root.update_text
            size_hint: 1, None
            height: '30dp'
            Widget:
                size_hint: 1, None
            MButton:
                text: 'Z'
            MButton:
                text: 'X'
            MButton:
                text: 'C'
            MButton:
                text: 'V'
            MButton:
                text: 'B'
            MButton:
                text: 'N'
            MButton:
                text: 'M'
            MButton:
                text: ' '
            MButton:
                text: '<'

<AddXpubDialog>
    title: ''
    message: ''
    BigLabel:
        text: root.title
    GridLayout
        cols: 1
        padding: 0, '12dp'
        spacing: '12dp'
        size_hint: 1, None
        height: self.minimum_height
        SeedButton:
            id: text_input
            text: ''
            on_text: Clock.schedule_once(root.check_text)
        SeedLabel:
            text: root.message
    GridLayout
        rows: 1
        spacing: '12dp'
        size_hint: 1, None
        height: self.minimum_height
        IconButton:
            id: scan
            height: '48sp'
            on_release: root.scan_xpub()
            icon: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/camera'
            size_hint: 1, None
        WizardButton:
            text: _('Paste')
            on_release: root.do_paste()
        WizardButton:
            text: _('Clear')
            on_release: root.do_clear()


<ShowXpubDialog>
    xpub: ''
    message: _('Here is your master public key. Share it with your cosigners.')
    BigLabel:
        text: "MASTER PUBLIC KEY"
    GridLayout
        cols: 1
        padding: 0, '12dp'
        spacing: '12dp'
        size_hint: 1, None
        height: self.minimum_height
        SeedButton:
            id: text_input
            text: root.xpub
        SeedLabel:
            text: root.message
    GridLayout
        rows: 1
        spacing: '12dp'
        size_hint: 1, None
        height: self.minimum_height
        WizardButton:
            text: _('QR code')
            on_release: root.do_qr()
        WizardButton:
            text: _('Copy')
            on_release: root.do_copy()
        WizardButton:
            text: _('Share')
            on_release: root.do_share()

<ShowSeedDialog>
    spacing: '12dp'
    value: 'next'
    SeedDialogHeader:
        text: "PLEASE WRITE DOWN YOUR SEED PHRASE"
        options_dialog: root.options_dialog
    GridLayout:
        id: grid
        cols: 1
        pos_hint: {'center_y': .5}
        size_hint_y: None
        height: self.minimum_height
        spacing: '12dp'
        SeedButton:
            text: root.seed_text
        SeedLabel:
            text: root.message

<LineDialog>
    BigLabel:
        text: root.title
    SeedLabel:
        text: root.message
    TextInput:
        id: passphrase_input
        multiline: False
        size_hint: 1, None
        height: '48dp'
        on_text: Clock.schedule_once(root.on_text)
    SeedLabel:
        text: root.warning

<ChoiceLineDialog>
    BigLabel:
        text: root.title
    SeedLabel:
        text: root.message1
    GridLayout:
        row_default_height: '48dp'
        id: choices
        cols: 1
        spacing: '14dp'
        size_hint: 1, None
    SeedLabel:
        text: root.message2
    TextInput:
        id: text_input
        multiline: False
        size_hint: 1, None
        height: '48dp'

''')



class WizardDialog(EventsDialog):
    ''' Abstract dialog to be used as the base for all Create Account Dialogs
    '''
    crcontent = ObjectProperty(None)

    def __init__(self, wizard, **kwargs):
        self.auto_dismiss = False
        super(WizardDialog, self).__init__()
        self.wizard = wizard
        self.ids.back.disabled = not wizard.can_go_back()
        self.app = App.get_running_app()
        self.run_next = kwargs['run_next']

        self._trigger_size_dialog = Clock.create_trigger(self._size_dialog, -1)
        # note: everything bound here needs to be unbound as otherwise the
        # objects will be kept around and keep receiving the callbacks
        Window.bind(size=self._trigger_size_dialog,
                    rotation=self._trigger_size_dialog,
                    on_keyboard=self.on_keyboard)
        self._trigger_size_dialog()
        self._on_release = False

    def _size_dialog(self, dt):
        if self.app.ui_mode[0] == 'p':
            self.size = Window.size
        else:
            #tablet
            if self.app.orientation[0] == 'p':
                #portrait
                self.size = Window.size[0]/1.67, Window.size[1]/1.4
            else:
                self.size = Window.size[0]/2.5, Window.size[1]

    def add_widget(self, widget, index=0):
        if not self.crcontent:
            super(WizardDialog, self).add_widget(widget)
        else:
            self.crcontent.add_widget(widget, index=index)

    def on_keyboard(self, instance, key, keycode, codepoint, modifier):
        if key == 27:
            if self.wizard.can_go_back():
                self.dismiss()
                self.wizard.go_back()
            else:
                if not self.app.is_exit:
                    self.app.is_exit = True
                    self.app.show_info(_('Press again to exit'))
                else:
                    self._on_release = False
                    self.dismiss()
            return True

    def on_dismiss(self):
        Window.unbind(size=self._trigger_size_dialog,
                      rotation=self._trigger_size_dialog,
                      on_keyboard=self.on_keyboard)
        if self.app.wallet is None and not self._on_release:
            self.app.stop()

    def get_params(self, button):
        return (None,)

    def on_release(self, button):
        if self._on_release is True:
            return
        self._on_release = True
        self.dismiss()
        if not button:
            self.wizard.terminate(aborted=True)
            return
        if button is self.ids.back:
            self.wizard.go_back()
            return
        params = self.get_params(button)
        self.run_next(*params)


class WizardMultisigDialog(WizardDialog):

    def get_params(self, button):
        m = self.ids.m.value
        n = self.ids.n.value
        return m, n


class WizardOTPDialogBase(WizardDialog):

    def get_otp(self):
        otp = self.ids.otp.text
        if len(otp) != 6:
            return
        try:
            return int(otp)
        except Exception:
            return

    def on_text(self, dt):
        self.ids.next.disabled = self.get_otp() is None

    def on_enter(self, dt):
        # press next
        next = self.ids.next
        if not next.disabled:
            next.dispatch('on_release')


class WizardKnownOTPDialog(WizardOTPDialogBase):

    def __init__(self, wizard, **kwargs):
        WizardOTPDialogBase.__init__(self, wizard, **kwargs)
        self.message = _("This wallet is already registered with TrustedCoin. To finalize wallet creation, please enter your Google Authenticator Code.")
        self.message2 =_("If you have lost your Google Authenticator account, you can request a new secret. You will need to retype your seed.")
        self.request_new = False

    def get_params(self, button):
        return (self.get_otp(), self.request_new)

    def request_new_secret(self):
        self.request_new = True
        self.on_release(True)

    def abort_wallet_creation(self):
        self._on_release = True
        self.wizard.terminate(aborted=True)
        self.dismiss()


class WizardNewOTPDialog(WizardOTPDialogBase):

    def __init__(self, wizard, **kwargs):
        WizardOTPDialogBase.__init__(self, wizard, **kwargs)
        otp_secret = kwargs['otp_secret']
        uri = "otpauth://totp/%s?secret=%s"%('trustedcoin.com', otp_secret)
        self.message = "Please scan the following QR code in Google Authenticator. You may also use the secret key: %s"%otp_secret
        self.message2 = _('Then, enter your Google Authenticator code:')
        self.ids.qr.set_data(uri)

    def get_params(self, button):
        return (self.get_otp(), False)

class WizardTOSDialog(WizardDialog):

    def __init__(self, wizard, **kwargs):
        WizardDialog.__init__(self, wizard, **kwargs)
        self.ids.next.text = 'Accept'
        self.ids.next.disabled = False
        self.message = kwargs['tos']

class WizardEmailDialog(WizardDialog):

    def get_params(self, button):
        return (self.ids.email.text,)

    def on_text(self, dt):
        self.ids.next.disabled = not is_valid_email(self.ids.email.text)

    def on_enter(self, dt):
        # press next
        next = self.ids.next
        if not next.disabled:
            next.dispatch('on_release')

class WizardConfirmDialog(WizardDialog):

    def __init__(self, wizard, **kwargs):
        super(WizardConfirmDialog, self).__init__(wizard, **kwargs)
        self.message = kwargs.get('message', '')
        self.value = 'ok'

    def on_parent(self, instance, value):
        if value:
            self._back = _back = partial(self.app.dispatch, 'on_back')

    def get_params(self, button):
        return (True,)


class WizardChoiceDialog(WizardDialog):

    def __init__(self, wizard, **kwargs):
        super(WizardChoiceDialog, self).__init__(wizard, **kwargs)
        self.title = kwargs.get('message', '')
        self.message = kwargs.get('message', '')
        choices = kwargs.get('choices', [])
        self.init_choices(choices)

    def init_choices(self, choices):
        layout = self.ids.choices
        layout.bind(minimum_height=layout.setter('height'))
        for action, text in choices:
            l = WizardButton(text=text)
            l.action = action
            l.height = '48dp'
            l.root = self
            layout.add_widget(l)

    def on_parent(self, instance, value):
        if value:
            self._back = _back = partial(self.app.dispatch, 'on_back')

    def get_params(self, button):
        return (button.action,)


class LineDialog(WizardDialog):
    title = StringProperty('')
    message = StringProperty('')
    warning = StringProperty('')

    def __init__(self, wizard, **kwargs):
        WizardDialog.__init__(self, wizard, **kwargs)
        self.title = kwargs.get('title', '')
        self.message = kwargs.get('message', '')
        self.ids.next.disabled = True
        self.test = kwargs['test']

    def get_text(self):
        return self.ids.passphrase_input.text

    def on_text(self, dt):
        self.ids.next.disabled = not self.test(self.get_text())

    def get_params(self, b):
        return (self.get_text(),)

class CLButton(ToggleButton):
    def on_release(self):
        self.root.script_type = self.script_type
        self.root.set_text(self.value)

class ChoiceLineDialog(WizardChoiceDialog):
    title = StringProperty('')
    message1 = StringProperty('')
    message2 = StringProperty('')

    def __init__(self, wizard, **kwargs):
        WizardDialog.__init__(self, wizard, **kwargs)
        self.title = kwargs.get('title', '')
        self.message1 = kwargs.get('message1', '')
        self.message2 = kwargs.get('message2', '')
        self.choices = kwargs.get('choices', [])
        default_choice_idx = kwargs.get('default_choice_idx', 0)
        self.ids.next.disabled = False
        layout = self.ids.choices
        layout.bind(minimum_height=layout.setter('height'))
        for idx, (script_type, title, text) in enumerate(self.choices):
            b = CLButton(text=title, height='30dp', group=self.title, allow_no_selection=False)
            b.script_type = script_type
            b.root = self
            b.value = text
            layout.add_widget(b)
            if idx == default_choice_idx:
                b.trigger_action(duration=0)

    def set_text(self, value):
        self.ids.text_input.text = value

    def get_params(self, b):
        return (self.ids.text_input.text, self.script_type)

class ShowSeedDialog(WizardDialog):
    seed_text = StringProperty('')
    message = (_("Write your seed phrase down on paper.") + " " +
               _("The seed phrase will allow you to recover your wallet in case you forget your password or lose your device.") + "\n\n" +
               _("WARNING") + ":\n" +
               "- " + _("Never disclose your seed.") + "\n" +
               "- " + _("Never type it on a website.") + "\n" +
               "- " + _("Do not store it electronically."))

    def __init__(self, wizard, **kwargs):
        super(ShowSeedDialog, self).__init__(wizard, **kwargs)
        self.seed_text = kwargs['seed_text']
        self.opt_ext = True
        self.is_ext = False

    def on_parent(self, instance, value):
        if value:
            self._back = _back = partial(self.ids.back.dispatch, 'on_release')

    def options_dialog(self):
        from .seed_options import SeedOptionsDialog
        def callback(ext, _):
            self.is_ext = ext
        d = SeedOptionsDialog(self.opt_ext, False, self.is_ext, False, callback)
        d.open()

    def get_params(self, b):
        return (self.is_ext,)


class WordButton(Button):
    pass

class WizardButton(Button):
    pass


class RestoreSeedDialog(WizardDialog):

    def __init__(self, wizard, **kwargs):
        super(RestoreSeedDialog, self).__init__(wizard, **kwargs)
        self._test = kwargs['test']
        from electrum.mnemonic import Mnemonic
        from electrum.old_mnemonic import wordlist as old_wordlist
        self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
        self.ids.text_input_seed.text = ''
        self.message = _('Please type your seed phrase using the virtual keyboard.')
        self.title = _('Enter Seed')
        self.opt_ext = kwargs['opt_ext']
        self.opt_bip39 = kwargs['opt_bip39']
        self.is_ext = False
        self.is_bip39 = False

    def options_dialog(self):
        from .seed_options import SeedOptionsDialog
        def callback(ext, bip39):
            self.is_ext = ext
            self.is_bip39 = bip39
            self.update_next_button()
        d = SeedOptionsDialog(self.opt_ext, self.opt_bip39, self.is_ext, self.is_bip39, callback)
        d.open()

    def get_suggestions(self, prefix):
        for w in self.words:
            if w.startswith(prefix):
                yield w

    def update_next_button(self):
        from electrum.keystore import bip39_is_checksum_valid
        text = self.get_text()
        if self.is_bip39:
            is_seed, is_wordlist = bip39_is_checksum_valid(text)
        else:
            is_seed = bool(self._test(text))
        self.ids.next.disabled = not is_seed

    def on_text(self, dt):
        self.update_next_button()

        text = self.ids.text_input_seed.text
        if not text:
            last_word = ''
        elif text[-1] == ' ':
            last_word = ''
        else:
            last_word = text.split(' ')[-1]

        enable_space = False
        self.ids.suggestions.clear_widgets()
        suggestions = [x for x in self.get_suggestions(last_word)]

        if last_word in suggestions:
            b = WordButton(text=last_word)
            self.ids.suggestions.add_widget(b)
            enable_space = True

        for w in suggestions:
            if w != last_word and len(suggestions) < 10:
                b = WordButton(text=w)
                self.ids.suggestions.add_widget(b)

        i = len(last_word)
        p = set()
        for x in suggestions:
            if len(x)>i: p.add(x[i])

        for line in [self.ids.line1, self.ids.line2, self.ids.line3]:
            for c in line.children:
                if isinstance(c, Button):
                    if c.text in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
                        c.disabled = (c.text.lower() not in p) and bool(last_word)
                    elif c.text == ' ':
                        c.disabled = not enable_space

    def on_word(self, w):
        text = self.get_text()
        words = text.split(' ')
        words[-1] = w
        text = ' '.join(words)
        self.ids.text_input_seed.text = text + ' '
        self.ids.suggestions.clear_widgets()

    def get_text(self):
        ti = self.ids.text_input_seed
        return ' '.join(ti.text.strip().split())

    def update_text(self, c):
        c = c.lower()
        text = self.ids.text_input_seed.text
        if c == '<':
            text = text[:-1]
        else:
            text += c
        self.ids.text_input_seed.text = text

    def on_parent(self, instance, value):
        if value:
            tis = self.ids.text_input_seed
            tis.focus = True
            #tis._keyboard.bind(on_key_down=self.on_key_down)
            self._back = _back = partial(self.ids.back.dispatch,
                                         'on_release')

    def on_key_down(self, keyboard, keycode, key, modifiers):
        if keycode[0] in (13, 271):
            self.on_enter()
            return True

    def on_enter(self):
        #self._remove_keyboard()
        # press next
        next = self.ids.next
        if not next.disabled:
            next.dispatch('on_release')

    def _remove_keyboard(self):
        tis = self.ids.text_input_seed
        if tis._keyboard:
            tis._keyboard.unbind(on_key_down=self.on_key_down)
            tis.focus = False

    def get_params(self, b):
        seed_type = 'bip39' if self.is_bip39 else 'electrum'
        return (self.get_text(), seed_type, self.is_ext)


class ConfirmSeedDialog(RestoreSeedDialog):

    def __init__(self, *args, **kwargs):
        RestoreSeedDialog.__init__(self, *args, **kwargs)
        self.ids.seed_dialog_header.ids.options_button.disabled = True
        self.ids.text_input_seed.text = kwargs['seed']

    def get_params(self, b):
        return (self.get_text(),)
    def options_dialog(self):
        pass


class ShowXpubDialog(WizardDialog):

    def __init__(self, wizard, **kwargs):
        WizardDialog.__init__(self, wizard, **kwargs)
        self.xpub = kwargs['xpub']
        self.ids.next.disabled = False

    def do_copy(self):
        self.app._clipboard.copy(self.xpub)

    def do_share(self):
        self.app.do_share(self.xpub, _("Master Public Key"))

    def do_qr(self):
        from .qr_dialog import QRDialog
        popup = QRDialog(_("Master Public Key"), self.xpub, True)
        popup.open()


class AddXpubDialog(WizardDialog):

    def __init__(self, wizard, **kwargs):
        WizardDialog.__init__(self, wizard, **kwargs)
        def is_valid(x):
            try:
                return kwargs['is_valid'](x)
            except Exception:
                return False
        self.is_valid = is_valid
        self.title = kwargs['title']
        self.message = kwargs['message']
        self.allow_multi = kwargs.get('allow_multi', False)

    def check_text(self, dt):
        self.ids.next.disabled = not bool(self.is_valid(self.get_text()))

    def get_text(self):
        ti = self.ids.text_input
        return ti.text.strip()

    def get_params(self, button):
        return (self.get_text(),)

    def scan_xpub(self):
        def on_complete(text):
            if self.allow_multi:
                self.ids.text_input.text += text + '\n'
            else:
                self.ids.text_input.text = text
        self.app.scan_qr(on_complete)

    def do_paste(self):
        self.ids.text_input.text = self.app._clipboard.paste()

    def do_clear(self):
        self.ids.text_input.text = ''




class InstallWizard(BaseWizard, Widget):

    def __init__(self, *args, **kwargs):
        BaseWizard.__init__(self, *args, **kwargs)
        self.app = App.get_running_app()

    def terminate(self, *, storage=None, db=None, aborted=False):
        # storage must be None because manual upgrades are disabled on Kivy
        assert storage is None
        if not aborted:
            password = self.pw_args.password
            storage, db = self.create_storage(self.path)
            self.app.on_wizard_success(storage, db, password)
        else:
            try: os.unlink(self.path)
            except FileNotFoundError: pass
            self.reset_stack()
            self.confirm_dialog(message=_('Wallet creation failed'), run_next=lambda x: self.app.on_wizard_aborted())

    def choice_dialog(self, **kwargs):
        choices = kwargs['choices']
        if len(choices) > 1:
            WizardChoiceDialog(self, **kwargs).open()
        else:
            f = kwargs['run_next']
            f(choices[0][0])

    def multisig_dialog(self, **kwargs): WizardMultisigDialog(self, **kwargs).open()
    def show_seed_dialog(self, **kwargs): ShowSeedDialog(self, **kwargs).open()
    def line_dialog(self, **kwargs): LineDialog(self, **kwargs).open()
    def derivation_and_script_type_gui_specific_dialog(self, **kwargs): ChoiceLineDialog(self, **kwargs).open()

    def confirm_seed_dialog(self, **kwargs):
        kwargs['title'] = _('Confirm Seed')
        kwargs['message'] = _('Please retype your seed phrase, to confirm that you properly saved it')
        kwargs['opt_bip39'] = self.opt_bip39
        kwargs['opt_ext'] = self.opt_ext
        ConfirmSeedDialog(self, **kwargs).open()

    def restore_seed_dialog(self, **kwargs):
        kwargs['opt_bip39'] = self.opt_bip39
        kwargs['opt_ext'] = self.opt_ext
        RestoreSeedDialog(self, **kwargs).open()

    def confirm_dialog(self, **kwargs):
        WizardConfirmDialog(self, **kwargs).open()

    def tos_dialog(self, **kwargs):
        WizardTOSDialog(self, **kwargs).open()

    def email_dialog(self, **kwargs):
        WizardEmailDialog(self, **kwargs).open()

    def otp_dialog(self, **kwargs):
        if kwargs['otp_secret']:
            WizardNewOTPDialog(self, **kwargs).open()
        else:
            WizardKnownOTPDialog(self, **kwargs).open()

    def add_xpub_dialog(self, **kwargs):
        kwargs['message'] += ' ' + _('Use the camera button to scan a QR code.')
        AddXpubDialog(self, **kwargs).open()

    def add_cosigner_dialog(self, **kwargs):
        kwargs['title'] = _("Add Cosigner") + " %d"%kwargs['index']
        kwargs['message'] = _('Please paste your cosigners master public key, or scan it using the camera button.')
        AddXpubDialog(self, **kwargs).open()

    def show_xpub_dialog(self, **kwargs): ShowXpubDialog(self, **kwargs).open()

    def show_message(self, msg): self.show_error(msg)

    def show_error(self, msg):
        Clock.schedule_once(lambda dt: self.app.show_error(msg))

    def request_password(self, run_next, force_disable_encrypt_cb=False):
        if self.app.password is not None:
            run_next(self.app.password, True)
            return
        def on_success(old_pw, pw):
            assert old_pw is None
            run_next(pw, True)
        def on_failure():
            self.show_error(_('Password mismatch'))
            self.request_password(run_next)
        popup = PasswordDialog(
            self.app,
            check_password=lambda x:True,
            on_success=on_success,
            on_failure=on_failure,
            is_change=True,
            is_password=True,
            message=_('Choose a password'))
        popup.open()

    def action_dialog(self, action, run_next):
        f = getattr(self, action)
        f()
