/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

var walletPubKeyRegex = /^(bc1|[13]|D)[a-zA-HJ-NP-Z0-9]{25,}$/;
var paymentRequestRegex = /^(bitcoin|dogecoin):(bc1|[13]|D)[a-zA-HJ-NP-Z0-9]{25,}$/;
var walletPrivateKeyRegex = /^[0-9A-Za-z]+$/;

function scanWalletQrCode(callback) {
    scanBarcode(function (result) {
        if (walletPubKeyRegex.test(result)) {
            callback(result);
        } else {
            app.dialog.alert("That doesn't look like a valid wallet address.", "Error");
            return;
        }
    }, function () {
        app.dialog.alert("Something went wrong and we can't scan right now.", "Error");
    });
}

function scanPrivateKeyQrCode(callback) {
    scanBarcode(function (result) {
        if (walletPrivateKeyRegex.test(result)) {
            callback(result);
        } else {
            app.dialog.alert("That doesn't look like a valid wallet address.", "Error");
            return;
        }
    }, function () {
        app.dialog.alert("Something went wrong and we can't scan right now.", "Error");
    });
}

/**
 * Create and sign a crypto transaction.
 *
 * @param {type} bitcoreLib Bitcore, Litecore, Dogecore, etc.
 * @param {type} privateKeyString Private key from wallet QR code
 * @param {type} sourceAddress Sender's wallet address
 * @param {type} destinationAddress Recipient's wallet address
 * @param {Array} utxos Unspent transaction inputs, as array. See createUtxo()
 * @param {type} outputSatoshis Amount to send to recipient's wallet
 * @returns {string} Hex of serialized transaction, suitable for broadcast via Bitcoin Core or an API.
 */
function createSignedTransaction(bitcoreLib, privateKeyString, sourceAddress, destinationAddress, utxos, outputSatoshis, feePerByte) {
    if (typeof feePerByte == "undefined") {
        feePerByte = -1;
    }
    try {
        var privateKey = new bitcoreLib.PrivateKey(privateKeyString);

        var transaction = new bitcoreLib.Transaction()
                .from(utxos)
                .to(destinationAddress, outputSatoshis)
                .change(sourceAddress);

        var size = transaction._estimateSize();
        var fee = size * feePerByte;

        if (feePerByte > -1) {
            // use our fee
            transaction = transaction.fee(fee);
        } else {
            // use lib's fee
            fee = transaction.getFee();
        }

        transaction = transaction.sign(privateKey);

        var inputTotal = transaction._getInputAmount();

        var outputTotal = fee + outputSatoshis;
    } catch (ex) {
        throw new Error("There was an internal error while creating the transaction. Details: " + ex.message);
    }
    console.log(inputTotal, outputTotal);
    if (outputTotal > inputTotal) {
        throw new Error("You have insufficient funds to cover the payment and transaction fees.");
    }

    try {
        return {serialized: transaction.serialize(), fee: fee, sendamount: outputSatoshis, totalspent: outputTotal};
    } catch (ex) {
        throw new Error("Couldn't create the transaction. It's likely you typed something wrong. Check that you have enough funds.");
    }
}

/**
 * Create a UTXO.
 *
 * @param {type} sourceAddress Sender's wallet address
 * @param {type} txHash From UTXO (unspent output)
 * @param {type} txOutputIndex From UTXO (unspent output)
 * @param {type} script From UTXO (unspent output)
 * @param {type} inputSatoshis From UTXO (unspent output)
 * @returns {createUtxo.utxo}
 */
function createUtxo(sourceAddress, txHash, txOutputIndex, script, inputSatoshis) {
    var utxo = {
        "txId": txHash,
        "outputIndex": txOutputIndex,
        "address": sourceAddress,
        "script": script,
        "satoshis": inputSatoshis
    };
    return utxo;
}

