// Copyright (c) 2014-2017, MyMonero.com
// 
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice, this list of
//    conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
//    of conditions and the following disclaimer in the documentation and/or other
//    materials provided with the distribution.
// 
// 3. Neither the name of the copyright holder nor the names of its contributors may be
//    used to endorse or promote products derived from this software without specific
//    prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

thinwalletCtrls.controller('SendCoinsCtrl', function($scope, $http, $q,
                                                     AccountService, ModalService,
                                                     ApiCalls)
{
    "use strict";
    $scope.status = "";
    $scope.error = "";
    $scope.submitting = false;
    $scope.targets = [{}];
    $scope.totalAmount = JSBigInt.ZERO;
    $scope.mixins = config.defaultMixin.toString();
    $scope.view_only = AccountService.isViewOnly();

    $scope.success_page = false;
    $scope.sent_tx = {};


    var view_only = AccountService.isViewOnly();

    var explorerUrl = "";

    if (config.nettype == 0)
        explorerUrl = config.mainnetExplorerUrl;
    else if (config.nettype == 1)
        explorerUrl = config.testnetExplorerUrl;
    else
        explorerUrl = config.stagenetExplorerUrl;

    // few multiplayers based on uint64_t wallet2::get_fee_multiplier
    var fee_multiplayers = [1, 4, 20, 166];

    var default_priority = 2;

    $scope.priority = default_priority.toString();

    $scope.openaliasDialog = undefined;

    function isInt(value) {
        return !isNaN(value) &&
            parseInt(Number(value)) == value &&
            !isNaN(parseInt(value, 10));
    }


    function confirmOpenAliasAddress(domain, address, name, description, dnssec_used) {
        var deferred = $q.defer();
        if ($scope.openaliasDialog !== undefined) {
            deferred.reject("OpenAlias confirm dialog is already being shown!");
            return;
        }
        $scope.openaliasDialog = {
            address: address,
            domain: domain,
            name: name,
            description: description,
            dnssec_used: dnssec_used,
            confirm: function() {
                $scope.openaliasDialog = undefined;
                console.log("User confirmed OpenAlias resolution for " + domain + " to " + address);
                deferred.resolve();
            },
            cancel: function() {
                $scope.openaliasDialog = undefined;
                console.log("User rejected OpenAlias resolution for " + domain + " to " + address);
                deferred.reject("OpenAlias resolution rejected by user");
            }
        };
        return deferred.promise;
    }

    $scope.transferConfirmDialog = undefined;

    function confirmTransfer(address, amount, tx_hash, fee, tx_prv_key,
                             payment_id, mixin, priority, txBlobKBytes, raw_tx,
                             no_inputs, no_outputs) {

        var deferred = $q.defer();

        if ($scope.transferConfirmDialog !== undefined) {
            deferred.reject("transferConfirmDialog is already being shown!");
            return;
        }

        var priority_names = ["Low", "Medium", "High", "Paranoid"];

        $scope.transferConfirmDialog = {
            address: address,
            fee: fee,
            tx_hash: tx_hash,
            amount: amount,
            tx_prv_key: tx_prv_key,
            payment_id: payment_id,
            mixin: mixin + 1,
            raw_tx: raw_tx,
            no_inputs: no_inputs,
            no_outputs: no_outputs,
            txBlobKBytes: Math.round(txBlobKBytes*1e3) / 1e3,
            priority: priority_names[priority - 1],
            confirm: function() {
                $scope.transferConfirmDialog = undefined;
                deferred.resolve();
            },
            cancel: function() {
                $scope.transferConfirmDialog = undefined;
                deferred.reject("Transfer canceled by user");
            }
        };
        return deferred.promise;
    }

    function getTxCharge(amount) {
        amount = new JSBigInt(amount);
        // amount * txChargeRatio
        return amount.divide(1 / config.txChargeRatio);
    }

    $scope.removeTarget = function(index) {
        $scope.targets.splice(index, 1);
    };

    $scope.$watch('targets', function() {
        var totalAmount = JSBigInt.ZERO;
        for (var i = 0; i < $scope.targets.length; ++i) {
            try {
                var amount = cnUtil.parseMoney($scope.targets[i].amount);
                totalAmount = totalAmount.add(amount);
            } catch (e) {
            }
        }
        $scope.totalAmount = totalAmount;
    }, true);

    $scope.resetSuccessPage = function() {
        $scope.success_page = false;
        $scope.sent_tx = {};
    };

    $scope.sendCoins = function(targets, mixin, payment_id) {
        if ($scope.submitting) return;
        $scope.status = "";
        $scope.error = "";
        $scope.submitting = true;
        mixin = parseInt(mixin);
        var rct = true; //maybe want to set this later based on inputs (?)
        var realDsts = [];
        var targetPromises = [];
        for (var i = 0; i < targets.length; ++i) {
            var target = targets[i];
            if (!target.address && !target.amount) {
                continue;
            }
            var deferred = $q.defer();
            targetPromises.push(deferred.promise);
            (function(deferred, target) {
                var amount;
                try {
                    amount = cnUtil.parseMoney(target.amount);
                } catch (e) {
                    deferred.reject("Failed to parse amount (#" + i + ")");
                    return;
                }
                if (target.address.indexOf('.') === -1) {
                    try {
                        // verify that the address is valid
                        cnUtil.decode_address(target.address);
                        deferred.resolve({
                            address: target.address,
                            amount: amount
                        });
                    } catch (e) {
                        deferred.reject("Failed to decode address (#" + i + "): " + e);
                        return;
                    }
                } else {
                    var domain = target.address.replace(/@/g, ".");
                    ApiCalls.get_txt_records(domain)
                        .then(function(response) {
                            var data = response.data;
                            var records = data.records;
                            var oaRecords = [];
                            console.log(domain + ": ", data.records);
                            if (data.dnssec_used) {
                                if (data.secured) {
                                    console.log("DNSSEC validation successful");
                                } else {
                                    deferred.reject("DNSSEC validation failed for " + domain + ": " + data.dnssec_fail_reason);
                                    return;
                                }
                            } else {
                                console.log("DNSSEC Not used");
                            }
                            for (var i = 0; i < records.length; i++) {
                                var record = records[i];
                                if (record.slice(0, 4 + config.openAliasPrefix.length + 1) !== "oa1:" + config.openAliasPrefix + " ") {
                                    continue;
                                }
                                console.log("Found OpenAlias record: " + record);
                                oaRecords.push(parseOpenAliasRecord(record));
                            }
                            if (oaRecords.length === 0) {
                                deferred.reject("No OpenAlias records found for: " + domain);
                                return;
                            }
                            if (oaRecords.length !== 1) {
                                deferred.reject("Multiple addresses found for given domain: " + domain);
                                return;
                            }
                            console.log("OpenAlias record: ", oaRecords[0]);
                            var oaAddress = oaRecords[0].address;
                            try {
                                cnUtil.decode_address(oaAddress);
                                confirmOpenAliasAddress(domain, oaAddress,
                                    oaRecords[0].name, oaRecords[0].description,
                                    data.dnssec_used && data.secured)
                                    .then(function() {
                                        deferred.resolve({
                                            address: oaAddress,
                                            amount: amount,
                                            domain: domain
                                        });
                                }, function(err) {
                                    deferred.reject(err);
                                });
                            } catch (e) {
                                deferred.reject("Failed to decode OpenAlias address: " + oaRecords[0].address + ": " + e);
                                return;
                            }
                        }, function(data) {
                            deferred.reject("Failed to resolve DNS records for '" + domain + "': " + "Unknown error");
                        });
                }
            })(deferred, target);
        }

        var strpad = function(org_str, padString, length)
        {   // from http://stackoverflow.com/a/10073737/248823
            var str = org_str;
            while (str.length < length)
                str = padString + str;
            return str;
        };

        // Transaction will need at least 1KB fee (13KB for RingCT)

        var feePerKB = new JSBigInt(config.feePerKB);

        var priority = $scope.priority || default_priority;

        if (!isInt(priority))
        {
            $scope.submitting = false;
            $scope.error = "Priority is not an integer number";
            return;
        }

        if (!(priority >= 1 && priority <= 4))
        {
            $scope.submitting = false;
            $scope.error = "Priority is not between 1 and 4";
            return;
        }

        var fee_multiplayer = fee_multiplayers[priority - 1]; // default is 4

        var neededFee = rct ? feePerKB.multiply(13) : feePerKB;
        var totalAmountWithoutFee;
        var unspentOuts;
        var pid_encrypt = false; //don't encrypt payment ID unless we find an integrated one

        $q.all(targetPromises).then(function(destinations) {
            totalAmountWithoutFee = new JSBigInt(0);
            for (var i = 0; i < destinations.length; i++) {
                totalAmountWithoutFee = totalAmountWithoutFee.add(destinations[i].amount);
            }
            realDsts = destinations;
            console.log("Parsed destinations: " + JSON.stringify(realDsts));
            console.log("Total before fee: " + cnUtil.formatMoney(totalAmountWithoutFee));
            if (realDsts.length === 0) {
                $scope.submitting = false;
                $scope.error = "You need to enter a valid destination";
                return;
            }
            if (payment_id)
            {
                if (payment_id.length <= 64 && /^[0-9a-fA-F]+$/.test(payment_id))
                {
                    // if payment id is shorter, but has correct number, just
                    // pad it to required length with zeros
                    payment_id = strpad(payment_id, "0", 64);
                }

                // now double check if ok, when we padded it
                if (payment_id.length !== 64 || !(/^[0-9a-fA-F]{64}$/.test(payment_id)))
                {
                    $scope.submitting = false;
                    $scope.error = "The payment ID you've entered is not valid";
                    return;
                }

            }
            if (realDsts.length === 1) {//multiple destinations aren't supported by MyMonero, but don't include integrated ID anyway (possibly should error in the future)
                var decode_result = cnUtil.decode_address(realDsts[0].address);
                if (decode_result.intPaymentId && payment_id) {
                    $scope.submitting = false;
                    $scope.error = "Payment ID field must be blank when using an Integrated Address";
                    return;
                } else if (decode_result.intPaymentId) {
                    payment_id = decode_result.intPaymentId;
                    pid_encrypt = true; //encrypt if using an integrated address
                }
            }
            if (totalAmountWithoutFee.compare(0) <= 0) {
                $scope.submitting = false;
                $scope.error = "The amount you've entered is too low";
                return;
            }
            $scope.status = "Generating transaction...";
            console.log("Destinations: ");
            // Log destinations to console
            for (var j = 0; j < realDsts.length; j++) {
                console.log(realDsts[j].address + ": " + cnUtil.formatMoneyFull(realDsts[j].amount));
            }
            var getUnspentOutsRequest = {
                address: AccountService.getAddress(),
                view_key: AccountService.getViewKey(),
                amount: '0',
                mixin: mixin,
                // Use dust outputs only when we are using no mixins
                use_dust: mixin === 0,
                dust_threshold: config.dustThreshold.toString()
            };

            ApiCalls.get_unspent_outs(getUnspentOutsRequest)
                .then(function(request) {
                    var data = request.data;
                    unspentOuts = checkUnspentOuts(data.outputs || []);
                    unused_outs = unspentOuts.slice(0);
                    using_outs = [];
                    using_outs_amount = new JSBigInt(0);
                    if (data.per_kb_fee)
                    {
                        feePerKB = new JSBigInt(data.per_kb_fee);
                        neededFee = feePerKB.multiply(13).multiply(fee_multiplayer);
                    }
                    transfer().then(transferSuccess, transferFailure);
                }, function(data) {
                    $scope.status = "";
                    $scope.submitting = false;
                    if (data && data.Error) {
                        $scope.error = data.Error;
                        console.warn(data.Error);
                    } else {
                        $scope.error = "Something went wrong with getting your available balance for spending";
                    }
                });
        }, function(err) {
            $scope.submitting = false;
            $scope.error = err;
            console.log("Error decoding targets: " + err);
        });

        function checkUnspentOuts(outputs) {
            for (var i = 0; i < outputs.length; i++) {
                for (var j = 0; outputs[i] && j < outputs[i].spend_key_images.length; j++) {
                    var key_img = AccountService.cachedKeyImage(outputs[i].tx_pub_key, outputs[i].index);
                    if (key_img === outputs[i].spend_key_images[j]) {
                        console.log("Output was spent with key image: " + key_img + " amount: " + cnUtil.formatMoneyFull(outputs[i].amount));
                        // Remove output from list
                        outputs.splice(i, 1);
                        if (outputs[i]) {
                            j = outputs[i].spend_key_images.length;
                        }
                        i--;
                    } else {
                        console.log("Output used as mixin (" + key_img + "/" + outputs[i].spend_key_images[j] + ")");
                    }
                }
            }
            console.log("Unspent outs: " + JSON.stringify(outputs));
            return outputs;
        }

        function transferSuccess(tx_h) {
            var prevFee = neededFee;
            var raw_tx = tx_h.raw;
            var tx_hash = tx_h.hash;
            var tx_prvkey = tx_h.prvkey;
            var no_inputs = tx_h.no_inputs;
            var no_outputs = tx_h.no_outputs;
            // work out per-kb fee for transaction
            var txBlobBytes = raw_tx.length / 2;
            var txBlobKBytes = txBlobBytes / 1024.0;
            var numKB = Math.floor(txBlobKBytes);
            if (txBlobBytes % 1024) {
                numKB++;
            }
            console.log(txBlobBytes + " bytes <= " + numKB + " KB (current fee: " + cnUtil.formatMoneyFull(prevFee) + ")");
            neededFee = feePerKB.multiply(numKB).multiply(fee_multiplayer);
            // if we need a higher fee
            if (neededFee.compare(prevFee) > 0) {
                console.log("Previous fee: " + cnUtil.formatMoneyFull(prevFee) + " New fee: " + cnUtil.formatMoneyFull(neededFee));
                transfer().then(transferSuccess, transferFailure);
                return;
            }

            // generated with correct per-kb fee
            console.log("Successful tx generation, submitting tx");
            console.log("Tx hash: " + tx_hash);
            $scope.status = "Submitting...";
            var request = {
                address: AccountService.getAddress(),
                view_key: AccountService.getViewKey(),
                tx: raw_tx
            };


            confirmTransfer(realDsts[0].address, realDsts[0].amount,
                            tx_hash, neededFee, tx_prvkey, payment_id,
                            mixin, priority, txBlobKBytes, raw_tx,
                            no_inputs, no_outputs).then(function() {

                //alert('Confirmed ');

                ApiCalls.submit_raw_tx(request.address, request.view_key, request.tx)
                    .then(function(response) {

                        var data = response.data;

                        if (data.status === "error")
                        {
                            $scope.status = "";
                            $scope.submitting = false;
                            $scope.error = "Something unexpected occurred when submitting your transaction: " + data.error;
                            return;
                        }

                        //console.log("Successfully submitted tx");
                        $scope.targets = [{}];
                        $scope.sent_tx = {
                            address: realDsts[0].address,
                            domain: realDsts[0].domain,
                            amount: realDsts[0].amount,
                            payment_id: payment_id,
                            tx_id: tx_hash,
                            tx_prvkey: tx_prvkey,
                            tx_fee: neededFee/*.add(getTxCharge(neededFee))*/,
                            explorerLink: explorerUrl + "tx/" + tx_hash
                        };

                        $scope.success_page = true;
                        $scope.status = "";
                        $scope.submitting = false;
                    }, function(error) {
                        $scope.status = "";
                        $scope.submitting = false;
                        $scope.error = "Something unexpected occurred when submitting your transaction: ";
                    });

            }, function(reason) {
                //alert('Failed: ' + reason);
                transferFailure("Transfer canceled");
            });


        }

        function transferFailure(reason) {
            $scope.status = "";
            $scope.submitting = false;
            $scope.error = reason;
            console.log("Transfer failed: " + reason);
        }

        var unused_outs;
        var using_outs;
        var using_outs_amount;

        function random_index(list) {
            return Math.floor(Math.random() * list.length);
        }

        function pop_random_value(list) {
            var idx = random_index(list);
            var val = list[idx];
            list.splice(idx, 1);
            return val;
        }

        function select_outputs(target_amount) {
            console.log("Selecting outputs to use. Current total: " + cnUtil.formatMoney(using_outs_amount) + " target: " + cnUtil.formatMoney(target_amount));
            while (using_outs_amount.compare(target_amount) < 0 && unused_outs.length > 0) {
                var out = pop_random_value(unused_outs);
                if (!rct && out.rct) {continue;} //skip rct outs if not creating rct tx
                using_outs.push(out);
                using_outs_amount = using_outs_amount.add(out.amount);
                console.log("Using output: " + cnUtil.formatMoney(out.amount) + " - " + JSON.stringify(out));
            }
        }

        function transfer() {
            var deferred = $q.defer();
            (function() {
                var dsts = realDsts.slice(0);
                // Calculate service charge and add to tx destinations
                //var chargeAmount = getTxCharge(neededFee);
                //dsts.push({
                //    address: config.txChargeAddress,
                //    amount: chargeAmount
                //});
                // Add fee to total amount
                var totalAmount = totalAmountWithoutFee.add(neededFee)/*.add(chargeAmount)*/;
                console.log("Balance required: " + cnUtil.formatMoneySymbol(totalAmount));

                select_outputs(totalAmount);

                //compute fee as closely as possible before hand
                if (using_outs.length > 1 && rct)
                {
                    var newNeededFee = JSBigInt(Math.ceil(cnUtil.estimateRctSize(using_outs.length, mixin, 2) / 1024)).multiply(feePerKB).multiply(fee_multiplayer);
                    totalAmount = totalAmountWithoutFee.add(newNeededFee);
                    //add outputs 1 at a time till we either have them all or can meet the fee
                    while (using_outs_amount.compare(totalAmount) < 0 && unused_outs.length > 0)
                    {
                        var out = pop_random_value(unused_outs);
                        using_outs.push(out);
                        using_outs_amount = using_outs_amount.add(out.amount);
                        console.log("Using output: " + cnUtil.formatMoney(out.amount) + " - " + JSON.stringify(out));
                        newNeededFee = JSBigInt(Math.ceil(cnUtil.estimateRctSize(using_outs.length, mixin, 2) / 1024)).multiply(feePerKB).multiply(fee_multiplayer);
                        totalAmount = totalAmountWithoutFee.add(newNeededFee);
                    }
                    console.log("New fee: " + cnUtil.formatMoneySymbol(newNeededFee) + " for " + using_outs.length + " inputs");
                    neededFee = newNeededFee;
                }

                if (using_outs_amount.compare(totalAmount) < 0)
                {
                    deferred.reject("Not enough spendable outputs / balance too low (have "
                        + cnUtil.formatMoneyFull(using_outs_amount) + " but need "
                        + cnUtil.formatMoneyFull(totalAmount)
                        + " (estimated fee " + cnUtil.formatMoneyFull(neededFee) + " included)");
                    return;
                }
                else if (using_outs_amount.compare(totalAmount) > 0)
                {
                    var changeAmount = using_outs_amount.subtract(totalAmount);

                    if (!rct)
                    {   //for rct we don't presently care about dustiness
                        //do not give ourselves change < dust threshold
                        var changeAmountDivRem = changeAmount.divRem(config.dustThreshold);
                        if (changeAmountDivRem[1].toString() !== "0") {
                            // add dusty change to fee
                            console.log("Adding change of " + cnUtil.formatMoneyFullSymbol(changeAmountDivRem[1]) + " to transaction fee (below dust threshold)");
                        }
                        if (changeAmountDivRem[0].toString() !== "0") {
                            // send non-dusty change to our address
                            var usableChange = changeAmountDivRem[0].multiply(config.dustThreshold);
                            console.log("Sending change of " + cnUtil.formatMoneySymbol(usableChange) + " to " + AccountService.getAddress());
                            dsts.push({
                                address: AccountService.getAddress(),
                                amount: usableChange
                            });
                        }
                    }
                    else
                    {
                        //add entire change for rct
                        console.log("Sending change of " + cnUtil.formatMoneySymbol(changeAmount)
                            + " to " + AccountService.getAddress());
                        dsts.push({
                            address: AccountService.getAddress(),
                            amount: changeAmount
                        });
                    }
                }
                else if (using_outs_amount.compare(totalAmount) === 0 && rct)
                {
                    //create random destination to keep 2 outputs always in case of 0 change
                    var fakeAddress = cnUtil.create_address(cnUtil.random_scalar()).public_addr;
                    console.log("Sending 0 XMR to a fake address to keep tx uniform (no change exists): " + fakeAddress);
                    dsts.push({
                        address: fakeAddress,
                        amount: 0
                    });
                }

                if (mixin > 0)
                {
                    var amounts = [];
                    for (var l = 0; l < using_outs.length; l++)
                    {
                        amounts.push(using_outs[l].rct ? "0" : using_outs[l].amount.toString());
                        //amounts.push("0");
                    }
                    var request = {
                        amounts: amounts,
                        count: mixin + 1 // Add one to mixin so we can skip real output key if necessary
                    };

                    ApiCalls.get_random_outs(request.amounts, request.count)
                        .then(function(response) {
                            var data = response.data;

                            if (data.status === "error")
                            {
                                $scope.status = "";
                                $scope.submitting = false;
                                $scope.error = "Failed to create transaction: " + data.error;
                                return;
                            }

                            createTx(data.amount_outs);
                        }, function(data) {
                            deferred.reject('Failed to get unspent outs');
                        });
                } else if (mixin < 0 || isNaN(mixin)) {
                    deferred.reject("Invalid mixin");
                    return;
                } else { // mixin === 0
                    createTx();
                }

                // Create & serialize transaction
                function createTx(mix_outs)
                {
                    var signed;
                    try {
                        console.log('Destinations: ');
                        cnUtil.printDsts(dsts);
                        //need to get viewkey for encrypting here, because of splitting and sorting
                        if (pid_encrypt)
                        {
                            var realDestViewKey = cnUtil.decode_address(dsts[0].address).view;
                        }

                        var splittedDsts = cnUtil.decompose_tx_destinations(dsts, rct);

                        console.log('Decomposed destinations:');

                        cnUtil.printDsts(splittedDsts);

                        signed = cnUtil.create_transaction(
                            AccountService.getPublicKeys(),
                            AccountService.getSecretKeys(),
                            splittedDsts, using_outs,
                            mix_outs, mixin, neededFee,
                            payment_id, pid_encrypt,
                            realDestViewKey, 0, rct);

                    } catch (e) {
                        deferred.reject("Failed to create transaction: " + e);
                        return;
                    }
                    console.log("signed tx: ", JSON.stringify(signed));
                    //move some stuff here to normalize rct vs non
                    var raw_tx_and_hash = {};
                    if (signed.version === 1) {
                        raw_tx_and_hash.raw = cnUtil.serialize_tx(signed);
                        raw_tx_and_hash.hash = cnUtil.cn_fast_hash(raw_tx);
                        raw_tx_and_hash.prvkey = signed.prvkey;
                        raw_tx_and_hash.no_outputs = signed.vout.length;
                        raw_tx_and_hash.no_inputs = signed.vin.length;
                    } else {
                        raw_tx_and_hash = cnUtil.serialize_rct_tx_with_hash(signed);
                    }
                    console.log("raw_tx and hash:");
                    console.log(raw_tx_and_hash);
                    deferred.resolve(raw_tx_and_hash);
                }
            })();
            return deferred.promise;
        }
    };
});

function parseOpenAliasRecord(record) {
    var parsed = {};
    if (record.slice(0, 4 + config.openAliasPrefix.length + 1) !== "oa1:" + config.openAliasPrefix + " ") {
        throw "Invalid OpenAlias prefix";
    }
    function parse_param(name) {
        var pos = record.indexOf(name + "=");
        if (pos === -1) {
            // Record does not contain param
            return undefined;
        }
        pos += name.length + 1;
        var pos2 = record.indexOf(";", pos);
        return record.substr(pos, pos2 - pos);
    }

    parsed.address = parse_param('recipient_address');
    parsed.name = parse_param('recipient_name');
    parsed.description = parse_param('tx_description');
    return parsed;
}
