import shared
import threading
import time
import sys
from pyelliptic.openssl import OpenSSL
import ctypes
import hashlib
import highlevelcrypto
from addresses import *
from pyelliptic import arithmetic
import tr

class addressGenerator(threading.Thread):

    def __init__(self):
        # QThread.__init__(self, parent)
        threading.Thread.__init__(self)

    def run(self):
        while True:
            queueValue = shared.addressGeneratorQueue.get()
            nonceTrialsPerByte = 0
            payloadLengthExtraBytes = 0
            if queueValue[0] == 'createChan':
                command, addressVersionNumber, streamNumber, label, deterministicPassphrase = queueValue
                eighteenByteRipe = False
                numberOfAddressesToMake = 1
            elif queueValue[0] == 'joinChan':
                command, chanAddress, label, deterministicPassphrase = queueValue
                eighteenByteRipe = False
                addressVersionNumber = decodeAddress(chanAddress)[1]
                streamNumber = decodeAddress(chanAddress)[2]
                numberOfAddressesToMake = 1
            elif len(queueValue) == 7:
                command, addressVersionNumber, streamNumber, label, numberOfAddressesToMake, deterministicPassphrase, eighteenByteRipe = queueValue
            elif len(queueValue) == 9:
                command, addressVersionNumber, streamNumber, label, numberOfAddressesToMake, deterministicPassphrase, eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes = queueValue
            else:
                sys.stderr.write(
                    'Programming error: A structure with the wrong number of values was passed into the addressGeneratorQueue. Here is the queueValue: %s\n' % repr(queueValue))
            if addressVersionNumber < 3 or addressVersionNumber > 3:
                sys.stderr.write(
                    'Program error: For some reason the address generator queue has been given a request to create at least one version %s address which it cannot do.\n' % addressVersionNumber)
            if nonceTrialsPerByte == 0:
                nonceTrialsPerByte = shared.config.getint(
                    'bitmessagesettings', 'defaultnoncetrialsperbyte')
            if nonceTrialsPerByte < shared.networkDefaultProofOfWorkNonceTrialsPerByte:
                nonceTrialsPerByte = shared.networkDefaultProofOfWorkNonceTrialsPerByte
            if payloadLengthExtraBytes == 0:
                payloadLengthExtraBytes = shared.config.getint(
                    'bitmessagesettings', 'defaultpayloadlengthextrabytes')
            if payloadLengthExtraBytes < shared.networkDefaultPayloadLengthExtraBytes:
                payloadLengthExtraBytes = shared.networkDefaultPayloadLengthExtraBytes
            if addressVersionNumber == 3:  # currently the only one supported.
                if command == 'createRandomAddress':
                    shared.UISignalQueue.put((
                        'updateStatusBar', tr.translateText("MainWindow", "Generating one new address")))
                    # This next section is a little bit strange. We're going to generate keys over and over until we
                    # find one that starts with either \x00 or \x00\x00. Then when we pack them into a Bitmessage address,
                    # we won't store the \x00 or \x00\x00 bytes thus making the
                    # address shorter.
                    startTime = time.time()
                    numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0
                    potentialPrivSigningKey = OpenSSL.rand(32)
                    potentialPubSigningKey = pointMult(potentialPrivSigningKey)
                    while True:
                        numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1
                        potentialPrivEncryptionKey = OpenSSL.rand(32)
                        potentialPubEncryptionKey = pointMult(
                            potentialPrivEncryptionKey)
                        # print 'potentialPubSigningKey', potentialPubSigningKey.encode('hex')
                        # print 'potentialPubEncryptionKey',
                        # potentialPubEncryptionKey.encode('hex')
                        ripe = hashlib.new('ripemd160')
                        sha = hashlib.new('sha512')
                        sha.update(
                            potentialPubSigningKey + potentialPubEncryptionKey)
                        ripe.update(sha.digest())
                        # print 'potential ripe.digest',
                        # ripe.digest().encode('hex')
                        if eighteenByteRipe:
                            if ripe.digest()[:2] == '\x00\x00':
                                break
                        else:
                            if ripe.digest()[:1] == '\x00':
                                break
                    print 'Generated address with ripe digest:', ripe.digest().encode('hex')
                    print 'Address generator calculated', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, 'addresses at', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix / (time.time() - startTime), 'addresses per second before finding one with the correct ripe-prefix.'
                    address = encodeAddress(3, streamNumber, ripe.digest())

                    # An excellent way for us to store our keys is in Wallet Import Format. Let us convert now.
                    # https://en.bitcoin.it/wiki/Wallet_import_format
                    privSigningKey = '\x80' + potentialPrivSigningKey
                    checksum = hashlib.sha256(hashlib.sha256(
                        privSigningKey).digest()).digest()[0:4]
                    privSigningKeyWIF = arithmetic.changebase(
                        privSigningKey + checksum, 256, 58)
                    # print 'privSigningKeyWIF',privSigningKeyWIF

                    privEncryptionKey = '\x80' + potentialPrivEncryptionKey
                    checksum = hashlib.sha256(hashlib.sha256(
                        privEncryptionKey).digest()).digest()[0:4]
                    privEncryptionKeyWIF = arithmetic.changebase(
                        privEncryptionKey + checksum, 256, 58)
                    # print 'privEncryptionKeyWIF',privEncryptionKeyWIF

                    shared.config.add_section(address)
                    shared.config.set(address, 'label', label)
                    shared.config.set(address, 'enabled', 'true')
                    shared.config.set(address, 'decoy', 'false')
                    shared.config.set(address, 'noncetrialsperbyte', str(
                        nonceTrialsPerByte))
                    shared.config.set(address, 'payloadlengthextrabytes', str(
                        payloadLengthExtraBytes))
                    shared.config.set(
                        address, 'privSigningKey', privSigningKeyWIF)
                    shared.config.set(
                        address, 'privEncryptionKey', privEncryptionKeyWIF)
                    with open(shared.appdata + 'keys.dat', 'wb') as configfile:
                        shared.config.write(configfile)

                    # The API and the join and create Chan functionality
                    # both need information back from the address generator.
                    shared.apiAddressGeneratorReturnQueue.put(address)

                    shared.UISignalQueue.put((
                        'updateStatusBar', tr.translateText("MainWindow", "Done generating address. Doing work necessary to broadcast it...")))
                    shared.UISignalQueue.put(('writeNewAddressToTable', (
                        label, address, streamNumber)))
                    shared.reloadMyAddressHashes()
                    shared.workerQueue.put((
                        'sendOutOrStoreMyV3Pubkey', ripe.digest()))

                elif command == 'createDeterministicAddresses' or command == 'getDeterministicAddress' or command == 'createChan' or command == 'joinChan':
                    if len(deterministicPassphrase) == 0:
                        sys.stderr.write(
                            'WARNING: You are creating deterministic address(es) using a blank passphrase. Bitmessage will do it but it is rather stupid.')
                    if command == 'createDeterministicAddresses':
                        statusbar = 'Generating ' + str(
                            numberOfAddressesToMake) + ' new addresses.'
                        shared.UISignalQueue.put((
                            'updateStatusBar', statusbar))
                    signingKeyNonce = 0
                    encryptionKeyNonce = 1
                    listOfNewAddressesToSendOutThroughTheAPI = [
                    ]  # We fill out this list no matter what although we only need it if we end up passing the info to the API.

                    for i in range(numberOfAddressesToMake):
                        # This next section is a little bit strange. We're going to generate keys over and over until we
                        # find one that has a RIPEMD hash that starts with either \x00 or \x00\x00. Then when we pack them
                        # into a Bitmessage address, we won't store the \x00 or
                        # \x00\x00 bytes thus making the address shorter.
                        startTime = time.time()
                        numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0
                        while True:
                            numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1
                            potentialPrivSigningKey = hashlib.sha512(
                                deterministicPassphrase + encodeVarint(signingKeyNonce)).digest()[:32]
                            potentialPrivEncryptionKey = hashlib.sha512(
                                deterministicPassphrase + encodeVarint(encryptionKeyNonce)).digest()[:32]
                            potentialPubSigningKey = pointMult(
                                potentialPrivSigningKey)
                            potentialPubEncryptionKey = pointMult(
                                potentialPrivEncryptionKey)
                            # print 'potentialPubSigningKey', potentialPubSigningKey.encode('hex')
                            # print 'potentialPubEncryptionKey',
                            # potentialPubEncryptionKey.encode('hex')
                            signingKeyNonce += 2
                            encryptionKeyNonce += 2
                            ripe = hashlib.new('ripemd160')
                            sha = hashlib.new('sha512')
                            sha.update(
                                potentialPubSigningKey + potentialPubEncryptionKey)
                            ripe.update(sha.digest())
                            # print 'potential ripe.digest',
                            # ripe.digest().encode('hex')
                            if eighteenByteRipe:
                                if ripe.digest()[:2] == '\x00\x00':
                                    break
                            else:
                                if ripe.digest()[:1] == '\x00':
                                    break

                        print 'ripe.digest', ripe.digest().encode('hex')
                        print 'Address generator calculated', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, 'addresses at', numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix / (time.time() - startTime), 'keys per second.'
                        address = encodeAddress(3, streamNumber, ripe.digest())

                        saveAddressToDisk = True
                        # If we are joining an existing chan, let us check to make sure it matches the provided Bitmessage address
                        if command == 'joinChan':
                            if address != chanAddress:
                                shared.apiAddressGeneratorReturnQueue.put('chan name does not match address')
                                saveAddressToDisk = False
                        if command == 'getDeterministicAddress':
                            saveAddressToDisk = False

                        if saveAddressToDisk:
                            # An excellent way for us to store our keys is in Wallet Import Format. Let us convert now.
                            # https://en.bitcoin.it/wiki/Wallet_import_format
                            privSigningKey = '\x80' + potentialPrivSigningKey
                            checksum = hashlib.sha256(hashlib.sha256(
                                privSigningKey).digest()).digest()[0:4]
                            privSigningKeyWIF = arithmetic.changebase(
                                privSigningKey + checksum, 256, 58)

                            privEncryptionKey = '\x80' + \
                                potentialPrivEncryptionKey
                            checksum = hashlib.sha256(hashlib.sha256(
                                privEncryptionKey).digest()).digest()[0:4]
                            privEncryptionKeyWIF = arithmetic.changebase(
                                privEncryptionKey + checksum, 256, 58)

                            addressAlreadyExists = False
                            try:
                                shared.config.add_section(address)
                            except:
                                print address, 'already exists. Not adding it again.'
                                addressAlreadyExists = True
                            if not addressAlreadyExists:
                                print 'label:', label
                                shared.config.set(address, 'label', label)
                                shared.config.set(address, 'enabled', 'true')
                                shared.config.set(address, 'decoy', 'false')
                                if command == 'joinChan' or command == 'createChan':
                                    shared.config.set(address, 'chan', 'true')
                                shared.config.set(address, 'noncetrialsperbyte', str(
                                    nonceTrialsPerByte))
                                shared.config.set(address, 'payloadlengthextrabytes', str(
                                    payloadLengthExtraBytes))
                                shared.config.set(
                                    address, 'privSigningKey', privSigningKeyWIF)
                                shared.config.set(
                                    address, 'privEncryptionKey', privEncryptionKeyWIF)
                                with open(shared.appdata + 'keys.dat', 'wb') as configfile:
                                    shared.config.write(configfile)

                                shared.UISignalQueue.put(('writeNewAddressToTable', (
                                    label, address, str(streamNumber))))
                                listOfNewAddressesToSendOutThroughTheAPI.append(
                                    address)
                                shared.myECCryptorObjects[ripe.digest()] = highlevelcrypto.makeCryptor(
                                    potentialPrivEncryptionKey.encode('hex'))
                                shared.myAddressesByHash[
                                    ripe.digest()] = address
                                shared.workerQueue.put((
                                    'sendOutOrStoreMyV3Pubkey', ripe.digest())) # If this is a chan address,
                                        # the worker thread won't send out the pubkey over the network.


                    # Done generating addresses.
                    if command == 'createDeterministicAddresses' or command == 'joinChan' or command == 'createChan':
                        shared.apiAddressGeneratorReturnQueue.put(
                            listOfNewAddressesToSendOutThroughTheAPI)
                        shared.UISignalQueue.put((
                            'updateStatusBar', tr.translateText("MainWindow", "Done generating address")))
                        # shared.reloadMyAddressHashes()
                    elif command == 'getDeterministicAddress':
                        shared.apiAddressGeneratorReturnQueue.put(address)
                    #todo: return things to the API if createChan or joinChan assuming saveAddressToDisk
                else:
                    raise Exception(
                        "Error in the addressGenerator thread. Thread was given a command it could not understand: " + command)