/**
 * Get unspent outputs for a wallet address.
 * @param {string} walletaddress
 * @param {function} successCallback Passes object with {utxos: [{txHash,txOutputIndex,script,value}], currency: "DOGE", label: "Dogecoin"}
 * @param {function} errorCallback Passes string error message suitable for display
 * @returns {undefined}
 */
function getUTXOData(walletaddress, successCallback, errorCallback) {
    apirequest(SETTINGS.apis.getutxo, {
        walletaddress: walletaddress
    }, function (resp) {
        if (resp.status == "OK") {
            successCallback({
                utxos: resp.unspent_outputs,
                currency: resp.currency,
                label: resp.label
            });
        } else {
            errorCallback(resp.msg);
        }
    }, function (errorData) {
        try {
            var error = $.parseJSON(errorData.responseText);
            if (error && typeof error.msg != 'undefined') {
                errorCallback(resp.msg);
                sendErrorReport("Crypto", "Couldn't get UTXO data", error.msg);
            } else {
                errorCallback("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.");
                sendErrorReport("Crypto", "Couldn't get UTXO data", "Server/network problem: " + errorData.status + ": " + errorData.statusText);
            }
        } catch (ex) {
            errorCallback("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.");
            sendErrorReport("Crypto", "Couldn't get UTXO data", "Server/network problem: " + errorData.status + ": " + errorData.statusText);
        }
    });
}

function sendCoins(privatekey, fromaddress, toaddress, amount) {
    var progressdialog = app.dialog.progress("Querying blockchain...", 20);
    getUTXOData(fromaddress, function (success) {
        progressdialog.setProgress(40);
        progressdialog.setText("Creating transaction...");
        if (success.utxos.length == 0) {
            app.dialog.close();
            app.dialog.alert("Your wallet has no available funds (ZERO_LENGTH_UTXO).", "Error");
            return;
        }
        var utxos = [];
        for (var i = 0; i < success.utxos.length; i++) {
            utxos.push(createUtxo(fromaddress, success.utxos[i].txHash, success.utxos[i].txOutputIndex, success.utxos[i].script, success.utxos[i].value));
        }
        var bitcore = null;
        var satoshis = parseInt((amount * 100000000).toFixed(0)); // Make sure it's an int and not something like 10.0000000001 or 9.532999999999
        switch (success.currency) {
            case "DOGE":
                bitcore = require("bitcore-lib-doge");
                break;
            case "BTC":
                bitcore = require("bitcore-lib");
                break;
            default:
                app.dialog.close();
                app.dialog.alert("This app version doesn't support " + success.currency + ".", "Error");
                return;
        }

        progressdialog.setProgress(60);
        progressdialog.setText("Calculating fees...");
        apirequest(SETTINGS.apis.cryptofees, {
            currency: success.currency
        }, function (resp) {
            if (resp.status == "OK") {
                try {
                    var txdata = createSignedTransaction(bitcore, privatekey, fromaddress, toaddress, utxos, satoshis, resp.feePerByte);
                } catch (ex) {
                    console.error(ex);
                    app.dialog.close();
                    app.dialog.alert(ex.message, "Error");
                    return;
                }

                progressdialog.close();

                app.dialog.confirm("Sending " + (txdata.sendamount / 100000000) + " " + success.currency
                        + " with a fee of " + (txdata.fee / 100000000) + " " + success.currency
                        + " for a total spend of " + (txdata.totalspent / 100000000) + " " + success.currency + ".",
                        "Confirm Transaction",
                        function (ok) {
                            progressdialog = app.dialog.progress("Sending payment...", 80);

                            apirequest(SETTINGS.apis.broadcasttransaction, {
                                transactiondata: txdata.serialized,
                                currency: success.currency
                            }, function (resp) {
                                if (resp.status == "OK") {
                                    app.dialog.close();
                                    app.dialog.alert("Sent " + amount + " " + success.currency + " to " + toaddress.substring(0, 5) + "..." + toaddress.substring(toaddress.length - 5, 999), "Success!");
                                    $('#walletPrivateKey').val(""); // clear private key input box
                                    app.popup.close();
                                    return;
                                } else {
                                    app.dialog.close();
                                    app.dialog.alert(resp.msg, "Error");
                                }
                            }, function (errorData) {
                                app.dialog.close();
                                try {
                                    var error = $.parseJSON(errorData.responseText);
                                    if (error && typeof error.msg != 'undefined') {
                                        app.dialog.alert(error.msg, "Error");
                                        sendErrorReport("Crypto", "Couldn't broadcast transaction", error.msg);
                                    } else {
                                        app.dialog.alert("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.", "Error");
                                        sendErrorReport("Crypto", "Couldn't broadcast transaction", "Server/network problem: " + errorData.status + ": " + errorData.statusText);
                                    }
                                } catch (ex) {
                                    app.dialog.alert("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.", "Error");
                                    sendErrorReport("Crypto", "Couldn't broadcast transaction", "Server/network problem: " + errorData.status + ": " + errorData.statusText);
                                }
                            });
                        });
            } else {
                app.dialog.close();
                app.dialog.alert(resp.msg, "Error");
            }
        }, function (errorData) {
            app.dialog.close();
            try {
                var error = $.parseJSON(errorData.responseText);
                if (error && typeof error.msg != 'undefined') {
                    app.dialog.alert(error.msg, "Error");
                    sendErrorReport("Crypto", "Couldn't get transaction fees", error.msg);
                } else {
                    app.dialog.alert("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.", "Error");
                    sendErrorReport("Crypto", "Couldn't get transaction fees", "Server/network problem: " + errorData.status + ": " + errorData.statusText);
                }
            } catch (ex) {
                app.dialog.alert("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.", "Error");
                sendErrorReport("Crypto", "Couldn't get transaction fees", "Server/network problem: " + errorData.status + ": " + errorData.statusText);
            }
        });
    }, function (error) {
        app.dialog.close();
        app.dialog.alert(error, "Error");
    });
}

