# -*- coding: utf-8 -*-

import logging
import asyncio

from PyQt5.QtWidgets import QWidget, QComboBox, QDialog
from PyQt5.QtCore import pyqtSlot, QEvent, QLocale, QDateTime, pyqtSignal
from ucoinpy.api import bma

from ..tools.exceptions import MembershipNotFoundError
from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
from ..core.graph import Graph
from ..core.registry import BlockchainState
from .member import MemberDialog
from .certification import CertificationDialog
from .transfer import TransferMoneyDialog
from .contact import ConfigureContactDialog
from ..gen_resources.wot_tab_uic import Ui_WotTabWidget
from cutecoin.gui.views.wot import NODE_STATUS_HIGHLIGHTED, NODE_STATUS_SELECTED, NODE_STATUS_OUT
from cutecoin.gui.widgets.busy import Busy


class WotTabWidget(QWidget, Ui_WotTabWidget):

    money_sent = pyqtSignal()

    def __init__(self, app):
        """
        :param cutecoin.core.app.Application app:   Application instance
        :return:
        """
        super().__init__()
        # construct from qtDesigner
        self.setupUi(self)

        # Default text when combo lineEdit is empty
        self.comboBoxSearch.lineEdit().setPlaceholderText(self.tr('Research a pubkey, an uid...'))
        #  add combobox events
        self.comboBoxSearch.lineEdit().returnPressed.connect(self.search)
        # To fix a recall of the same item with different case,
        # the edited text is not added in the item list
        self.comboBoxSearch.setInsertPolicy(QComboBox.NoInsert)

        self.busy = Busy(self.graphicsView)
        self.busy.hide()

        # add scene events
        self.graphicsView.scene().node_clicked.connect(self.handle_node_click)
        self.graphicsView.scene().node_signed.connect(self.sign_node)
        self.graphicsView.scene().node_transaction.connect(self.send_money_to_node)
        self.graphicsView.scene().node_contact.connect(self.add_node_as_contact)
        self.graphicsView.scene().node_member.connect(self.identity_informations)
        self.graphicsView.scene().node_copy_pubkey.connect(self.copy_node_pubkey)

        self.account = None
        self.community = None
        self.password_asker = None
        self.app = app
        self.draw_task = None

        # nodes list for menu from search
        self.nodes = list()

        # create node metadata from account
        self._current_identity = None

    def cancel_once_tasks(self):
        cancel_once_task(self, self.draw_graph)
        cancel_once_task(self, self.refresh_informations_frame)
        cancel_once_task(self, self.reset)

    def change_account(self, account, password_asker):
        self.account = account
        self.password_asker = password_asker

    def change_community(self, community):
        self._auto_refresh(community)
        self.community = community
        self.reset()

    def _auto_refresh(self, new_community):
        if self.community:
            try:
                self.community.network.new_block_mined.disconnect(self.refresh)
            except TypeError as e:
                if "connected" in str(e):
                    logging.debug("new block mined not connected")
        if self.app.preferences["auto_refresh"]:
            if new_community:
                new_community.network.new_block_mined.connect(self.refresh)
            elif self.community:
                self.community.network.new_block_mined.connect(self.refresh)

    @once_at_a_time
    @asyncify
    @asyncio.coroutine
    def refresh_informations_frame(self):
        parameters = self.community.parameters
        try:
            identity = yield from self.account.identity(self.community)
            membership = identity.membership(self.community)
            renew_block = membership['blockNumber']
            last_renewal = self.community.get_block(renew_block)['medianTime']
            expiration = last_renewal + parameters['sigValidity']
        except MembershipNotFoundError:
            last_renewal = None
            expiration = None

        certified = yield from identity.unique_valid_certified_by(self.app.identities_registry, self.community)
        certifiers = yield from identity.unique_valid_certifiers_of(self.app.identities_registry, self.community)
        if last_renewal and expiration:
            date_renewal = QLocale.toString(
                QLocale(),
                QDateTime.fromTime_t(last_renewal).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat)
            )
            date_expiration = QLocale.toString(
                QLocale(),
                QDateTime.fromTime_t(expiration).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat)
            )

            if self.account.pubkey in self.community.members_pubkeys():
                # set infos in label
                self.label_general.setText(
                    self.tr("""
                    <table cellpadding="5">
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    </table>
                    """).format(
                        self.account.name, self.account.pubkey,
                        self.tr("Membership"),
                        self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration),
                        self.tr("Your web of trust"),
                        self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
                                                                                             len(certified))
                    )
                )
            else:
                # set infos in label
                self.label_general.setText(
                    self.tr("""
                    <table cellpadding="5">
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                    </table>
                    """).format(
                        self.account.name, self.account.pubkey,
                        self.tr("Not a member"),
                        self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration),
                        self.tr("Your web of trust"),
                        self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
                                                                                             len(certified))
                    )
                )
        else:
            # set infos in label
            self.label_general.setText(
                self.tr("""
                <table cellpadding="5">
                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                <tr><td align="right"><b>{:}</b></td></tr>
                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
                </table>
                """).format(
                    self.account.name, self.account.pubkey,
                    self.tr("Not a member"),
                    self.tr("Your web of trust"),
                    self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
                                                                                         len(certified))
                )
            )

    @pyqtSlot(dict)
    def handle_node_click(self, metadata):
        self.draw_graph(
            self.app.identities_registry.from_handled_data(
                metadata['text'],
                metadata['id'],
                BlockchainState.VALIDATED,
                self.community
            )
        )

    @once_at_a_time
    @asyncify
    @asyncio.coroutine
    def draw_graph(self, identity):
        """
        Draw community graph centered on the identity

        :param cutecoin.core.registry.Identity identity: Graph node identity
        """
        logging.debug("Draw graph - " + identity.uid)
        self.busy.show()

        if self.community:
            identity_account = yield from self.account.identity(self.community)

            #Connect new identity
            if self._current_identity != identity:
                self._current_identity = identity

            # create Identity from node metadata
            certifier_list = yield from identity.unique_valid_certifiers_of(self.app.identities_registry,
                                                                            self.community)
            certified_list = yield from identity.unique_valid_certified_by(self.app.identities_registry,
                                                                           self.community)

            # create empty graph instance
            graph = Graph(self.app, self.community)

            # add wallet node
            node_status = 0
            if identity == identity_account:
                node_status += NODE_STATUS_HIGHLIGHTED
            is_member = yield from identity.is_member(self.community)
            if is_member is False:
                node_status += NODE_STATUS_OUT
            node_status += NODE_STATUS_SELECTED
            graph.add_identity(identity, node_status)

            # populate graph with certifiers-of
            yield from graph.add_certifier_list(certifier_list, identity, identity_account)
            # populate graph with certified-by
            yield from graph.add_certified_list(certified_list, identity, identity_account)

            # draw graph in qt scene
            self.graphicsView.scene().update_wot(graph.get())

            # if selected member is not the account member...
            if identity.pubkey != identity_account.pubkey:
                # add path from selected member to account member
                path = yield from graph.get_shortest_path_between_members(identity, identity_account)
                if path:
                    self.graphicsView.scene().update_path(path)
        self.busy.hide()

    @once_at_a_time
    @asyncify
    @asyncio.coroutine
    def reset(self, checked=False):
        """
        Reset graph scene to wallet identity
        """
        if self.account and self.community:
            identity = yield from self.account.identity(self.community)
            self.draw_graph(identity)

    def refresh(self):
        """
        Refresh graph scene to current metadata
        """
        if self._current_identity:
            self.draw_graph(self._current_identity)
        else:
            self.reset()

    @asyncify
    @asyncio.coroutine
    def search(self):
        """
        Search nodes when return is pressed in combobox lineEdit
        """
        text = self.comboBoxSearch.lineEdit().text()

        if len(text) < 2:
            return False
        response = yield from self.community.bma_access.future_request(bma.wot.Lookup, {'search': text})

        nodes = {}
        for identity in response['results']:
            nodes[identity['pubkey']] = identity['uids'][0]['uid']

        if nodes:
            self.nodes = list()
            self.comboBoxSearch.clear()
            self.comboBoxSearch.lineEdit().setText(text)
            for pubkey, uid in nodes.items():
                self.nodes.append({'pubkey': pubkey, 'uid': uid})
                self.comboBoxSearch.addItem(uid)
            self.comboBoxSearch.showPopup()

    def select_node(self, index):
        """
        Select node in graph when item is selected in combobox
        """
        if index < 0 or index >= len(self.nodes):
            return False
        node = self.nodes[index]
        metadata = {'id': node['pubkey'], 'text': node['uid']}
        self.draw_graph(
            self.app.identities_registry.from_handled_data(
                metadata['text'],
                metadata['id'],
                BlockchainState.VALIDATED,
                self.community
            )
        )

    def identity_informations(self, metadata):
        identity = self.app.identities_registry.from_handled_data(
            metadata['text'],
            metadata['id'],
            BlockchainState.VALIDATED,
            self.community
        )
        dialog = MemberDialog(self.app, self.account, self.community, identity)
        dialog.exec_()

    @asyncify
    @asyncio.coroutine
    def sign_node(self, metadata):
        identity = self.app.identities_registry.from_handled_data(
            metadata['text'],
            metadata['id'],
            BlockchainState.VALIDATED,
            self.community
        )
        yield from CertificationDialog.certify_identity(self.app, self.account, self.password_asker,
                                             self.community, identity)

    @asyncify
    @asyncio.coroutine
    def send_money_to_node(self, metadata):
        identity = self.app.identities_registry.from_handled_data(
            metadata['text'],
            metadata['id'],
            BlockchainState.VALIDATED,
            self.community
        )
        result = yield from TransferMoneyDialog.send_money_to_identity(self.app, self.account, self.password_asker,
                                                            self.community, identity)
        if result == QDialog.Accepted:
            self.money_sent.emit()

    def copy_node_pubkey(self, metadata):
        cb = self.app.qapp.clipboard()
        cb.clear(mode=cb.Clipboard)
        cb.setText(metadata['id'], mode=cb.Clipboard)

    def add_node_as_contact(self, metadata):
        # check if contact already exists...
        if metadata['id'] == self.account.pubkey \
                or metadata['id'] in [contact['pubkey'] for contact in self.account.contacts]:
            return False
        dialog = ConfigureContactDialog(self.account, self.window(), {'name': metadata['text'],
                                                                      'pubkey': metadata['id']})
        result = dialog.exec_()
        if result == QDialog.Accepted:
            self.window().refresh_contacts()

    def resizeEvent(self, event):
        self.busy.resize(event.size())
        super().resizeEvent(event)

    def changeEvent(self, event):
        """
        Intercepte LanguageChange event to translate UI
        :param QEvent QEvent: Event
        :return:
        """
        if event.type() == QEvent.LanguageChange:
            self.retranslateUi(self)
            self._auto_refresh(None)
            self.refresh()
        return super(WotTabWidget, self).changeEvent(event)