# Does an EC point multiplication; turns a private key into a public key.


def pointMult(secret):
    # ctx = OpenSSL.BN_CTX_new() #This value proved to cause Seg Faults on
    # Linux. It turns out that it really didn't speed up EC_POINT_mul anyway.
    k = OpenSSL.EC_KEY_new_by_curve_name(OpenSSL.get_curve('secp256k1'))
    priv_key = OpenSSL.BN_bin2bn(secret, 32, 0)
    group = OpenSSL.EC_KEY_get0_group(k)
    pub_key = OpenSSL.EC_POINT_new(group)

    OpenSSL.EC_POINT_mul(group, pub_key, priv_key, None, None, None)
    OpenSSL.EC_KEY_set_private_key(k, priv_key)
    OpenSSL.EC_KEY_set_public_key(k, pub_key)
    # print 'priv_key',priv_key
    # print 'pub_key',pub_key

    size = OpenSSL.i2o_ECPublicKey(k, 0)
    mb = ctypes.create_string_buffer(size)
    OpenSSL.i2o_ECPublicKey(k, ctypes.byref(ctypes.pointer(mb)))
    # print 'mb.raw', mb.raw.encode('hex'), 'length:', len(mb.raw)
    # print 'mb.raw', mb.raw, 'length:', len(mb.raw)

    OpenSSL.EC_POINT_free(pub_key)
    # OpenSSL.BN_CTX_free(ctx)
    OpenSSL.BN_free(priv_key)
    OpenSSL.EC_KEY_free(k)
    return mb.raw
    
    