function walletGUISendCoins() {
    if (!walletPubKeyRegex.test($('#walletAddress').text())) {
        app.dialog.alert("Your wallet address doesn't look right. Check it and try again.", "Error");
        return;
    }

    if (isNaN($('#transactionAmount').val()) || $('#transactionAmount').val() < 0.00000001) {
        app.dialog.alert("The amount to send doesn't look right. Check it and try again.", "Error");
        return;
    }

    // Remove payment request URL stuff
    if ($('#walletToAddress').val().startsWith("bitcoin:")) {
        $('#walletToAddress').val($('#walletToAddress').val().replace("bitcoin:", ""));
    }
    if ($('#walletToAddress').val().startsWith("dogecoin:")) {
        $('#walletToAddress').val($('#walletToAddress').val().replace("dogecoin:", ""));
    }

    if (!walletPubKeyRegex.test($('#walletToAddress').val())) {
        app.dialog.alert("The recipient's wallet address doesn't look right. Check it and try again.", "Error");
        return;
    }
    sendCoins($('#walletPrivateKey').val(), $('#walletAddress').text(), $('#walletToAddress').val(), parseFloat($('#transactionAmount').val()));
}

function openWalletBalancePage( {to, resolve, reject}) {
    var address = to.params.walletaddress;
    if (!walletPubKeyRegex.test(address)) {
        app.dialog.alert("That doesn't look like a valid wallet address.", "Error");
        reject();
        return;
    }

    app.dialog.preloader("Loading...");
    apirequest(SETTINGS.apis.walletbalance, {
        walletaddress: address
    }, function (resp) {
        app.dialog.close();
        if (resp.status == "OK") {
            var context = {
                balance: resp.balance,
                currencyunit: resp.currency,
                fiatvalue: resp.usdvalue,
                currencyname: resp.label,
                attribution: resp.attribution,
                logo: "./assets/images/crypto/" + resp.currency + ".svg",
                walletaddress: to.params.walletaddress
            };
            resolve({
                content: compiledPages.crypto_wallet(context)
            });
            $("#walletBalanceAmount").text(resp.balance + " " + resp.currency);
            $("#walletFiatAmount").text(resp.usdvalue);
            $("#walletCurrency").text(resp.label);
            $("#walletBalanceAttribution").text(resp.attribution);
            $("#walletBalanceLogo").attr("src", "./assets/images/crypto/" + resp.currency + ".svg");
        } else {
            reject();
            app.dialog.alert(resp.msg, "Error");
        }
    }, function (error) {
        reject();
        app.dialog.close();
        try {
            var error = $.parseJSON(error.responseText);
            if (error && typeof error.msg != 'undefined') {
                app.dialog.alert(error.msg, "Error");
                sendErrorReport("Crypto", "Couldn't get wallet balance", error.msg);
            } else {
                app.dialog.alert("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.", "Error");
                sendErrorReport("Crypto", "Couldn't get wallet balance", "Server/network problem: " + error.status + ": " + error.statusText);
            }
        } catch (ex) {
            app.dialog.alert("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.", "Error");
            sendErrorReport("Crypto", "Couldn't get wallet balance", "Server/network problem: " + error.status + ": " + error.statusText);
        }
    });
}

