//  web3swift
//
//  Created by Alex Vlasov.
//  Copyright © 2018 Alex Vlasov. All rights reserved.
//

import Foundation
import BigInt

public enum TransactionSignerError: Error {
    case signatureError(String)
}

public struct Web3Signer {
    public static func signTX(transaction:inout EthereumTransaction,
                              keystore: AbstractKeystore,
                              account: EthereumAddress,
                              password: String,
                              useExtraEntropy: Bool = false) throws {
        var privateKey = try keystore.UNSAFE_getPrivateKeyData(password: password, account: account)
        defer {Data.zero(&privateKey)}
        try EIP155Signer.sign(transaction: &transaction, privateKey: privateKey, useExtraEntropy: useExtraEntropy)
    }

    public static func signPersonalMessage<T:AbstractKeystore>(_ personalMessage: Data,
                                                               keystore: T,
                                                               account: EthereumAddress,
                                                               password: String,
                                                               useExtraEntropy: Bool = false) throws -> Data? {
        var privateKey = try keystore.UNSAFE_getPrivateKeyData(password: password, account: account)
        defer {Data.zero(&privateKey)}
        guard let hash = Web3.Utils.hashPersonalMessage(personalMessage) else {return nil}
        let (compressedSignature, _) = SECP256K1.signForRecovery(hash: hash, privateKey: privateKey, useExtraEntropy: useExtraEntropy)
        return compressedSignature
    }
    
    public struct EIP155Signer {
        public static func sign(transaction:inout EthereumTransaction,
                                privateKey: Data,
                                useExtraEntropy: Bool = false) throws {
            for _ in 0..<1024 {
                print("--------------------------")
                let result = try self.attemptSignature(transaction: &transaction,
                                                       privateKey: privateKey,
                                                       useExtraEntropy: useExtraEntropy)
                if (result) {
                    print("--------------------------")
                    return
                }
            }
            throw AbstractKeystoreError.invalidAccountError
        }
        
        private static func attemptSignature(transaction:inout EthereumTransaction,
                                             privateKey: Data,
                                             useExtraEntropy: Bool = false) throws -> Bool {
            guard let hash = transaction.hashForSignature else {return false}
            print("Hash to Sign         : " + hash.toHexString())
            let signature  = SECP256K1.signForRecovery(hash: hash, privateKey: privateKey, useExtraEntropy: useExtraEntropy)
            // print("Signature: " + signature.rawSignature!.toHexString())
            guard let serializedSignature = signature.serializedSignature else {return false}
            // print("Serialized Signature : " + serializedSignature.toHexString())
            guard let unmarshalledSignature = SECP256K1.unmarshalSignature(signatureData: serializedSignature) else {
                return false
            }
            let originalPublicKey = SECP256K1.privateToPublic(privateKey: privateKey)
            // print("Original Public Key  : " + originalPublicKey!.toHexString())
            // print("Unmarshalled V       : " + unmarshalledSignature.v.description)
            print("Transaction Type     : " + (transaction.transactionType == .LEGACY ? "Legacy" : "EIP-1559"))
            if (transaction.transactionType == .LEGACY) {
                transaction.v = try unmarshalledSignature.getLegacyV(chainId: transaction.chainID)
            }
            else {
                transaction.v = try unmarshalledSignature.getParity(chainId: transaction.chainID)
            }
            // print("Normalized V         : " + transaction.v.description)
            
            transaction.r = BigUInt(Data(unmarshalledSignature.r))
            transaction.s = BigUInt(Data(unmarshalledSignature.s))
            let recoveredPublicKey = transaction.recoverPublicKey()
            // print("Recovered Public Key : " + recoveredPublicKey!.toHexString())
            if (!(originalPublicKey!.constantTimeComparisonTo(recoveredPublicKey))) {
                return false
            }
            print("Success!")
            return true
        }
    }
}




