from PySide2 import QtCore, QtWidgets
import web3
from web3 import Web3, HTTPProvider
from populus.utils.wait import wait_for_transaction_receipt

w3 = Web3(HTTPProvider('http://localhost:7545'))

false = False
true = True
abi = [
            {
                "constant": false,
                "gas": 71987,
                "inputs": [
                    {
                        "name": "tweet",
                        "type": "bytes32"
                    }
                ],
                "name": "write_a_tweet",
                "outputs": [],
                "payable": false,
                "type": "function"
            },
            {
                "constant": true,
                "gas": 968,
                "inputs": [
                    {
                        "name": "arg0",
                        "type": "address"
                    },
                    {
                        "name": "arg1",
                        "type": "int128"
                    }
                ],
                "name": "tweets__messages",
                "outputs": [
                    {
                        "name": "out",
                        "type": "bytes32"
                    }
                ],
                "payable": false,
                "type": "function"
            },
            {
                "constant": true,
                "gas": 787,
                "inputs": [
                    {
                        "name": "arg0",
                        "type": "address"
                    }
                ],
                "name": "tweets__index",
                "outputs": [
                    {
                        "name": "out",
                        "type": "int128"
                    }
                ],
                "payable": false,
                "type": "function"
            }
        ]

with open('address.txt', 'r') as f:
    address = f.read().rstrip("\n")

TwitterOnBlockchain = w3.eth.contract(address=address, abi=abi)

def strip_x00_from_tweet(tweet):
    null_index = tweet.find(b'\x00')
    return tweet[:null_index]


class Web3ReadTweetsThread(QtCore.QThread):

    fetched_posts = QtCore.Signal(list)
    account = ''

    def __init__(self, parent=None):
        super(Web3ReadTweetsThread, self).__init__(parent)

    def setAccount(self, account):
        self.account = account

    def run(self):
        try:
            index = TwitterOnBlockchain.functions.tweets__index(Web3.toChecksumAddress(self.account)).call()
        except web3.exceptions.ValidationError:
            return
        tweets = []
        for i in range(index):
            tweet = TwitterOnBlockchain.functions.tweets__messages(Web3.toChecksumAddress(self.account), i).call()
            tweets.append(tweet.decode('utf-8'))
        self.fetched_posts.emit(tweets)


class Web3WriteATweetThread(QtCore.QThread):

    write_a_tweet = QtCore.Signal()
    private_key = ''
    tweet = ''

    def __init__(self, parent=None):
        super(Web3WriteATweetThread, self).__init__(parent)

    def setPrivateKey(self, private_key):
        self.private_key = private_key

    def setTweet(self, tweet):
        self.tweet = tweet

    def run(self):
        try:
            account = w3.eth.account.privateKeyToAccount('0x'+self.private_key)
        except ValueError:
            QtWidgets.QMessageBox.warning(self, 'Error', 'Private key is invalid.')
            return
        nonce = w3.eth.getTransactionCount(Web3.toChecksumAddress(account.address))
        txn = TwitterOnBlockchain.functions.write_a_tweet(self.tweet.encode('utf-8')).buildTransaction({
                  'from': account.address,
                  'gas': 70000,
                  'gasPrice': w3.toWei('1', 'gwei'),
                  'nonce': nonce
              })
        signed = w3.eth.account.signTransaction(txn, private_key=self.private_key)
        txhash = w3.eth.sendRawTransaction(signed.rawTransaction)
        wait_for_transaction_receipt(w3, txhash)
        self.write_a_tweet.emit()