/**
 * Setup an input for specifying amount to send in USD, with conversion to crypto.
 * @param {string} walletAddress Detects cryptocurrency from wallet address
 * @returns {undefined}
 */
function setupFiatConversion(walletAddress) {
    apirequest(SETTINGS.apis.walletbalance, {
        walletaddress: walletAddress
    }, function (resp) {
        if (resp.status != "OK") {
            return;
        }
        if (resp.exchangerates.usd == -1) {
            return;
        }
        $("#cryptoFiatInputItem").css("display", "");
        $("#cryptoAmountSendCurrencyLabel").text(resp.currency);
        $("#cryptoAmountSendFiatLabel").text("$");
        $("#transactionAmountFiat").data("exchange-rate", resp.exchangerates.usd);
        $("#transactionAmountFiat").data("cryptocurrency", resp.currency);

        $("#transactionAmount").off("input change paste keyup");
        $("#transactionAmountFiat").off("input change paste keyup");

        $("#transactionAmount").on("input change paste keyup", function () {
            if ($("#cryptoFiatInputItem").css("display") == "none") {
                return;
            }
            var amount = parseFloat($("#transactionAmount").val());
            var exchangerate = parseFloat($("#transactionAmountFiat").data("exchange-rate"));
            $("#transactionAmountFiat").val((amount * exchangerate).toFixed(2));
            //$("#transactionAmount").val(amount.toFixed(8));
        });
        $("#transactionAmountFiat").on("input change paste keyup", function () {
            if ($("#cryptoFiatInputItem").css("display") == "none") {
                return;
            }
            var fiatamount = parseFloat($("#transactionAmountFiat").val());
            var exchangerate = parseFloat($("#transactionAmountFiat").data("exchange-rate"));
            $("#transactionAmount").val((fiatamount / exchangerate).toFixed(8));
        });
    });
}

/**
 * Hides the fiat conversion input box.
 * @returns {undefined}
 */
function unsetupFiatConversion() {
    $("#cryptoFiatInputItem").css("display", "none");
    $("#cryptoAmountSendCurrencyLabel").text("");
    $("#transactionAmountFiat").removeData("exchange-rate");
    $("#transactionAmountFiat").removeData("cryptocurrency");
    $("#transactionAmount").off("input change paste keyup");
    $("#transactionAmountFiat").off("input change paste keyup");
}

$("#app").on("click", "#sendCryptoOpenPopupBtn", function () {
    if (walletPubKeyRegex.test($("#walletAddress").text())) {
        setupFiatConversion($("#walletAddress").text());
    } else {
        unsetupFiatConversion();
    }
});