// 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.

class HostedMoneroAPIClient
{
    constructor(options)
    {
        const self = this
        self.$http = options.$http
        if (self.$http == null || typeof self.$http === 'undefined') {
            throw "self.$http required"
        }
    }
    UnspentOuts(parameters, fn)
    { // -> RequestHandle
        const self = this
        const endpointPath = 'get_unspent_outs'
        self.$http.post(config.apiUrl + endpointPath, parameters).then(
            function(data)
            {
                fn(null, data.data)
            }
        ).catch(
            function(data)
            {
                fn(data && data.data.Error ? data.data.Error : "Something went wrong with getting your available balance for spending");
            }
        );
        const requestHandle = 
        {
            abort: function()
            {
                console.warn("TODO: abort!")
            }
        }
        return requestHandle
    }
    RandomOuts(parameters, fn)
    { // -> RequestHandle
        const self = this
        const endpointPath = 'get_random_outs'
        self.$http.post(config.apiUrl + endpointPath, parameters).then(
            function(data)
            {
                fn(null, data.data)
            }
        ).catch(
            function(data)
            {
                fn(data && data.data.Error ? data.data.Error : "Something went wrong while getting decoy outputs");
            }
        );
        const requestHandle = 
        {
            abort: function()
            {
                console.warn("TODO: abort!")
            }
        }
        return requestHandle
    }
    SubmitRawTx(parameters, fn)
    {
        const self = this
        const endpointPath = 'submit_raw_tx'
        self.$http.post(config.apiUrl + endpointPath, parameters).then(
            function(data)
            {
                fn(null, data.data)
            }
        ).catch(
            function(data)
            {
                fn(data && data.data.Error ? data.data.Error : "Something went wrong while submitting your transaction");
            }
        );
        const requestHandle = 
        {
            abort: function()
            {
                console.warn("TODO: abort!")
            }
        }
        return requestHandle
    }
}

