import sys

from PyQt4.QtGui import *
from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore

import electrum
from electrum.i18n import _

from seed_dialog import SeedDisplayLayout, SeedWarningLayout, SeedInputLayout
from network_dialog import NetworkChoiceLayout
from util import *
from password_dialog import PasswordLayout, PW_NEW, PW_PASSPHRASE

from electrum.wallet import Wallet
from electrum.mnemonic import prepare_seed
from electrum.util import UserCancelled
from electrum.wizard import (WizardBase,
                             MSG_ENTER_PASSWORD, MSG_RESTORE_PASSPHRASE,
                             MSG_COSIGNER, MSG_ENTER_SEED_OR_MPK,
                             MSG_SHOW_MPK, MSG_VERIFY_SEED,
                             MSG_GENERATING_WAIT)

def clean_text(seed_e):
    text = unicode(seed_e.toPlainText()).strip()
    text = ' '.join(text.split())
    return text

class CosignWidget(QWidget):
    size = 120

    def __init__(self, m, n):
        QWidget.__init__(self)
        self.R = QRect(0, 0, self.size, self.size)
        self.setGeometry(self.R)
        self.setMinimumHeight(self.size)
        self.setMaximumHeight(self.size)
        self.m = m
        self.n = n

    def set_n(self, n):
        self.n = n
        self.update()

    def set_m(self, m):
        self.m = m
        self.update()

    def paintEvent(self, event):
        import math
        bgcolor = self.palette().color(QPalette.Background)
        pen = QPen(bgcolor, 7, QtCore.Qt.SolidLine)
        qp = QPainter()
        qp.begin(self)
        qp.setPen(pen)
        qp.setRenderHint(QPainter.Antialiasing)
        qp.setBrush(Qt.gray)
        for i in range(self.n):
            alpha = int(16* 360 * i/self.n)
            alpha2 = int(16* 360 * 1/self.n)
            qp.setBrush(Qt.green if i<self.m else Qt.gray)
            qp.drawPie(self.R, alpha, alpha2)
        qp.end()