class TwitterDapp(QtWidgets.QWidget):

    private_key = '0x0'
    account = ''
    bookmark_file = 'bookmark.txt'
    addresses = []

    def __init__(self):
        super(TwitterDapp, self).__init__()

        self.createPrivateKeyGroupBox()
        self.createWritingTweetGroupBox()
        self.createTweetsGroupBox()
        self.createBookmarkGroupBox()

        self.setWindowTitle("Twitter-Like Blockchain Dapp")

        mainLayout = QtWidgets.QVBoxLayout()
        mainLayout.addWidget(self.private_key_group_box)
        mainLayout.addLayout(self.write_button_layout)
        mainLayout.addWidget(self.tweets_group_box)
        mainLayout.addWidget(self.bookmark_group_box)

        self.setLayout(mainLayout)

        self.web3_read_tweets_thread = Web3ReadTweetsThread()
        self.web3_read_tweets_thread.fetched_posts.connect(self.fillPosts)
        self.web3_write_a_tweet_thread = Web3WriteATweetThread()
        self.web3_write_a_tweet_thread.write_a_tweet.connect(self.successfullyWriteATweet)

    def createPrivateKeyGroupBox(self):
        self.private_key_group_box = QtWidgets.QGroupBox("Account")
        self.private_key_field = QtWidgets.QLineEdit()
        self.welcome_message = QtWidgets.QLabel()

        layout = QtWidgets.QFormLayout()
        layout.addRow(QtWidgets.QLabel("Private key:"), self.private_key_field)
        button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok)
        button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.checkPrivateKey)
        layout.addRow(button_box)
        layout.addRow(self.welcome_message)

        self.private_key_group_box.setLayout(layout)

    def createWritingTweetGroupBox(self):
        self.tweet_button = QtWidgets.QPushButton("Write a new tweet")
        self.tweet_button.setMaximumSize(200,40)
        self.write_button_layout = QtWidgets.QHBoxLayout()
        self.write_button_layout.addWidget(self.tweet_button)
        self.connect(self.tweet_button, QtCore.SIGNAL('clicked()'), self.writeANewTweet)

    def createTweetsGroupBox(self):
        self.tweets_group_box = QtWidgets.QGroupBox("Tweets")
        self.account_address = QtWidgets.QLineEdit()
        self.fetch_button = QtWidgets.QPushButton("Fetch")
        self.add_to_bookmark_button = QtWidgets.QPushButton("Bookmark it!")

        self.connect(self.fetch_button, QtCore.SIGNAL('clicked()'), self.fetchTweets)
        self.connect(self.add_to_bookmark_button, QtCore.SIGNAL('clicked()'), self.bookmarkAddress)

        account_address_layout = QtWidgets.QHBoxLayout()
        account_address_layout.addWidget(self.account_address)
        account_address_layout.addWidget(self.fetch_button)
        account_address_layout.addWidget(self.add_to_bookmark_button)

        self.tweets_layout = QtWidgets.QVBoxLayout()

        self.tweets_main_layout = QtWidgets.QVBoxLayout()
        self.tweets_main_layout.addWidget(QtWidgets.QLabel("Address:"))
        self.tweets_main_layout.addLayout(account_address_layout)
        self.tweets_main_layout.addSpacing(20)
        self.tweets_main_layout.addLayout(self.tweets_layout)
        self.tweets_group_box.setLayout(self.tweets_main_layout)

    def createBookmarkGroupBox(self):
        self.bookmark_group_box = QtWidgets.QGroupBox("Bookmark")

        self.bookmark_layout = QtWidgets.QVBoxLayout()

        self.bookmark_group_box.setLayout(self.bookmark_layout)

        with open(self.bookmark_file) as f:
            addresses = f.readlines()
        self.addresses = list(map(lambda x: x.rstrip(), filter(lambda x: len(x) > 1, addresses)))
        self.fillBookmark()

    @QtCore.Slot()
    def fetchTweets(self):
        account = self.account_address.displayText()
        self.web3_read_tweets_thread.setAccount(account)
        self.web3_read_tweets_thread.start()

    @QtCore.Slot()
    def bookmarkAddress(self):
        account = self.account_address.displayText()
        if account:
            self.addresses.append(account)
            self.addresses = list(set(self.addresses))
        with open(self.bookmark_file, 'w') as f:
            for address in self.addresses:
                f.write(address + "\n")
        self.fillBookmark()

    @QtCore.Slot()
    def writeANewTweet(self):
        text, ok = QtWidgets.QInputDialog.getText(self, "Write a new tweet",
                 "Tweet:", QtWidgets.QLineEdit.Normal, "")
        if ok and text != '':
            self.web3_write_a_tweet_thread.setPrivateKey(self.private_key)
            self.web3_write_a_tweet_thread.setTweet(text)
            self.web3_write_a_tweet_thread.start()

    def checkPrivateKey(self):
        self.private_key = self.private_key_field.displayText()
        try:
            self.account = w3.eth.account.privateKeyToAccount('0x'+self.private_key)
        except ValueError:
            QtWidgets.QMessageBox.warning(self, 'Error', 'Private key is invalid.')
            return
        self.welcome_message.setText('Welcome, ' + self.account.address + '!')
        self.private_key_field.clear()

    def clearTweetsField(self):
        while True:
            label_item = self.tweets_layout.takeAt(0)
            if label_item is None:
                break
            else:
                label_item.widget().close()

    def fillPosts(self, posts):
        self.clearTweetsField()
        for post in posts:
            label_field = QtWidgets.QLabel(post)
            self.tweets_layout.addWidget(label_field)

    def clearBookmarkField(self):
        while True:
            label_item = self.bookmark_layout.takeAt(0)
            if label_item is None:
                break
            else:
                label_item.widget().close()

    def fillBookmark(self):
        self.clearBookmarkField()
        for address in self.addresses:
            label_field = QtWidgets.QLabel(address)
            label_field.setTextInteractionFlags(label_field.textInteractionFlags() | QtCore.Qt.TextSelectableByMouse)
            self.bookmark_layout.addWidget(label_field)

    def successfullyWriteATweet(self):
        self.welcome_message.setText('You have successfully written a new tweet!')


if __name__ == '__main__':

    import sys

    app = QtWidgets.QApplication(sys.argv)
    twitter_dapp = TwitterDapp()
    twitter_dapp.show()
    sys.exit(app.exec_())