thinwalletCtrls.controller('SendCoinsCtrl', function($scope, $http, $q, AccountService) {
    "use strict";
    $scope.status = "";
    $scope.error = "";
    $scope.submitting = false;
    $scope.targets = [{}];
    $scope.totalAmount = JSBigInt.ZERO;

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

    $scope.openaliasDialog = undefined;

    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.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, payment_id) 
    {
        if ($scope.submitting) {
            console.warn("Already submitting")
            return;
        }
        $scope.status = "";
        $scope.error = "";
        $scope.submitting = true;
        //
        mymonero_core_js.monero_utils_promise.then(function(coreBridge_instance)
        {
            if (targets.length > 1) {
                throw "MyMonero currently only supports one target"
            }
            const target = targets[0]
            if (!target.address && !target.amount) {
                throw "Target address and amount required.";
            }
            function fn(err, mockedTransaction, optl_domain)
            { // this function handles all termination of sendCoins(), except for canceled_fn()
                if (err) {
                    console.error("Err:", err)
                    $scope.status = "";
                    $scope.error = "Something unexpected occurred when sending funds: " + (err.Error || err);
                    $scope.submitting = false;
                    return
                }
                console.log("Tx succeeded", mockedTransaction)
                $scope.targets = [{}];
                $scope.sent_tx = {
                    address: mockedTransaction.target_address,
                    domain: optl_domain,
                    amount: mockedTransaction.total_sent, // it's expecting a JSBigInt back so it can `| money`
                    payment_id: mockedTransaction.payment_id,
                    tx_id: mockedTransaction.hash,
                    tx_fee: mockedTransaction.tx_fee,
                    tx_key: mockedTransaction.tx_key
                };
                $scope.success_page = true;
                $scope.status = "";
                $scope.submitting = false;
            }
            function canceled_fn()
            {
                $scope.status = "Canceled";
                $scope.submitting = false;
            }
            var sweeping = targets[0].sendAll === true ? true : false; // default false, i.e. non-existence of UI element to set the flag
            var amount = sweeping ? 0 : target.amount;
            if (target.address.indexOf('.') !== -1) {
                var domain = target.address.replace(/@/g, ".");
                $http.post(config.apiUrl + "get_txt_records", {
                    domain: domain
                }).then(function(data) {
                    var data = data.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 {
                            fn("DNSSEC validation failed for " + domain + ": " + data.dnssec_fail_reason, null);
                            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) {
                        fn("No OpenAlias records found for: " + domain);
                        return;
                    }
                    if (oaRecords.length !== 1) {
                        fn("Multiple addresses found for given domain: " + domain);
                        return;
                    }
                    console.log("OpenAlias record: ", oaRecords[0]);
                    var oaAddress = oaRecords[0].address;
                    try {
                        coreBridge_instance.decode_address(oaAddress, config.nettype);
                        confirmOpenAliasAddress(
                            domain, 
                            oaAddress, 
                            oaRecords[0].name, 
                            oaRecords[0].description, 
                            data.dnssec_used && data.secured
                        ).then(
                            function()
                            {
                                sendTo(oaAddress, amount, domain);
                            }, 
                            function(err)
                            {
                                fn(err);
                                return;
                            }
                        );
                    } catch (e) {
                        fn("Failed to decode OpenAlias address: " + oaRecords[0].address + ": " + e);
                        return;
                    }
                }).catch(function(data) {
                    fn("Failed to resolve DNS records for '" + domain + "': " + ((data || {}).Error || data || "Unknown error"));
                    return
                });
                return
            }
            // xmr address (incl subaddresses):
            try {
                // verify that the address is valid
                coreBridge_instance.decode_address(target.address, config.nettype);
            } catch (e) {
                fn("Failed to decode address with error: " + e);
                return;
            }
            sendTo(target.address, amount, null /*domain*/);
            //
            function sendTo(target_address, amount, domain/*may be null*/)
            {
                const mixin = 10; // mandatory fixed mixin for v8 Monero fork
                let statusUpdate_messageBase = sweeping ? `Sending wallet balance…` : `Sending ${amount} XMR…`
                function _configureWith_statusUpdate(str, code)
                {
                    console.log("Step:", str)
                    $scope.status = str
                }
                //
                _configureWith_statusUpdate(statusUpdate_messageBase);
                //
                const sec_keys = AccountService.getSecretKeys()
                const pub_keys = AccountService.getPublicKeys()
                const apiClient = new HostedMoneroAPIClient({ $http: $http })
                var parsed_amount;
                try {
                    parsed_amount = mymonero_core_js.monero_amount_format_utils.parseMoney(target.amount);
                } catch (e) {
                    fn("Please enter a valid amount");
                    return
                }
                const send_args =
                {
                    is_sweeping: sweeping, 
                    payment_id_string: payment_id, // passed in
                    sending_amount: sweeping ? 0 : parsed_amount.toString(), // sending amount
                    from_address_string: AccountService.getAddress(),
                    sec_viewKey_string: sec_keys.view,
                    sec_spendKey_string: sec_keys.spend,
                    pub_spendKey_string: pub_keys.spend,
                    to_address_string: target_address,
                    priority: 1,
                    unlock_time: 0, // unlock_time 
                    nettype: config.nettype,
                    //
                    get_unspent_outs_fn: function(req_params, cb)
                    {
                        apiClient.UnspentOuts(req_params, function(err_msg, res)
                        {
                            cb(err_msg, res);
                        });
                    },
                    get_random_outs_fn: function(req_params, cb)
                    {
                        apiClient.RandomOuts(req_params, function(err_msg, res)
                        {
                            cb(err_msg, res);
                        });
                    },
                    submit_raw_tx_fn: function(req_params, cb)
                    {
                        apiClient.SubmitRawTx(req_params, function(err_msg, res)
                        {
                            cb(err_msg, res);
                        });
                    },
                    //
                    status_update_fn: function(params)
                    {
                        let suffix = mymonero_core_js.monero_sendingFunds_utils.SendFunds_ProcessStep_MessageSuffix[params.code]
                        _configureWith_statusUpdate(
                            statusUpdate_messageBase + " " + suffix, // TODO: localize this concatenation
                            params.code
                        )
                    },
                    error_fn: function(params)
                    {
                        fn(params.err_msg)
                    },
                    success_fn: function(params)
                    {
                        const total_sent__JSBigInt = new JSBigInt(params.total_sent)
                        const tx_fee = new JSBigInt(params.used_fee)
                        const total_sent__atomicUnitString = total_sent__JSBigInt.toString()
                        const total_sent__floatString = mymonero_core_js.monero_amount_format_utils.formatMoney(total_sent__JSBigInt) 
                        const total_sent__float = parseFloat(total_sent__floatString)
                        //
                        const mockedTransaction = 
                        {
                            hash: params.tx_hash,
                            mixin: "" + params.mixin,
                            coinbase: false,
                            mempool: true, // is that correct?
                            //
                            isJustSentTransaction: true, // this is set back to false once the server reports the tx's existence
                            timestamp: new Date(), // faking
                            //
                            unlock_time: 0,
                            //
                            // height: null, // mocking the initial value -not- to exist (rather than to erroneously be 0) so that isconfirmed -> false
                            //
                            total_sent: new JSBigInt(total_sent__atomicUnitString),
                            total_received: new JSBigInt("0"),
                            //
                            approx_float_amount: -1 * total_sent__float, // -1 cause it's outgoing
                            // amount: new JSBigInt(sentAmount), // not really used (note if you uncomment, import JSBigInt)
                            //
                            payment_id: params.final_payment_id, // b/c `payment_id` may be nil of short pid was used to fabricate an integrated address
                            //
                            // info we can only preserve locally
                            tx_fee: tx_fee,
                            tx_key: params.tx_key,
                            tx_pub_key: params.tx_pub_key,
                            target_address: target_address, // only we here are saying it's the target
                        };
                        fn(null, mockedTransaction, domain)
                    }
                }
                try {
                    // verify that the address is valid
                    coreBridge_instance.async__send_funds(send_args);
                } catch (e) {
                    fn("Failed to send with exception: " + e);
                    return;
                }
            }
        })
    };
});

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;
}