# WindowModalDialog must come first as it overrides show_error
class InstallWizard(QDialog, MessageBoxMixin, WizardBase):

    def __init__(self, config, app, plugins):
        QDialog.__init__(self, None)
        self.setWindowTitle('Electrum  -  ' + _('Install Wizard'))
        self.app = app
        self.config = config
        # Set for base base class
        self.plugins = plugins
        self.language_for_seed = config.get('language')
        self.setMinimumSize(530, 370)
        self.setMaximumSize(530, 370)
        self.connect(self, QtCore.SIGNAL('accept'), self.accept)
        self.title = WWLabel()
        self.main_widget = QWidget()
        self.cancel_button = QPushButton(_("Cancel"), self)
        self.next_button = QPushButton(_("Next"), self)
        self.next_button.setDefault(True)
        self.logo = QLabel()
        self.please_wait = QLabel(_("Please wait..."))
        self.please_wait.setAlignment(Qt.AlignCenter)
        self.icon_filename = None
        self.loop = QEventLoop()
        self.rejected.connect(lambda: self.loop.exit(False))
        self.cancel_button.clicked.connect(lambda: self.loop.exit(False))
        self.next_button.clicked.connect(lambda: self.loop.exit(True))
        outer_vbox = QVBoxLayout(self)
        inner_vbox = QVBoxLayout()
        inner_vbox = QVBoxLayout()
        inner_vbox.addWidget(self.title)
        inner_vbox.addWidget(self.main_widget)
        inner_vbox.addStretch(1)
        inner_vbox.addWidget(self.please_wait)
        inner_vbox.addStretch(1)
        icon_vbox = QVBoxLayout()
        icon_vbox.addWidget(self.logo)
        icon_vbox.addStretch(1)
        hbox = QHBoxLayout()
        hbox.addLayout(icon_vbox)
        hbox.addSpacing(5)
        hbox.addLayout(inner_vbox)
        hbox.setStretchFactor(inner_vbox, 1)
        outer_vbox.addLayout(hbox)
        outer_vbox.addLayout(Buttons(self.cancel_button, self.next_button))
        self.set_icon(':icons/electrum.png')
        self.show()
        self.raise_()
        self.refresh_gui()  # Need for QT on MacOSX.  Lame.

    def finished(self):
        '''Ensure the dialog is closed.'''
        self.accept()
        self.refresh_gui()

    def on_error(self, exc_info):
        if not isinstance(exc_info[1], UserCancelled):
            traceback.print_exception(*exc_info)
            self.show_error(str(exc_info[1]))

    def set_icon(self, filename):
        prior_filename, self.icon_filename = self.icon_filename, filename
        self.logo.setPixmap(QPixmap(filename).scaledToWidth(60))
        return prior_filename

    def set_main_layout(self, layout, title=None, raise_on_cancel=True,
                        next_enabled=True):
        self.title.setText(title or "")
        self.title.setVisible(bool(title))
        # Get rid of any prior layout by assigning it to a temporary widget
        prior_layout = self.main_widget.layout()
        if prior_layout:
            QWidget().setLayout(prior_layout)
        self.main_widget.setLayout(layout)
        self.cancel_button.setEnabled(True)
        self.next_button.setEnabled(next_enabled)
        self.main_widget.setVisible(True)
        self.please_wait.setVisible(False)
        result = self.loop.exec_()
        if not result and raise_on_cancel:
            raise UserCancelled
        self.title.setVisible(False)
        self.cancel_button.setEnabled(False)
        self.next_button.setEnabled(False)
        self.main_widget.setVisible(False)
        self.please_wait.setVisible(True)
        self.refresh_gui()
        return result

    def refresh_gui(self):
        # For some reason, to refresh the GUI this needs to be called twice
        self.app.processEvents()
        self.app.processEvents()

    def run(self, *args):
        '''Wrap the base wizard implementation with try/except blocks
        to give a sensible error message to the user.'''
        wallet = None
        try:
            wallet = WizardBase.run(self, *args)
        except UserCancelled:
            self.print_error("wallet creation cancelled by user")
            self.accept()  # For when called from menu
        except BaseException as e:
            self.on_error(sys.exc_info())
            raise
        return wallet

    def remove_from_recently_open(self, filename):
        self.config.remove_from_recently_open(filename)

    def request_seed(self, title, is_valid=None):
        is_valid = is_valid or Wallet.is_any
        slayout = SeedInputLayout()
        def sanitized_seed():
            return clean_text(slayout.seed_edit())
        def set_enabled():
            self.next_button.setEnabled(is_valid(sanitized_seed()))
        slayout.seed_edit().textChanged.connect(set_enabled)
        self.set_main_layout(slayout.layout(), title, next_enabled=False)
        return sanitized_seed()

    def show_seed(self, seed):
        slayout = SeedWarningLayout(seed)
        self.set_main_layout(slayout.layout())

    def verify_seed(self, seed, is_valid=None):
        while True:
            r = self.request_seed(MSG_VERIFY_SEED, is_valid)
            if prepare_seed(r) == prepare_seed(seed):
                return
            self.show_error(_('Incorrect seed'))

    def show_and_verify_seed(self, seed, is_valid=None):
        """Show the user their seed.  Ask them to re-enter it.  Return
        True on success."""
        self.show_seed(seed)
        self.app.clipboard().clear()
        self.verify_seed(seed, is_valid)

    def pw_layout(self, msg, kind):
        playout = PasswordLayout(None, msg, kind, self.next_button)
        self.set_main_layout(playout.layout())
        return playout.new_password()

    def request_passphrase(self, device_text, restore=True):
        """Request a passphrase for a wallet from the given device and
        confirm it.  restore is True if restoring a wallet.  Should return
        a unicode string."""
        if restore:
            msg = MSG_RESTORE_PASSPHRASE % device_text
        return unicode(self.pw_layout(msg, PW_PASSPHRASE) or '')

    def request_password(self, msg=None):
        """Request the user enter a new password and confirm it.  Return
        the password or None for no password."""
        return self.pw_layout(msg or MSG_ENTER_PASSWORD, PW_NEW)

    def show_restore(self, wallet, network):
        # FIXME: these messages are shown after the install wizard is
        # finished and the window closed.  On MacOSX they appear parented
        # with a re-appeared ghost install wizard window...
        if network:
            def task():
                wallet.wait_until_synchronized()
                if wallet.is_found():
                    msg = _("Recovery successful")
                else:
                    msg = _("No transactions found for this seed")
                self.emit(QtCore.SIGNAL('synchronized'), msg)
            self.connect(self, QtCore.SIGNAL('synchronized'), self.show_message)
            t = threading.Thread(target = task)
            t.start()
        else:
            msg = _("This wallet was restored offline. It may "
                    "contain more addresses than displayed.")
            self.show_message(msg)

    def create_addresses(self, wallet):
        def task():
            wallet.synchronize()
            self.emit(QtCore.SIGNAL('accept'))
        t = threading.Thread(target = task)
        t.start()
        self.please_wait.setText(MSG_GENERATING_WAIT)
        self.refresh_gui()

    def query_create_or_restore(self, wallet_kinds):
        """Ask the user what they want to do, and which wallet kind.
        wallet_kinds is an array of translated wallet descriptions.
        Return a a tuple (action, kind_index).  Action is 'create' or
        'restore', and kind the index of the wallet kind chosen."""

        actions = [_("Create a new wallet"),
                   _("Restore a wallet or import keys")]
        title = _("Electrum could not find an existing wallet.")
        actions_clayout = ChoicesLayout(_("What do you want to do?"), actions)
        wallet_clayout = ChoicesLayout(_("Wallet kind:"), wallet_kinds)

        vbox = QVBoxLayout()
        vbox.addLayout(actions_clayout.layout())
        vbox.addLayout(wallet_clayout.layout())
        self.set_main_layout(vbox, title)

        action = ['create', 'restore'][actions_clayout.selected_index()]
        return action, wallet_clayout.selected_index()

    def query_hw_wallet_choice(self, msg, action, choices):
        actions = [_("Initialize a new or wiped device"),
                   _("Use a device you have already set up"),
                   _("Restore Electrum wallet from device seed words")]
        default_action = 1 if action == 'create' else 2
        actions_clayout = ChoicesLayout(_("What do you want to do?"), actions,
                                        checked_index=default_action)
        wallet_clayout = ChoicesLayout(msg, choices)

        vbox = QVBoxLayout()
        vbox.addLayout(actions_clayout.layout())
        vbox.addLayout(wallet_clayout.layout())
        self.set_main_layout(vbox)
        self.next_button.setEnabled(len(choices) != 0)

        if actions_clayout.selected_index() == 2:
            action = 'restore'
        else:
            action = 'create'
        return action, wallet_clayout.selected_index()

    def request_many(self, n, xpub_hot=None):
        vbox = QVBoxLayout()
        scroll = QScrollArea()
        scroll.setWidgetResizable(True)
        scroll.setFrameShape(QFrame.NoFrame)
        vbox.addWidget(scroll)

        w = QWidget()
        innerVbox = QVBoxLayout(w)
        scroll.setWidget(w)

        entries = []

        if xpub_hot:
            layout = SeedDisplayLayout(xpub_hot, title=MSG_SHOW_MPK, sid='hot')
        else:
            layout = SeedInputLayout(title=MSG_ENTER_SEED_OR_MPK, sid='hot')
            entries.append(layout.seed_edit())
        innerVbox.addLayout(layout.layout())

        for i in range(n):
            msg = MSG_COSIGNER % (i + 1) if xpub_hot else MSG_ENTER_SEED_OR_MPK
            layout = SeedInputLayout(title=msg, sid='cold')
            innerVbox.addLayout(layout.layout())
            entries.append(layout.seed_edit())

        def get_texts():
            return [clean_text(entry) for entry in entries]
        def set_enabled():
            texts = get_texts()
            is_valid = Wallet.is_xpub if xpub_hot else Wallet.is_any
            all_valid = all(is_valid(text) for text in texts)
            if xpub_hot:
                texts.append(xpub_hot)
            has_dups = len(set(texts)) < len(texts)
            self.next_button.setEnabled(all_valid and not has_dups)
        for e in entries:
            e.textChanged.connect(set_enabled)
        self.set_main_layout(vbox, next_enabled=False)
        return get_texts()

    def choose_server(self, network):
        title = _("Electrum communicates with remote servers to get "
                  "information about your transactions and addresses. The "
                  "servers all fulfil the same purpose only differing in "
                  "hardware. In most cases you simply want to let Electrum "
                  "pick one at random.  However if you prefer feel free to "
                  "select a server manually.")
        choices = [_("Auto connect"), _("Select server manually")]
        choices_title = _("How do you want to connect to a server? ")
        clayout = ChoicesLayout(choices_title, choices)
        self.set_main_layout(clayout.layout(), title)

        auto_connect = True
        if clayout.selected_index() == 1:
            nlayout = NetworkChoiceLayout(network, self.config, wizard=True)
            if self.set_main_layout(nlayout.layout(), raise_on_cancel=False):
                nlayout.accept()
                auto_connect = False
        self.config.set_key('auto_connect', auto_connect, True)
        network.auto_connect = auto_connect

    def query_choice(self, msg, choices):
        clayout = ChoicesLayout(msg, choices)
        self.set_main_layout(clayout.layout(), next_enabled=bool(choices))
        return clayout.selected_index()

    def query_multisig(self, action):
        cw = CosignWidget(2, 2)
        m_edit = QSpinBox()
        n_edit = QSpinBox()
        m_edit.setValue(2)
        n_edit.setValue(2)
        n_edit.setMinimum(2)
        n_edit.setMaximum(15)
        m_edit.setMinimum(1)
        m_edit.setMaximum(2)
        n_edit.valueChanged.connect(m_edit.setMaximum)

        n_edit.valueChanged.connect(cw.set_n)
        m_edit.valueChanged.connect(cw.set_m)

        hbox = QHBoxLayout()
        hbox.addWidget(QLabel(_('Require')))
        hbox.addWidget(m_edit)
        hbox.addWidget(QLabel(_('of')))
        hbox.addWidget(n_edit)
        hbox.addWidget(QLabel(_('signatures')))
        hbox.addStretch(1)

        vbox = QVBoxLayout()
        vbox.addWidget(cw)
        vbox.addWidget(WWLabel(_("Choose the number of signatures needed "
                          "to unlock funds in your wallet:")))
        vbox.addLayout(hbox)
        self.set_main_layout(vbox, _("Multi-Signature Wallet"))
        m = int(m_edit.value())
        n = int(n_edit.value())
        wallet_type = '%dof%d'%(m,n)
        return wallet_type