const nsExit = require("nativescript-exit")
const globalVars = require("./globalVars")
const sqlite = require("nativescript-sqlite")
const dataManager = require("./dataManager")
const bip39 = require("bip39")
const Btc = require("bitcoinjs-lib")
const crypto = require("crypto-js")
const httpModule = require("http")
const connectivityModule = require("tns-core-modules/connectivity")
const dialogs = require("tns-core-modules/ui/dialogs");
const axios = require("axios")
const dashboard = require("./views/dashboard/dashboard")
const sendPage = require("./views/sendFabcoins/sendFabcoins")
const receivePage = require("./views/receiveFabcoins/receiveFabcoins")
const txHistoryPage = require("./views/transactionHistory/transactionHistory")

var insomnia = require("nativescript-insomnia");
const platform = require("tns-core-modules/platform")

var currentChangeIndex = 0;
var currentReceiveIndex = 0;

var fee = 0.00003000; //this is to keep things simple for the first version. It will be calculated dynamically in the later versions
var feePerVin = 0.00000300;

var currentBalance = 0

var myUtxo = new Array()
var myTxHistory = new Array() //it may be advisable to only keep 100 latest transactions in the memory
var mySpentBuffer = new Array()

var currentBalance = 0
var synchronizeActive = true;

var idleTime = 0
var isInitialized = false

var myConnection = false;

var myApiEndPoint;

var getCurrentApiEndpoint = function () {
    return myApiEndPoint;
}

var getUtxoCount = function () {
    return myUtxo.length;
}

var wait = (ms) => {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

var getFlatFee = function () {
    return fee;
}

var getFeePerVin = function () {
    return feePerVin;
}

var utxoObj = function (type, addrIdx, txid, txidx, amount, height, confirmations, id) {

    this.Id = id;
    this.Type = type;
    this.AddressIndex = addrIdx;
    this.TxId = txid;
    this.TxIdx = txidx;
    this.Amount = amount;
    this.Height = height;
    this.Confirmations = confirmations;
}

var spentBufferObject = function (type, addrIdx, txid, utxoTxId, txidx, amount, height, confirmations, id) {
    this.Id = id;
    this.Type = type;
    this.AddressIndex = addrIdx;
    this.TxId = txid;
    this.UtxoTxId = utxoTxId;
    this.TxIdx = txidx;
    this.Amount = amount;
    this.Height = height;
    this.Confirmations = confirmations;
}

var txHistoryObj = function (type, date, amount, txFee, txId, sentTo, confirmations, comment, id) {

    this.Id = id;
    this.Type = type;
    this.Date = date;
    this.Amount = amount;
    this.TxFee = txFee;
    this.TxId = txId;
    this.SentTo = sentTo;
    this.Confirmations = confirmations;
    this.Comment = comment;
}

var init = function () {

    if (isInitialized) return

    isInitialized = true;


    insomnia.keepAwake()



    //testing purposes only
    // clearTable(globalVars.databaseObjects.utxoTable.name)

    readUtxoFromDatabase();
    readSpentBufferFromDatabase()
    readTxHistoryFromDatabase();

    updateCurrentBalance();
    setChangeAndReceiveIndex()

    synchronizeActive = false;


    //check interval every minute
    setInterval(exitTest, 60000)

    setCurrentApiEndpoint()

}

var setCurrentApiEndpoint = async function () {

    let p = globalVars.apiEndPoints;
    //Math.random returns a number between 0 and 1  
    let s = Math.floor(Math.random() * 100) % p.length;

    let r = await getApiEndpointAvailability(p[s])

    if (r === true) {
        myApiEndPoint = p[s]
    }
    else if (r === false) {

        for (let i = 0; i < p.length; i++) {

            let res = await getApiEndpointAvailability(p[i])
            if (res === true) {
                myApiEndPoint = p[i]

                break;
            }
        }
    }

    async function getApiEndpointAvailability(apiEndpoint) {

        let isConnected = false;
        axios.default.get(apiEndpoint + globalVars.apiExistAddress + getAddress(globalVars.addressType.receive, 0)).then(function (res) {
            isConnected = true
        }).catch(function (e) {
            isConnected = false;
        })

        //3 senconds is the maximum time within which the response should be received
        await wait(3000)
        return isConnected
    }
}

var isPositiveNumber = function (s) {
    let p = String(s);

    if (Number(p) !== NaN && isFinite(Number(p)) && Number(p) > 0) return true
    return false
}

//refresh idle time
var setIdleTime = function () {
    idleTime = 0;
}

//exit after 30 minutes of inactivity
function exitTest() {
    idleTime = idleTime + 1;
    if (idleTime > 29) {
        exit()
    }
}

var readUtxoFromDatabase = function () {

    myUtxo = []
    currentBalance = 0;
    new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.all("SELECT * FROM " + globalVars.databaseObjects.utxoTable.name, function (err, rows) {
            rows.forEach(row => {
                //  myUtxo.push(new utxoObj(row[0], row[1], row[2], row[3], row[4], row[5], row[6]));
                myUtxo.push(new utxoObj(row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[0]));
                currentBalance += Number(row[5]);
            })
        })
    })
}

var readSpentBufferFromDatabase = function () {

    mySpentBuffer = []
    new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.all("SELECT * FROM " + globalVars.databaseObjects.spentBufferTable.name, function (err, rows) {
            rows.forEach(row => {
                mySpentBuffer.push(new spentBufferObject(row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[0]));
            })
        })
    })
}

var readTxHistoryFromDatabase = function () {
    myTxHistory = []
    new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.all("SELECT * FROM " + globalVars.databaseObjects.TransactionHistoryTable.name, function (err, rows) {
            rows.forEach(row => {
                //console.log(row)
                myTxHistory.unshift(new txHistoryObj(row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8]))
            })
        })
    })
}

var setChangeAndReceiveIndex = function () {

    if (dataManager.tableExists(globalVars.databaseObjects.addressIndexTable.name)) {
        new sqlite(globalVars.databaseObjects.name, function (err, db) {
            //0 is for change index
            db.get("SELECT idx FROM AddressIndex WHERE id = 0", function (err, elem) {
                currentChangeIndex = Number(elem)
            })
            //1 is for receive index
            db.get("SELECT idx FROM AddressIndex WHERE id = 1", function (err, elem) {
                currentReceiveIndex = Number(elem)

            })
        })
    }
}

var checkLastReceiveAddressesForReceiptOfFunds = async function () {

    let startIndex = 0;
    let maxUtxoIndex = 0;
    let val = 0;

    if (currentReceiveIndex > 100) {
        startIndex = currentReceiveIndex - 98;
    }


    let c = await checkAddressesForUtxo(globalVars.addressType.receive, startIndex, 100)


    val = c.value

    if (val > 0) {
        dialogs.alert({ title: globalVars.messageObjects.fabcoinsReceived, message: globalVars.messageObjects.youHaveReceived + val + globalVars.messageObjects.fabcoins, okButtonText: globalVars.messageObjects.Ok })
        txHistoryPage.resetTxHistory()
    }
}

var exit = function () {
    //all the exit action related actions may be performed here
    nsExit.exit()
}

var getReceiveAddress = async function () {

    let rAddress;
    if (dataManager.tableExists(globalVars.databaseObjects.addressIndexTable.name)) {
        await new sqlite(globalVars.databaseObjects.name, function (err, db) {
            db.get("SELECT idx FROM AddressIndex WHERE id = 1", function (err, elem) {
                let r = Number(elem)
                db.get("SELECT Address FROM ReceiveAddressList WHERE Idx = " + r, function (err, elem) {
                    rAddress = elem[0]
                })
            })
        })
        return rAddress;
    }
}

var getChangeAddress = async function () {
    let cAddress;
    if (dataManager.tableExists(globalVars.databaseObjects.addressIndexTable.name)) {
        await new sqlite(globalVars.databaseObjects.name, function (err, db) {
            db.get("SELECT idx FROM AddressIndex WHERE id = 0", function (err, elem) {
                let r = Number(elem)
                db.get("SELECT Address FROM ChangeAddressList WHERE Idx = " + r, function (err, elem) {
                    cAddress = elem[0]
                })
            })
        })
        return cAddress;
    }
}

var askForPassword = async function () {


    let r = await dialogs.prompt({
        title: globalVars.messageObjects.showMnemonics,
        message: globalVars.messageObjects.kindlyEnterPasswordToCompleteThisAction,
        okButtonText: globalVars.messageObjects.confirm,
        cancelButtonText: globalVars.messageObjects.cancel,
        inputType: dialogs.inputType.password
    })

    let b = verifyUserPassword(r.text);
    r.text = "**********************"
    return b;

}

var getChangeObject = async function () {

    let cAddress;
    let idx = 0;
    if (dataManager.tableExists(globalVars.databaseObjects.addressIndexTable.name)) {
        await new sqlite(globalVars.databaseObjects.name, function (err, db) {
            db.get("SELECT idx FROM AddressIndex WHERE id = 0", function (err, elem) {

                let r = Number(elem)
                db.get("SELECT Address FROM ChangeAddressList WHERE Idx = " + r, function (err, elem) {

                    cAddress = {
                        addrIndex: r,
                        address: elem[0]
                    }
                })
            })
        })
        return cAddress;
    }
}

var getReceiveObject = async function () {

    let rAddress;
    let idx = 0;
    if (dataManager.tableExists(globalVars.databaseObjects.addressIndexTable.name)) {
        await new sqlite(globalVars.databaseObjects.name, function (err, db) {
            db.get("SELECT idx FROM AddressIndex WHERE id = 1", function (err, elem) {
                let r = Number(elem)
                db.get("SELECT Address FROM ReceiveAddressList WHERE Idx = " + r, function (err, elem) {
                    rAddress = {
                        addrIndex: r,
                        address: elem[0]
                    }
                })
            })
        })
        return rAddress;
    }
}

//to be tested
var getAddress = function (addressType, idx) {

    //if the index is more then the available addresses, return the last address
    let p = getMaxAvailableAddressIndex(addressType);

    if (p < idx) idx = p - 1

    let address;

    if (addressType === globalVars.addressType.change) {

        new sqlite(globalVars.databaseObjects.name, function (err, db) {

            db.get("SELECT Address FROM ChangeAddressList WHERE Idx = " + idx, function (err, elem) {
                address = elem[0]
                if (elem == null) address = false
            })

        })
        return address;

    }
    else if (addressType === globalVars.addressType.receive) {
        new sqlite(globalVars.databaseObjects.name, function (err, db) {

            db.get("SELECT Address FROM ReceiveAddressList WHERE Idx = " + idx, function (err, elem) {
                address = elem[0]
                if (elem == null) address = false
            })

        })
        return address;
    }
}



var generateChangeAddresses = function (mn) {

    let seed = bip39.mnemonicToSeed(mn)
    mn = "*******************************************************************"
    let masterNode = Btc.HDNode.fromSeedBuffer(seed, globalVars.currentNetwork);
    let changeNode = masterNode.derivePath("m/44/0'/0'/1") //1 is for change address

    new sqlite(globalVars.databaseObjects.name, function (err, db) {

        let b;
        db.get("SELECT COUNT(*) FROM ChangeAddressList", function (err, elem) {
            b = elem[0]
        })

        let k = b + 100
        for (let i = b; i < k; i++) {
            let c = changeNode.derive(i).getAddress()
            db.execSQL("INSERT INTO ChangeAddressList (Idx, Address, IsUsed) VALUES (?,?,?)", [i, c, 0], function (err, id) {
                if (err) throw (err)

            })
        }
    })

    seed = "******************************************************"
    masterNode = "******************************************************"
    changeNode = "******************************************************"

    return 0
}

var generateReceiveAddresses = function (mn) {

    let seed = bip39.mnemonicToSeed(mn)
    mn = "*******************************************************************"
    let masterNode = Btc.HDNode.fromSeedBuffer(seed, globalVars.currentNetwork);
    let receiveNode = masterNode.derivePath("m/44/0'/0'/0") // 0 is for receive address 

    new sqlite(globalVars.databaseObjects.name, function (err, db) {

        let b;
        db.get("SELECT COUNT(*) FROM ReceiveAddressList", function (err, elem) {
            b = elem[0]
        })


        let k = b + 100
        for (let i = b; i < k; i++) {

            let r = receiveNode.derive(i).getAddress()
            db.execSQL("INSERT INTO ReceiveAddressList (Idx, Address, IsUsed) VALUES (?,?,?)", [i, r, 0], function (err, id) {
                if (err) throw (err)
            })
        }
    })

    seed = "******************************************************"
    masterNode = "******************************************************"
    receiveNode = "******************************************************"


    return 0
}

var saveMnemonics = async function (mn, pd) {


    if (await dataManager.tableExists(globalVars.databaseObjects.MnemonicTable.name)) {
        await dataManager.dropTable(globalVars.databaseObjects.MnemonicTable.name)
    }


    await new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.execSQL("CREATE TABLE Mnemonics (id INTEGER PRIMARY KEY, mnemonic TEXT)", [], function (err) {
            let k = crypto.PBKDF2(pd, getUuid(), { keySize: 32, iterations: 1500 })
            let encMn = crypto.AES.encrypt(mn, k.toString())

            //let encMn = crypto.AES.encrypt(mn, pd) //encrypted Mnemonic
            db.execSQL("INSERT INTO Mnemonics (mnemonic) VALUES (?)", [encMn], function (err, id) {

            })
        })
    })

    mn = "*************"
    pd = "*************"


    return 0
}


var savePin = async function (pin) {

    if (await dataManager.tableExists(globalVars.databaseObjects.PinTable.name)) {
        await dataManager.dropTable(globalVars.databaseObjects.PinTable.name)
    }


    await new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.execSQL("CREATE TABLE Pin (id INTEGER PRIMARY KEY, pin TEXT)", [], function (err) {

            let h = new crypto.PBKDF2(pin, 'fabcoin', { keySize: 32, iterations: 500 }) //encrypted Mnemonic
            db.execSQL("INSERT INTO Pin (pin) VALUES (?)", [h], function (err, id) {

                h = "*********************************************"
                pin = "*************************************"
            })
        })
    })
}

var setUuid = function () {

    if (dataManager.tableExists(globalVars.databaseObjects.uuidTable.name)) {
        dataManager.dropTable(globalVars.databaseObjects.uuidTable.name)
    }

    new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.execSQL("CREATE TABLE Uuid (id INTEGER PRIMARY KEY, uuid TEXT)", [], function (err) {
            let uuid = platform.device.uuid;
            db.execSQL("INSERT INTO Uuid (uuid) VALUES (?)", [uuid], function (err, id) {

            })
        })
    })
}

var getUuid = function () {
    let uuid;
    new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.get("SELECT uuid FROM Uuid WHERE id = 1", function (err, element) {
            uuid = element[0]
        })
    })

    return uuid;
}


var sendFabcoins = async function (receiversAddress, amount, deductFeeFromSendAmount, comment) {


    checkInternetConnection()
    await wait(3000);

    if (!myConnection) {
        //try to reset the api endpoint first before giving the error
        await setCurrentApiEndpoint()
        checkInternetConnection()
        await wait(3000);
    }

    if (!myConnection) {
        dialogs.alert({ title: globalVars.messageObjects.error, message: globalVars.messageObjects.connectionTimeout + "\n" + globalVars.messageObjects.kindlyResetConnectionOrTryAfterSomeTime, okButtonText: globalVars.messageObjects.Ok })
        return;
    }

    let inputTx = new Array()

    let tmpAmt = 0;
    let count = 0;
    let sendAmount = amount

    if (deductFeeFromSendAmount) {
        while (tmpAmt < sendAmount) {
            tmpAmt += Number(myUtxo[count].Amount)
            inputTx.push(myUtxo[count])
            count++;
        }
    }
    else {
        while (tmpAmt < (sendAmount + fee)) {
            tmpAmt += Number(myUtxo[count].Amount)
            inputTx.push(myUtxo[count])
            count++;
        }
    }


    tmpAmt = Math.floor(tmpAmt * 1e8)
    sendAmount = Math.floor(sendAmount * 1e8)

    let Transaction = new Btc.TransactionBuilder(globalVars.currentNetwork)


    for (let k = 0; k < inputTx.length; k++) {
        Transaction.addInput(myUtxo[k].TxId, myUtxo[k].TxIdx)
    }


    //This portion of code is experimental.
    //This is to make sure that the 
    /*
    if (count > 10) {
        let amt = 0;
        for (let k = 0; k < 10; k++) {
            amt += Number(myUtxo[k].Amount)
        }

        if (!deductFeeFromSendAmount) {
            amt -= Number(fee)
        }

        dialogs.alert({ title: "Error", message: "The Fab Wallet(BETA) currently supports upto 10 inputs for a transaction. The inputs(UTXOs) for Your current amount exceeds this limit. Kindly Enter an amount lower then " + amt + " .", okButtonText: "Ok" })

        dataManager.showTable(globalVars.databaseObjects.utxoTable.name)
        console.log("hrere")
        console.log(count)
        return;
    }

    */


    let changeObj = await getChangeObject();
    let changeAddress = changeObj.address
    let changeAddressIndex = changeObj.addrIndex
    let changeAmount = Math.floor(tmpAmt - sendAmount - (fee * 1e8))


    if (deductFeeFromSendAmount) {
        changeAmount = Math.floor(tmpAmt - sendAmount)
        Transaction.addOutput(receiversAddress, sendAmount - (fee * 1e8))
    }
    else {
        changeAmount = Math.floor(tmpAmt - sendAmount - (fee * 1e8))
        Transaction.addOutput(receiversAddress, sendAmount)
    }

    let amtForDisplay = (deductFeeFromSendAmount) ? Number((sendAmount * 1e-8) - fee).toFixed(8) : Number(sendAmount * 1e-8).toFixed(8)

    if (changeAmount > 0) {
        Transaction.addOutput(changeAddress, changeAmount)
    }

    //ask for password
    let r = await dialogs.prompt({
        title: globalVars.messageObjects.sendFabcoin,
        message: globalVars.messageObjects.kindlyCheckTxDetailsAndEnterPassword + "\n" + globalVars.messageObjects.to + " : " + receiversAddress + "\n" + globalVars.messageObjects.amount + " : " + amtForDisplay + "\n" + globalVars.messageObjects.transactionFee + " : " + fee,
        okButtonText: globalVars.messageObjects.confirm,
        cancelButtonText: globalVars.messageObjects.cancel,
        inputType: dialogs.inputType.password
    })

    let pd = r.text;

    // if (!verifyUserPassword(r.text)) {

    if (!verifyPasswordHash(crypto.PBKDF2(r.text, getUuid(), { keySize: 32, iterations: 1000 }))) {
        //show warning and return
        dialogs.alert({ title: globalVars.messageObjects.error, message: globalVars.messageObjects.passwordIncorrect, okButtonText: globalVars.messageObjects.Ok });
        return
    }
    else {

        let encMn = getElementFromTable("Mnemonics", "mnemonic", "id", "1")
        let k = crypto.PBKDF2(r.text, getUuid(), { keySize: 32, iterations: 1500 })
        // let mn = crypto.AES.decrypt(encMn, r.text).toString(crypto.enc.Utf8);
        let mn = crypto.AES.decrypt(encMn, k.toString()).toString(crypto.enc.Utf8);

        let seed = bip39.mnemonicToSeed(mn)
        let masterNode = Btc.HDNode.fromSeedBuffer(seed, globalVars.currentNetwork);
        let changeNode = masterNode.derivePath("m/44/0'/0'/1") //1 is for change address
        let receiveNode = masterNode.derivePath("m/44/0'/0'/0") // 0 is for receive address 

        for (let k = inputTx.length - 1; k >= 0; k--) {

            let utxo = inputTx[k]

            let keypair = 0;

            if (utxo.Type === globalVars.addressType.change) {
                keypair = changeNode.derive(utxo.AddressIndex).keyPair;
            }
            else if (utxo.Type === globalVars.addressType.receive) {
                keypair = receiveNode.derive(utxo.AddressIndex).keyPair;
            }

            // let keypair = getKeypair(utxo.Type, utxo.AddressIndex)
            Transaction.sign(k, keypair)

        }
    }

    r.text = "**************"

    let TxHex = Transaction.build().toHex();
    let TxId = Transaction.build().getId();

    //the transaction is now ready to be broadcast
    //send the transaction out and update utxo,history and balance accordingly

    //TESTIING - Remove/alter after testing is completed

    // console.log(TxHex);
    // return;

    //send the transaction using the POST method and handle appropriate response/error
    // await axios.post("http://fabexplorer.info:9001/fabapi/sendrawtransaction", { txhex: TxHex }).then(function (response) {
    await axios.post(myApiEndPoint + globalVars.apiSendTx, { txhex: TxHex }).then(function (response) {
       // console.log("reseponse")
       // console.log(response)
        //This is the txid - console.log(response.data)
        //AT this point, The transaction was successful
        //increase the change index so that a new change address can be used for next transaction
        //remove the spent utxos from the UTXOTable and move it to Spentbuffer      

        for (let k = 0; k < inputTx.length; k++) {
            removeFromUtxo(inputTx[k].TxId, inputTx[k].TxIdx)

            //here, also add the same to the spent buffer
            addToSpentBuffer(inputTx[k].Type, inputTx[k].AddressIndex, TxId, inputTx[k].TxId, inputTx[k].TxIdx, inputTx[k].Amount, inputTx[k].Height, inputTx[k].confirmations)

        }
        //add the change address utxo to the database
        if (changeAmount > 0) {
            addToUtxo(globalVars.addressType.change, changeAddressIndex, TxId, 1, Number(changeAmount * 1e-8).toFixed(8), 1, 1)
            increaseChangeIndex()
        }
        // add to the transaction history
        if (deductFeeFromSendAmount) {
            addToTxHistory(globalVars.addressType.send, getCurrentTimeStamp(), (sendAmount * 1e-8) - fee, fee, TxId, receiversAddress, 1, comment);
        }
        else {
            addToTxHistory(globalVars.addressType.send, getCurrentTimeStamp(), (sendAmount * 1e-8), fee, TxId, receiversAddress, 1, comment);
        }
        //update utxo
        readUtxoFromDatabase()
        //update balance
        updateCurrentBalance()
        dialogs.alert({ title: globalVars.messageObjects.fabcoinsSent, message: globalVars.messageObjects.sendTransactionSubmitted, okButtonText: globalVars.messageObjects.Ok })



    }).catch(function (error) {
      //  console.log(error)
        //show appropriate alert
        dialogs.alert({ title: globalVars.messageObjects.error, message: globalVars.messageObjects.sendErrorMessage + "\n" + error, okButtonText: globalVars.messageObjects.Ok })

    })
    return


    /*

    let sendResult = await axios.get(globalVars.configURLSendTx + TxHex).then(function (response) {

        //This is the txid - console.log(response.data)
        //AT this point, The transaction was successful
        //increase the change index so that a new change address can be used for next transaction
        //remove the spent utxos from the UTXOTable and move it to Spentbuffer      

        for (let k = 0; k < inputTx.length; k++) {
            removeFromUtxo(inputTx[k].TxId, inputTx[k].TxIdx)

            //here, also add the same to the spent buffer
            addToSpentBuffer(inputTx[k].Type, inputTx[k].AddressIndex, TxId, inputTx[k].TxId, inputTx[k].TxIdx, inputTx[k].Amount, inputTx[k].Height, inputTx[k].confirmations)

        }
        //add the change address utxo to the database
        if (changeAmount > 0) {
            addToUtxo(globalVars.addressType.change, changeAddressIndex, TxId, 1, Number(changeAmount * 1e-8).toFixed(8), 1, 1)
            increaseChangeIndex()
        }
        // add to the transaction history
        if (deductFeeFromSendAmount) {
            addToTxHistory(globalVars.addressType.send, getCurrentTimeStamp(), (sendAmount * 1e-8) - fee, fee, TxId, receiversAddress, 1,comment);
        }
        else {
            addToTxHistory(globalVars.addressType.send, getCurrentTimeStamp(), (sendAmount * 1e-8), fee, TxId, receiversAddress, 1,comment);
        }
        //update utxo
        readUtxoFromDatabase()
        //update balance
        updateCurrentBalance()
        dialogs.alert({ title: globalVars.messageObjects.fabcoinsSent, message: globalVars.messageObjects.sendTransactionSubmitted, okButtonText: globalVars.messageObjects.Ok })
    }).catch(function (error) {
        //show appropriate alert
        dialogs.alert({ title: globalVars.messageObjects.error, message: globalVars.messageObjects.sendErrorMessage + "\n" + error, okButtonText: globalVars.messageObjects.Ok })

    }) */
}

var getSpendableBalance = function (deductFeeFromSendAmount) {


    let spendableBalance = 0;
    let flatFee = fee;
    // let feePerVin = 0.00000100
    let count = 0;
    let maxCount = 100;

    if (deductFeeFromSendAmount) {

        while (count < maxCount && count < myUtxo.length) {
            spendableBalance += Number(myUtxo[count].Amount)
            count++;
        }

    }
    else {

        spendableBalance -= flatFee;
        balancePlusFee = spendableBalance;
        let currentUtxoTotal = 0
        let cBalance = getCurrentBalance()

        while (count < maxCount) {
            if (count < myUtxo.length && spendableBalance < cBalance) {
                spendableBalance = spendableBalance + Number(myUtxo[count].Amount) - feePerVin
                count++;

            }
            else {
                break;
            }

        }
    }

    return Number(spendableBalance.toFixed(8));
}

var sendFabcoinsTest = async function (receiversAddress, amount, deductFeeFromSendAmount, comment) {



    checkInternetConnection()
    await wait(3000);



    let t1 = new Date().getTime()



    let inputTx = new Array()

    let tmpAmt = 0;
    let count = 0;
    let sendAmount = amount

    let mySendAmount = Number(amount);
    let currentUtxoTotal = Number(0);
    let myTxFee = Number(fee);
    let myVinCount = 0;
    let myChangeAmount = 0;
    //  let feePerVin = 0.00000200 //subject to change - may bedynamic in the later versions - declared above



    if (deductFeeFromSendAmount) {
        while (currentUtxoTotal < mySendAmount) {
            inputTx.push(myUtxo[myVinCount])
            currentUtxoTotal += Number(myUtxo[myVinCount].Amount)
            myVinCount++;
            myTxFee += Number(feePerVin.toFixed(8))
           // console.log(myVinCount, myTxFee, mySendAmount, currentUtxoTotal)
        }
    }
    else {

        //myTxFee += feePerVin
        while (currentUtxoTotal < Number((mySendAmount + myTxFee).toFixed(8))) {
          //  console.log("brfore", myVinCount, myTxFee, mySendAmount, currentUtxoTotal, (mySendAmount + myTxFee))

            inputTx.push(myUtxo[myVinCount])
          //  console.log(1)
            currentUtxoTotal += Number(myUtxo[myVinCount].Amount)
            myVinCount++;
            myTxFee += Number(feePerVin.toFixed(8))
            myTxFee = Number(myTxFee.toFixed(8))
           // console.log(myVinCount, myTxFee, mySendAmount, currentUtxoTotal, (mySendAmount + myTxFee))
        }
    }

    currentUtxoTotal = Number(currentUtxoTotal.toFixed(8))

 //   console.log("Here")
    let Transaction = new Btc.TransactionBuilder(globalVars.currentNetwork)

    for (let k = 0; k < inputTx.length; k++) {
        Transaction.addInput(myUtxo[k].TxId, myUtxo[k].TxIdx)
    }

    let changeObj = await getChangeObject();


    let changeAddress = changeObj.address
    let changeAddressIndex = changeObj.addrIndex

    if (deductFeeFromSendAmount) {

        myChangeAmount = convertToLiu(Number(currentUtxoTotal - mySendAmount))
        Transaction.addOutput(receiversAddress, convertToLiu(mySendAmount - myTxFee))
    }
    else {

        myChangeAmount = convertToLiu(Number(currentUtxoTotal - mySendAmount - myTxFee))
        Transaction.addOutput(receiversAddress, convertToLiu(mySendAmount))
    }






    let amtForDisplay = (deductFeeFromSendAmount) ? (mySendAmount - myTxFee).toFixed(8) : mySendAmount.toFixed(8)


    // console.log(myChangeAmount,globalVars.minimumThresholdAmount)
    // return;
    //must convert to liu in order to get the correct change amount
    if (myChangeAmount > convertToLiu(globalVars.minimumThresholdAmount)) {
        Transaction.addOutput(changeAddress, myChangeAmount)
    }

    let r = await dialogs.prompt({
        title: globalVars.messageObjects.sendFabcoin,
        message: globalVars.messageObjects.kindlyCheckTxDetailsAndEnterPassword + "\n" + globalVars.messageObjects.to + " : " + receiversAddress + "\n" + globalVars.messageObjects.amount + " : " + Number(amtForDisplay).toString() + "\n" + globalVars.messageObjects.transactionFee + " : " + Number(myTxFee.toFixed(8)).toString(),
        okButtonText: globalVars.messageObjects.confirm,
        cancelButtonText: globalVars.messageObjects.cancel,
        inputType: dialogs.inputType.password
    })

    //user clicked cancel
    if (r.result === false) return

    //console.log(mySendAmount, myTxFee, myVinCount, currentUtxoTotal, myChangeAmount, amtForDisplay)


    if (!verifyPasswordHash(crypto.PBKDF2(r.text, getUuid(), { keySize: 32, iterations: 1000 }))) {
        //show warning and return
        dialogs.alert({ title: globalVars.messageObjects.error, message: globalVars.messageObjects.passwordIncorrect, okButtonText: globalVars.messageObjects.Ok });
        return
    }
    else {

        let encMn = getElementFromTable("Mnemonics", "mnemonic", "id", "1")
        let k = crypto.PBKDF2(r.text, getUuid(), { keySize: 32, iterations: 1500 })
        // let mn = crypto.AES.decrypt(encMn, r.text).toString(crypto.enc.Utf8);
        let mn = crypto.AES.decrypt(encMn, k.toString()).toString(crypto.enc.Utf8);

        let seed = bip39.mnemonicToSeed(mn)
        let masterNode = Btc.HDNode.fromSeedBuffer(seed, globalVars.currentNetwork);
        let changeNode = masterNode.derivePath("m/44/0'/0'/1") //1 is for change address
        let receiveNode = masterNode.derivePath("m/44/0'/0'/0") // 0 is for receive address 

        for (let k = inputTx.length - 1; k >= 0; k--) {

            let utxo = inputTx[k]

            let keypair = 0;

            if (utxo.Type === globalVars.addressType.change) {
                keypair = changeNode.derive(utxo.AddressIndex).keyPair;
            }
            else if (utxo.Type === globalVars.addressType.receive) {
                keypair = receiveNode.derive(utxo.AddressIndex).keyPair;
            }

            // let keypair = getKeypair(utxo.Type, utxo.AddressIndex)
            Transaction.sign(k, keypair)

        }
    }

    r.text = "**************"

    let TxHex = Transaction.build().toHex();
    let TxId = Transaction.build().getId();

    //the transaction is now ready to be broadcast
    //send the transaction out and update utxo,history and balance accordingly

    //TESTIING - Remove/alter after testing is completed

    //console.log(TxId);
   // console.log(TxHex)

    // return;

    let t2 = new Date().getTime()

   // console.log((t2 - t1) + " ms for signing");

    //send the transaction using the POST method and handle appropriate response/error
    // await axios.post("http://fabexplorer.info:9001/fabapi/sendrawtransaction", { txhex: TxHex }).then(function (response) {

    //console.log(myApiEndPoint + globalVars.apiSendTx+TxHex)

    //testing purposes only
    // myApiEndPoint = globalVars.apiEndPoints[0]
   // console.log(myApiEndPoint + globalVars.apiSendTx + "/" + TxHex)
    await axios.post(myApiEndPoint + globalVars.apiSendTx, { txhex: TxHex }).then(function (response) {
     //   console.log("reseponse")
     //   console.log(response.data)
        //This is the txid - console.log(response.data)
        //AT this point, The transaction was successful
        //increase the change index so that a new change address can be used for next transaction
        //remove the spent utxos from the UTXOTable and move it to Spentbuffer      

        for (let k = 0; k < inputTx.length; k++) {
            removeFromUtxo(inputTx[k].TxId, inputTx[k].TxIdx)

            //here, also add the same to the spent buffer
            addToSpentBuffer(inputTx[k].Type, inputTx[k].AddressIndex, TxId, inputTx[k].TxId, inputTx[k].TxIdx, inputTx[k].Amount, inputTx[k].Height, inputTx[k].confirmations)

        }
        //add the change address utxo to the database
     //   console.log(myChangeAmount, convertToLiu(globalVars.minimumThresholdAmount))
        if (myChangeAmount > convertToLiu(globalVars.minimumThresholdAmount)) {
            addToUtxo(globalVars.addressType.change, changeAddressIndex, TxId, 1, Number((myChangeAmount * 1e-8).toFixed(8)), 1, 1)
            increaseChangeIndex()
        }

        // add to the transaction history
        if (deductFeeFromSendAmount) {
            addToTxHistory(globalVars.addressType.send, getCurrentTimeStamp(), (mySendAmount - myTxFee), myTxFee, TxId, receiversAddress, 1, comment);
        }
        else {
            addToTxHistory(globalVars.addressType.send, getCurrentTimeStamp(), (mySendAmount), myTxFee, TxId, receiversAddress, 1, comment);
        }
        //update utxo
        readUtxoFromDatabase()
        //update balance
        updateCurrentBalance()

        let t3 = new Date().getTime()
      //  console.log((t3 - t2) + " ms for sending.")
        dialogs.alert({ title: globalVars.messageObjects.fabcoinsSent, message: globalVars.messageObjects.sendTransactionSubmitted, okButtonText: globalVars.messageObjects.Ok })



    }).catch(function (error) {
      //  console.log(error)
        //show appropriate alert
        dialogs.alert({ title: globalVars.messageObjects.error, message: globalVars.messageObjects.sendErrorMessage + "\n" + error, okButtonText: globalVars.messageObjects.Ok })

    })
}



var sendFabcoinsTest1 = async function (receiversAddress, amount, deductFeeFromSendAmount, comment) {



    checkInternetConnection()
    await wait(3000);

    let t1 = new Date().getTime()
    let inputTx = new Array()

    //let mySendAmount = Number(amount);
    let mySendAmount = convertToLiu(amount)
    let currentUtxoTotal = Number(0);
    //    let myTxFee = Number(fee);
    let myTxFee = convertToLiu(fee);
    let myVinCount = 0;
    let myChangeAmount = 0;


  
    if (deductFeeFromSendAmount) {
        while (currentUtxoTotal < mySendAmount) {
            inputTx.push(myUtxo[myVinCount])
            // currentUtxoTotal += Number(myUtxo[myVinCount].Amount)
            currentUtxoTotal += convertToLiu(myUtxo[myVinCount].Amount)
            myVinCount++;
            //myTxFee += Number(feePerVin.toFixed(8))
            myTxFee += convertToLiu(feePerVin.toFixed(8))
          //  console.log(myVinCount, myTxFee, mySendAmount, currentUtxoTotal)
        }
    }
    else {

        //myTxFee += feePerVin
        while (currentUtxoTotal < (mySendAmount + myTxFee)) {
           // console.log("brfore", myVinCount, myTxFee, mySendAmount, currentUtxoTotal, (mySendAmount + myTxFee))

            inputTx.push(myUtxo[myVinCount])
          //  console.log(1)
            //  currentUtxoTotal += Number(myUtxo[myVinCount].Amount)
            currentUtxoTotal += convertToLiu(myUtxo[myVinCount].Amount)
            myVinCount++;
            // myTxFee += Number(feePerVin.toFixed(8))
            myTxFee += convertToLiu(feePerVin)
          //  console.log(myVinCount, myTxFee, mySendAmount, currentUtxoTotal, (mySendAmount + myTxFee))
        }
    }



    //currentUtxoTotal = Number(currentUtxoTotal.toFixed(8))


    let Transaction = new Btc.TransactionBuilder(globalVars.currentNetwork)

    for (let k = 0; k < inputTx.length; k++) {
        Transaction.addInput(myUtxo[k].TxId, myUtxo[k].TxIdx)
    }

    let changeObj = await getChangeObject();


    let changeAddress = changeObj.address
    let changeAddressIndex = changeObj.addrIndex

    if (deductFeeFromSendAmount) {

       // myChangeAmount = convertToLiu(Number(currentUtxoTotal - mySendAmount))
        myChangeAmount = currentUtxoTotal - mySendAmount
       // Transaction.addOutput(receiversAddress, convertToLiu(mySendAmount - myTxFee))
       Transaction.addOutput(receiversAddress, (mySendAmount - myTxFee))
    }
    else {

       // myChangeAmount = convertToLiu(Number(currentUtxoTotal - mySendAmount - myTxFee))
       myChangeAmount = (currentUtxoTotal - mySendAmount - myTxFee)
        //Transaction.addOutput(receiversAddress, convertToLiu(mySendAmount))
        Transaction.addOutput(receiversAddress, mySendAmount)
    }






    //let amtForDisplay = (deductFeeFromSendAmount) ? (mySendAmount - myTxFee).toFixed(8) : mySendAmount.toFixed(8)

    let amtForDisplay = (deductFeeFromSendAmount) ? (mySendAmount - myTxFee) : mySendAmount
    amtForDisplay = convertLiuToFabcoin(amtForDisplay)

    // console.log(myChangeAmount,globalVars.minimumThresholdAmount)
    // return;
    //must convert to liu in order to get the correct change amount
    if (myChangeAmount > convertToLiu(globalVars.minimumThresholdAmount)) {
        Transaction.addOutput(changeAddress, myChangeAmount)
    }

    let r = await dialogs.prompt({
        title: globalVars.messageObjects.sendFabcoin,
        message: globalVars.messageObjects.kindlyCheckTxDetailsAndEnterPassword + "\n" + globalVars.messageObjects.to + " : " + receiversAddress + "\n" + globalVars.messageObjects.amount + " : " + amtForDisplay + "\n" + globalVars.messageObjects.transactionFee + " : " + convertLiuToFabcoin(myTxFee),
        okButtonText: globalVars.messageObjects.confirm,
        cancelButtonText: globalVars.messageObjects.cancel,
        inputType: dialogs.inputType.password
    })

   

    //user clicked cancel
    if (r.result === false) return

    //console.log(mySendAmount, myTxFee, myVinCount, currentUtxoTotal, myChangeAmount, amtForDisplay)



    if (!verifyPasswordHash(crypto.PBKDF2(r.text, getUuid(), { keySize: 32, iterations: 1000 }))) {
        //show warning and return
        dialogs.alert({ title: globalVars.messageObjects.error, message: globalVars.messageObjects.passwordIncorrect, okButtonText: globalVars.messageObjects.Ok });
        return
    }
    else {

        let encMn = getElementFromTable("Mnemonics", "mnemonic", "id", "1")
        let k = crypto.PBKDF2(r.text, getUuid(), { keySize: 32, iterations: 1500 })
        // let mn = crypto.AES.decrypt(encMn, r.text).toString(crypto.enc.Utf8);
        let mn = crypto.AES.decrypt(encMn, k.toString()).toString(crypto.enc.Utf8);

        let seed = bip39.mnemonicToSeed(mn)
        let masterNode = Btc.HDNode.fromSeedBuffer(seed, globalVars.currentNetwork);
        let changeNode = masterNode.derivePath("m/44/0'/0'/1") //1 is for change address
        let receiveNode = masterNode.derivePath("m/44/0'/0'/0") // 0 is for receive address 

        for (let k = inputTx.length - 1; k >= 0; k--) {

            let utxo = inputTx[k]

            let keypair = 0;

            if (utxo.Type === globalVars.addressType.change) {
                keypair = changeNode.derive(utxo.AddressIndex).keyPair;
            }
            else if (utxo.Type === globalVars.addressType.receive) {
                keypair = receiveNode.derive(utxo.AddressIndex).keyPair;
            }

            // let keypair = getKeypair(utxo.Type, utxo.AddressIndex)
            Transaction.sign(k, keypair)

        }
    }

    r.text = "**************"

    let TxHex = Transaction.build().toHex();
    let TxId = Transaction.build().getId();

    //the transaction is now ready to be broadcast
    //send the transaction out and update utxo,history and balance accordingly

    //TESTIING - Remove/alter after testing is completed

  //  console.log(TxId);
   // console.log(TxHex)

    // return;

    let t2 = new Date().getTime()

   // console.log((t2 - t1) + " ms for signing");

    //send the transaction using the POST method and handle appropriate response/error
    // await axios.post("http://fabexplorer.info:9001/fabapi/sendrawtransaction", { txhex: TxHex }).then(function (response) {

    //console.log(myApiEndPoint + globalVars.apiSendTx+TxHex)

    //testing purposes only
    myApiEndPoint = globalVars.apiEndPoints[0]
  //  console.log(myApiEndPoint + globalVars.apiSendTx + TxHex)
    await axios.post(myApiEndPoint + globalVars.apiSendTx, { txhex: TxHex }).then(function (response) {
     //   console.log("reseponse")
      //  console.log(response.data)
        //This is the txid - console.log(response.data)
        //AT this point, The transaction was successful
        //increase the change index so that a new change address can be used for next transaction
        //remove the spent utxos from the UTXOTable and move it to Spentbuffer      

        for (let k = 0; k < inputTx.length; k++) {
            removeFromUtxo(inputTx[k].TxId, inputTx[k].TxIdx)

            //here, also add the same to the spent buffer
            addToSpentBuffer(inputTx[k].Type, inputTx[k].AddressIndex, TxId, inputTx[k].TxId, inputTx[k].TxIdx, inputTx[k].Amount, inputTx[k].Height, inputTx[k].confirmations)

        }

        

       // console.log(myChangeAmount, convertToLiu(globalVars.minimumThresholdAmount))
        //add the change address utxo to the database

        if (myChangeAmount > convertToLiu(globalVars.minimumThresholdAmount)) {
           // addToUtxo(globalVars.addressType.change, changeAddressIndex, TxId, 1, Number((myChangeAmount * 1e-8).toFixed(8)), 1, 1)
           addToUtxo(globalVars.addressType.change, changeAddressIndex, TxId, 1, convertLiuToFabcoin(myChangeAmount), 1, 1)
           increaseChangeIndex()
        }

        // add to the transaction history
        if (deductFeeFromSendAmount) {
            //addToTxHistory(globalVars.addressType.send, getCurrentTimeStamp(), (mySendAmount - myTxFee), myTxFee, TxId, receiversAddress, 1, comment);
            addToTxHistory(globalVars.addressType.send, getCurrentTimeStamp(), convertLiuToFabcoin(mySendAmount - myTxFee), convertLiuToFabcoin(myTxFee), TxId, receiversAddress, 1, comment);
        }
        else {
           // addToTxHistory(globalVars.addressType.send, getCurrentTimeStamp(), (mySendAmount), myTxFee, TxId, receiversAddress, 1, comment);
           addToTxHistory(globalVars.addressType.send, getCurrentTimeStamp(), convertLiuToFabcoin(mySendAmount), convertLiuToFabcoin(myTxFee), TxId, receiversAddress, 1, comment);
        }
        //update utxo
        readUtxoFromDatabase()
        //update balance
        updateCurrentBalance()

        let t3 = new Date().getTime()
     //   console.log((t3 - t2) + " ms for sending.")
        dialogs.alert({ title: globalVars.messageObjects.fabcoinsSent, message: globalVars.messageObjects.sendTransactionSubmitted, okButtonText: globalVars.messageObjects.Ok })



    }).catch(function (error) {
       // console.log(error)
        //show appropriate alert
        dialogs.alert({ title: globalVars.messageObjects.error, message: globalVars.messageObjects.sendErrorMessage + "\n" + error, okButtonText: globalVars.messageObjects.Ok })

    })
}

var convertToLiu = function (amount) {
    return  Math.round(amount * 1e8) // Math.floor(amount * 1e8)
}

var convertLiuToFabcoin = function(amount) {
    return Number(Number(amount * 1e-8).toFixed(8))
}


var removeFromUtxo = function (txid, txidx) {

    new sqlite(globalVars.databaseObjects.name, function (err, db) {
        let qry = "DELETE FROM UtxoTable WHERE TxId = '" + txid + "' AND TxIdx = " + txidx
        db.execSQL(qry, function (err) {

        })
    })
}

var removeFromSpentBuffer = function (txid) {
    new sqlite(globalVars.databaseObjects.name, function (err, db) {
        let qry = "DELETE FROM " + globalVars.databaseObjects.spentBufferTable.name + " WHERE TxId = '" + txid + "'"

        db.execSQL(qry, function (err) {

        })

    })

    readSpentBufferFromDatabase()
}

var addToSpentBuffer = function (type, addrIdx, txid, utxoTxId, txidx, amount, height, confirmations) {

    mySpentBuffer.push(new spentBufferObject(type, addrIdx, txid, utxoTxId, txidx, amount, height, confirmations))
    // also add to database
    new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.execSQL("INSERT INTO SpentBufferTable (Type,AddressIndex,TxId,UtxoTxId,TxIdx,Amount,Height,Confirmations) VALUES (?,?,?,?,?,?,?,?)", [type, addrIdx, txid, utxoTxId, txidx, amount, height, confirmations], function (err) {

        })
    })
}

var increaseReceiveIndex = function () {

    currentReceiveIndex += 1;

    if (tableExists(globalVars.databaseObjects.addressIndexTable.name)) {
        new sqlite(globalVars.databaseObjects.name, function (err, db) {
            //1 is for recive index
            let qryString = "UPDATE AddressIndex SET idx = " + currentReceiveIndex + " WHERE id = 1"
            db.execSQL(qryString, function (err, id) {

            })
        })
    }
}

var increaseChangeIndex = function () {

    currentChangeIndex += 1;

    //testing only
    //currentChangeIndex = 0


    if (tableExists(globalVars.databaseObjects.addressIndexTable.name)) {
        new sqlite(globalVars.databaseObjects.name, function (err, db) {

            //0 is for change index

            let qryString = "UPDATE AddressIndex SET idx = " + currentChangeIndex + " WHERE id = 0"

            db.execSQL(qryString, function (err, id) {

            })
        })
    }
}


var addToTxHistory = function (type, date, amount, txFee, txId, sentTo, confirmations, comment) {

    myTxHistory.unshift(new txHistoryObj(type, date, amount, txFee, txId, sentTo, confirmations, comment))
    new sqlite(globalVars.databaseObjects.name, function (err, db) {

        db.execSQL("INSERT INTO TransactionHistory (Type,Date,Amount,TxFee,TxId,SentTo,Confirmations,Comment) VALUES (?,?,?,?,?,?,?,?)", [type, date, amount, txFee, txId, sentTo, confirmations, comment], function (err) {

            // showTxHistory()
        })
    })
}

var getElementFromTable = function (tableName, reqColumnName, rowName, rowId) {

    var result
    let queryString = "SELECT " + reqColumnName + " FROM " + tableName + " WHERE " + rowName + " = " + rowId

    new sqlite("fabPass.db", function (err, db) {
        db.all(queryString, function (err, element) {

            //sHash = element
            if (err !== null) {
                result = err
            }
            else {
                result = element[0][0]
            }
        })
    })
    return result
}

var verifyUserPassword = function (userPassword) {


    var uHash = new crypto.PBKDF2(userPassword, getUuid(), { keySize: 32, iterations: 1000 })
    var sHash;

    var isValid = false

    new sqlite("fabPass.db", function (err, db) {
        db.get("SELECT pass FROM Pass WHERE id = 1", function (err, element) {

            sHash = element
        })
    })

    if (String(uHash) === String(sHash)) isValid = true


    userPassword = "*******************"

    return isValid
}

var verifyPasswordHash = function (pHash) {

    let isValid = false
    let sHash = ""

    new sqlite("fabPass.db", function (err, db) {
        db.get("SELECT pass FROM Pass WHERE id = 1", function (err, element) {
            sHash = element
        })
    })

    if (String(pHash) === String(sHash)) isValid = true

    pHash = "*******************"

    return isValid

}

var verifyUserPin = function (userPin) {

    let u = getUuid()

    var uHash = new crypto.PBKDF2(userPin, u, { keySize: 32, iterations: 500 })

    var sHash;

    var isValid = false

    new sqlite("fabPass.db", function (err, db) {
        db.get("SELECT pin FROM Pin WHERE id = 1", function (err, element) {
            sHash = element
        })
    })


    if (String(uHash) === String(sHash)) isValid = true

    userPin = "*******************"

    return isValid
}

//If the difference between available addresses and used addresses is less then 100, new addresses will need to be generated. In this case, the password is required
var areNewReceiveAddressesRequired = async function () {

    let isRequired = false
    let maxReceiveIndex = 0

    if (getMaxAvailableAddressIndex(globalVars.addressType.receive) === 0) {
        return true;
    }

    await new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.get("SELECT * FROM ReceiveAddressList WHERE Idx = (SELECT MAX(Idx) FROM ReceiveAddressList)", function (err, row) {
            maxReceiveIndex = Number(row[0]) - 20 //first element of the row is Index

        })

        db.get("SELECT IsUsed FROM ReceiveAddressList WHERE Idx = " + maxReceiveIndex, function (err, row) {


            let tmp = row[0]

            if (tmp === 0) {
                isRequired = false
            }
            else {
                isRequired = true
            }
        })
    })


    return isRequired

}

//If the difference between available change addresses and used addresses is less then 100, new addresses will need to be generated. In this case, the password is required
var areNewChangeAddressesRequired = async function () {

    let isRequired = false
    let maxChangeIndex = 0

    if (getMaxAvailableAddressIndex(globalVars.addressType.change) === 0) {
        return true;
    }

    await new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.get("SELECT * FROM ChangeAddressList WHERE Idx = (SELECT MAX(Idx) FROM ChangeAddressList)", function (err, row) {
            maxChangeIndex = Number(row[0]) - 20 //first element of the row is Index
        })


        db.get("SELECT IsUsed FROM ChangeAddressList WHERE Idx = " + maxChangeIndex, function (err, row) {


            let tmp = row[0]


            if (tmp === 0) {
                isRequired = false
            }
            else {
                isRequired = true
            }
        })
    })



    return isRequired

}




var checkInternetConnection = async function () {

    myConnection = false;
    let connectionType = connectivityModule.getConnectionType();
    if (connectionType === connectivityModule.connectionType.none) {
        return -1; //no internet
    }
    else {
        let cAddress = await getAddress(globalVars.addressType.change, 0)
        //  let res = await httpModule.request({ url: globalVars.configURLExistAddress + cAddress, method: 'GET', timeout: 2000 }).then(response => {
        let res = await httpModule.request({ url: myApiEndPoint + globalVars.apiExistAddress + cAddress, method: 'GET', timeout: 2000 }).then(response => {
            let ll = response.content
            if (Boolean(ll) === true || Boolean(ll) === false) {
                // internet is working
                myConnection = true;
                return 1
            }
            else return 0 //connection timed out
        }, (e) => {
            return 0 //api malfunction
        })
        return res;
    }
}





var queryByAddress = async function (address, type, idx) {

    // let present = (await axios.get(globalVars.configURLExistAddress + address)).data
    let present = (await axios.get(myApiEndPoint + globalVars.apiExistAddress + address)).data

    if (!(present === true || present === false)) {

        return
    }



    if (present) {

        //TODO Update isUsed in the database
        if (type === globalVars.addressType.receive) {
            await new sqlite(globalVars.databaseObjects.name, function (err, db) {
                let qryString = "UPDATE ReceiveAddressList SET IsUsed = 1 WHERE Idx = " + idx
                db.execSQL(qryString, function (err, id) {

                })
            })
        }
        else if (type === globalVars.addressType.change) {
            await new sqlite(globalVars.databaseObjects.name, function (err, db) {
                let qryString = "UPDATE ChangeAddressList SET IsUsed = 1 WHERE Idx = " + idx
                db.execSQL(qryString, function (err, id) {

                })
            })
        }

        //get the utxos and save it in the utxo database if not present
        //this will be an asynchronous function
        //let res = (await axios.default.get(globalVars.configURLUtxo + address)).data
        let res = (await axios.default.get(myApiEndPoint + globalVars.apiUtxo + address)).data

        if (res.length > 0) {

            for (let i = 0; i < res.length; i++) {

                let utxo = res[i]


                //to be implemented



                //TODO - properly handle height and confirmations 
                let utxoPresent = isUtxoPresent(type, idx, utxo.txid, utxo.voutindex, utxo.value, 1, 1)



                //because the utxo wasn't present, it was added to the current utxo set
                /* if (!utxoPresent) {
                     val += utxo.value;
                 }*/
            }
        }
        return true;
    }
    else {
        return false;
    }
}

var setAddressUsed = function (addressType, addressIndex) {

    let qry;

    if (addressType === globalVars.addressType.change) {
        qry = "UPDATE ChangeAddressList SET IsUsed = 1 WHERE Idx = " + addressIndex
    }
    else if (addressType === globalVars.addressType.receive) {
        qry = "UPDATE ReceiveAddressList SET IsUsed = 1 WHERE Idx = " + addressIndex
    }

    new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.execSQL(qry, function (err, id) {

        })
    })

}


var checkAddressesForUtxo = async function (addressType, startIndex, numAddresses) {

    let maxUtxoIndex = 0;
    let val = 0;
    let addressArray = new Array();
    let qry;
    let utxoPresent = false;

    //TODO add exception handling

    if (addressType === globalVars.addressType.change) {
        qry = "SELECT Address FROM ChangeAddressList WHERE Idx >= " + startIndex + " AND Idx < " + (startIndex + numAddresses)
    }
    else if (addressType === globalVars.addressType.receive) {
        qry = "SELECT Address FROM ReceiveAddressList WHERE Idx >= " + startIndex + " AND Idx < " + (startIndex + numAddresses)
    }

    new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.all(qry, function (err, rows) {
            rows.forEach(function (el, idx) {
                addressArray.push({
                    idx: Number(idx + startIndex),
                    address: el[0]
                })
            })
        })
    })

    //let mUrl = "http://fabtest.info:8666/transactions?"
    //let mUrl = globalVars.configURLUtxo
    let mUrl = myApiEndPoint + globalVars.apiUtxo
    for (let i = 0; i < numAddresses; i++) {
        if (i === 0) mUrl += "address=" + addressArray[i].address
        else mUrl += "&address=" + addressArray[i].address
    }



    let response = await axios.default.get(mUrl)


    let mUtxo = new Array()
    mUtxo = response.data.result

    for (let i = 0; i < mUtxo.length; i++) {
        if (mUtxo[i].utxos.length > 0) {
            utxoPresent = true
            for (j = 0; j < mUtxo[i].utxos.length; j++) {


                let utxo = mUtxo[i].utxos[j]
                let utxoIndex = findIndex(mUtxo[i].address, i)


                maxUtxoIndex = utxoIndex;

                let utxoPresentInLocalDatabase = isUtxoPresent(addressType, utxoIndex, utxo.txid, utxo.sequence, utxo.value, utxo.block, 1)

                if (!utxoPresentInLocalDatabase) {
                    val += utxo.value
                }
            }
        }
    }

    function findIndex(address, idx) {
        if (addressArray[idx].address === address) {
            return addressArray[idx].idx
        }
        else {
            for (let i = 0; i < addressArray.length; i++) {
                if (addressArray[i].address === address) {
                    return addressArray[i].idx
                }
            }
        }
    }


    let newReceiveIndex = 0
    let newChangeIndex = 0;
    if (addressType === globalVars.addressType.receive) {

        newReceiveIndex = maxUtxoIndex + 1



        if (newReceiveIndex > currentReceiveIndex) {

            setReceiveIndex(newReceiveIndex)
        }


    }
    else if (addressType === globalVars.addressType.change) {
        newChangeIndex = maxUtxoIndex + 1
        if (newChangeIndex > currentChangeIndex) {

            setChangeIndex(newChangeIndex)
        }
    }


    return {
        value: val,
        utxoPresent: utxoPresent
    }

}

var checkAddressForExistance = async function (address) {

    // let present = (await axios.get(globalVars.configURLExistAddress + address)).data
    let present = (await axios.get(myApiEndPoint + globalVars.apiExistAddress + address)).data

    if (!(present === true || present === false)) {
        return -1
    }

    return present;


}

var getMaxAvailableAddressIndex = function (addressType) {

    let qry;
    if (addressType === globalVars.addressType.receive) {
        qry = "SELECT COUNT(*) FROM ReceiveAddressList"
    }
    else if (addressType === globalVars.addressType.change) {
        qry = "SELECT COUNT(*) FROM ChangeAddressList"
    }

    let num = 0;
    new sqlite(globalVars.databaseObjects.name, function (err, db) {
        db.get(qry, function (err, elem) {
            num = elem[0]
        })
    })

    //as this returns count, index will be one less then count

    return num;
}

//Exclusive function for restore wallet workflow
//Use it for strictly restore function only
var restoreWallet = async function (mn) {



    let maxAvailableReceiveIdx = 0;
    let maxAvailableChangeIdx = 0;

    let crawlReceiveChain = true;
    let crawlChangeChain = true;

    //take care of receive addresses
    //while 80th address is true or there is utxo available


    //todo : turn all address isused to 1 before the index(current change and current recive) after the loop finishes 

    while (crawlReceiveChain) {


        generateReceiveAddresses(mn)

        if (myApiEndPoint === undefined) {
            await setCurrentApiEndpoint()
        }


        maxAvailableReceiveIdx = getMaxAvailableAddressIndex(globalVars.addressType.receive)

        let c = await checkAddressesForUtxo(globalVars.addressType.receive, maxAvailableReceiveIdx - 100, 100)



        let ad = getAddress(globalVars.addressType.receive, maxAvailableReceiveIdx - 20)

        let is80thAddressPresent = await checkAddressForExistance(ad)

        if (is80thAddressPresent === true || c.utxoPresent) {
            crawlReceiveChain = true
        }
        else {
            crawlReceiveChain = false
        }


    }

    while (crawlChangeChain) {

        generateChangeAddresses(mn)
        maxAvailableChangeIdx = getMaxAvailableAddressIndex(globalVars.addressType.change)
        let c = await checkAddressesForUtxo(globalVars.addressType.change, maxAvailableChangeIdx - 100, 100)


        let ad = getAddress(globalVars.addressType.change, maxAvailableChangeIdx - 20)
        let is80thAddressPresent = await checkAddressForExistance(ad)

        if (is80thAddressPresent === true || c.utxoPresent) {
            crawlChangeChain = true
        }
        else {
            crawlChangeChain = false
        }


    }


    new sqlite(globalVars.databaseObjects.name, function (err, db) {

        //turn all the isused brfore currentindex in receive and change chain to one 

        //for change address table
        for (let i = 0; i < currentChangeIndex; i++) {
            db.execSQL("UPDATE ChangeAddressList SET IsUsed = 1 WHERE Idx = " + i, function (err, id) {

            })
        }

        //for receive address table
        for (let i = 0; i < currentReceiveIndex; i++) {
            db.execSQL("UPDATE ReceiveAddressList SET IsUsed = 1 WHERE Idx = " + i, function (err, id) {

            })
        }
    })

}


//this function takes the current wallet utxo and 
// validates them against the corrosponding addresses
// if any utxo for the given address is not found in the blockchain, it will be removed
var validateCurrentUtxos = async function () {

    //This function has to be rewritten, It is not performing correctly
    //get the most current set of utxos
    readUtxoFromDatabase()

    let curAddresses = new Array()

    //let mUrl = "http://fabtest.info:8666/transactions?"
    // let mUrl = globalVars.configURLUtxo;
    let mUrl = myApiEndPoint + globalVars.apiUtxo
    for (let i = 0; i < myUtxo.length; i++) {

        let ad = getAddress(myUtxo[i].Type, myUtxo[i].AddressIndex)
        if (i === 0) mUrl += "address=" + ad
        else mUrl += "&address=" + ad
    }

    let response = await axios.default.get(mUrl)
    let mUtxo = new Array()
    mUtxo = response.data.result

    let isPresent;

    for (let j = 0; j < myUtxo.length; j++) {
        isPresent = false
        for (let i = 0; i < mUtxo.length; i++) {

            for (let k = 0; k < mUtxo[i].utxos.length; k++) {
                let utxo = mUtxo[i].utxos[k]



                let localUtxo = myUtxo[j]
                if (localUtxo.TxId === utxo.txid && localUtxo.TxIdx === utxo.sequence) {
                    isPresent = true;
                    break;
                }
            }


        }
        if (!isPresent) {
            removeFromUtxo(utxo.txid, utxo.sequence)
        }

        isPresent = false;
    }

    readUtxoFromDatabase()
    updateCurrentBalance()
}

var synchronize1 = async function () {

    //validateCurrentUtxos()

    //check internet connection before proceeding further
    checkInternetConnection()
    await wait(4000);

    if (!myConnection) {
        //try to reset the api endpoint first before giving the error
        await setCurrentApiEndpoint()
        checkInternetConnection()
        await wait(3000);
    }


    if (!myConnection) {

        dialogs.alert({
            title: globalVars.messageObjects.error, message:
                globalVars.messageObjects.connectionTimeout + "\n" + globalVars.messageObjects.kindlyResetConnectionOrTryAfterSomeTime, okButtonText: globalVars.messageObjects.Ok
        })
        return false;
    }

    synchronizeActive = true;

    //TODO rethink this strategy
    dataManager.clearTable(globalVars.databaseObjects.utxoTable.name)
    myUtxo = []
    mySpentBuffer = []

    let maxAvailableReceiveIdx = 0;
    let maxAvailableChangeIdx = 0;

    let cChangeIdx = 0;
    let cReceiveIdx = 0;

    let crawlReceiveChain = true;
    let crawlChangeChain = true;

    while (crawlReceiveChain) {

        // generateReceiveAddresses(mn)

        maxAvailableReceiveIdx = getMaxAvailableAddressIndex(globalVars.addressType.receive)

        if (cReceiveIdx + 1 >= maxAvailableReceiveIdx) {
            crawlReceiveChain = false;
            break;
            //generateReceiveAddresses(mn)
        }

        let c = await checkAddressesForUtxo(globalVars.addressType.receive, cReceiveIdx, 100)

        let ad = getAddress(globalVars.addressType.receive, cReceiveIdx + 80)

        let is80thAddressPresent = await checkAddressForExistance(ad)

        if (is80thAddressPresent === true) {
            crawlReceiveChain = true
            cReceiveIdx += 100
        }
        else {
            crawlReceiveChain = false
        }
    }

    while (crawlChangeChain) {

        maxAvailableChangeIdx = getMaxAvailableAddressIndex(globalVars.addressType.change)

        if (cChangeIdx + 1 >= maxAvailableChangeIdx) {
            crawlChangeChain = false;
            break;
            // generateChangeAddresses(mn)
        }

        let c = await checkAddressesForUtxo(globalVars.addressType.change, cChangeIdx, 100)

        let ad = getAddress(globalVars.addressType.change, cChangeIdx + 80)
        let is80thAddressPresent = await checkAddressForExistance(ad)

        if (is80thAddressPresent === true) {
            crawlChangeChain = true
            cChangeIdx += 100
        }
        else {
            crawlChangeChain = false
        }
    }

    readSpentBufferFromDatabase()
    readUtxoFromDatabase()
    updateCurrentBalance()
    synchronizeActive = false;

    return true;
}

var setChangeIndex = function (cIdx) {

    currentChangeIndex = cIdx;
    if (tableExists(globalVars.databaseObjects.addressIndexTable.name)) {
        new sqlite(globalVars.databaseObjects.name, function (err, db) {
            //0 is for change index
            let qryString = "UPDATE AddressIndex SET idx = " + cIdx + " WHERE id = 0"
            db.execSQL(qryString, function (err, id) {

            })
        })
    }
}

var setReceiveIndex = function (rIdx) {

    currentReceiveIndex = rIdx;
    if (tableExists(globalVars.databaseObjects.addressIndexTable.name)) {
        new sqlite(globalVars.databaseObjects.name, function (err, db) {
            //1 is for receive index
            let qryString = "UPDATE AddressIndex SET idx = " + rIdx + " WHERE id = 1"
            db.execSQL(qryString, function (err, id) {

            })
        })
    }
}


var isUtxoPresent = function (type, addrIdx, txid, txidx, amount, height, confirmations) {
    //here, if the utxo is already present in the spentbuffer, no need to add it again
    if (mySpentBuffer.length > 0)
        for (let k = 0; k < mySpentBuffer.length; k++) {
            let tmp = mySpentBuffer[k]
            if (tmp.UtxoTxId === txid && tmp.TxIdx === txidx) {
                isPresent = true;
                return true
            }
        }


    let t = 0;
    if (myUtxo.length > 0)
        for (var k = 0; k < myUtxo.length; k++) {
            utxo = myUtxo[k]
            if (utxo.TxId === txid && utxo.TxIdx === txidx) {

                isPresent = true
                return true
            }
        }

    //if not present, add the utxo to local database
    addToUtxo(type, addrIdx, txid, txidx, amount, height, confirmations)
    updateCurrentBalance()

    return false;
}

//THIS function will not be imported. It will be called internally
var addToUtxo = function (type, addrIdx, txid, txidx, amount, height, confirmations) {

    //If this function is called, It means that new UTXO has been received.
    myUtxo.push(new utxoObj(type, addrIdx, txid, txidx, amount, height, confirmations))

    currentBalance = Number(currentBalance) + Number(amount)
    // also add to database
    new sqlite("fabPass.db", function (err, db) {
        db.execSQL("INSERT INTO UtxoTable (Type,AddressIndex,TxId,TxIdx,Amount,Height,Confirmations) VALUES (?,?,?,?,?,?,?)", [type, addrIdx, txid, txidx, amount, height, confirmations], function (err) {
            updateCurrentBalance()
        })
    })

    //change address should not be a part of transaction history
    if (type === globalVars.addressType.receive) {
        //before adding to tx history , check if it already exists
        let isPresentInTxHistory = false
        for (let i = 0; i < myTxHistory.length; i++) {
            if (myTxHistory[i].TxId === txid && myTxHistory[i].Type === type && (myTxHistory[i].Amount - amount) < 0.000001) {
                isPresentInTxHistory = true
                break;
            }
        }
        if (!isPresentInTxHistory) {
            addToTxHistory(type, getCurrentTimeStamp(), amount, 0, txid, 0, 1, "None")
        }


    }

    //increament/set the receive index or change index depending upon the "type"
    setAddressUsed(type, addrIdx)
}

var updateCurrentBalance = function () {

    currentBalance = Number(currentBalance).toFixed(8)
    dashboard.updateBalance(currentBalance)
    receivePage.updateBalance(currentBalance)
    sendPage.updateBalance(currentBalance)

}

var getCurrentTimeStamp = function () {

    let d = new Date()
    let o = d.getTimezoneOffset()
    let d1 = new Date(d - o).getTime()

    return d1;
}

var checkSpentTxConfirmations = async function () {

    if (mySpentBuffer.length === 0) return;

    let p = mySpentBuffer[0]
    // let confirmations = await httpModule.request({ url: globalVars.configURLGetTx + p.TxId + "/true", method: 'GET', timeout: 2000 }).then(response => {
    let confirmations = await httpModule.request({ url: myApiEndPoint + globalVars.apiGetTx + p.TxId + "/true", method: 'GET', timeout: 2000 }).then(response => {
        let ll = JSON.parse(response.content).confirmations
        return ll
    }, (e) => {
        return -1 //no utxo present
    })

    if (confirmations > getMaxNumCnf()) {
        removeFromSpentBuffer(p.TxId)
    }

    //TODO - figure out a way to put the transactions back in the utxo if they don't get confirmed
}

var getMaxNumCnf = function () {

    /* if(appSettings.hasKey(globalVars.appSettingsObjects.confirmationCutoff)){
         return appSettings.getNumber(globalVars.appSettingsObjects.confirmationCutoff)
     }
     else{
         //if the default cuttoff is not set
         return 30 //default is 30
     }*/

    //let default confirmations be 30
    return 30

}

var tableExists = function (tableName) {

    var tmp = false;

    new sqlite("fabPass.db", function (err, db) {
        db.get("SELECT count(*) FROM sqlite_master WHERE type = 'table' AND name = ?", [tableName], function (err, table) {

            if (err !== null) {
                tmp = false
            }
            if (Number(table) === 0) {
                tmp = false
            }

            if (Number(table) === 1) {
                tmp = true
            }
        })
    })

    return tmp
}

var getCurrentBalance = function () {
    return currentBalance;
}

var getSynchronizeActive = function () {
    return synchronizeActive
}

var getTxHistory = function () {
    return myTxHistory;
}

var deleteWallet = function () {

    //TODO - overwrite all the hashes and encryptions before deleting

    dataManager.dropAllTables();
}

var deletePin = function () {
    dataManager.dropTable(globalVars.databaseObjects.pinTable.name);
}


exports.exit = exit;
exports.getReceiveAddress = getReceiveAddress;
exports.getReceiveObject = getReceiveObject;
exports.saveMnemonics = saveMnemonics;
exports.checkInternetConnection = checkInternetConnection;
exports.getCurrentBalance = getCurrentBalance;
exports.sendFabcoins = sendFabcoins;
exports.increaseChangeIndex = increaseChangeIndex;
exports.increaseReceiveIndex = increaseReceiveIndex;
exports.init = init;
exports.getSynchronizeActive = getSynchronizeActive;
exports.verifyUserPassword = verifyUserPassword;
exports.checkSpentTxConfirmations = checkSpentTxConfirmations;
exports.getTxHistory = getTxHistory;
exports.getElementFromTable = getElementFromTable;
exports.askForPassword = askForPassword;
exports.deleteWallet = deleteWallet;
exports.savePin = savePin;
exports.verifyUserPin = verifyUserPin;
exports.deletePin = deletePin;
exports.areNewReceiveAddressesRequired = areNewReceiveAddressesRequired;
exports.areNewChangeAddressesRequired = areNewChangeAddressesRequired;
exports.generateChangeAddresses = generateChangeAddresses;
exports.generateReceiveAddresses = generateReceiveAddresses;
exports.checkLastReceiveAddressesForReceiptOfFunds = checkLastReceiveAddressesForReceiptOfFunds
exports.restoreWallet = restoreWallet;
exports.synchronize1 = synchronize1;
exports.setIdleTime = setIdleTime;
exports.setUuid = setUuid;
exports.getUuid = getUuid;
exports.verifyPasswordHash = verifyPasswordHash;
exports.sendFabcoinsTest = sendFabcoinsTest;
exports.sendFabcoinsTest1 = sendFabcoinsTest1;
exports.getSpendableBalance = getSpendableBalance;
exports.getCurrentApiEndpoint = getCurrentApiEndpoint;
exports.getUtxoCount = getUtxoCount;
exports.isPositiveNumber = isPositiveNumber;
exports.getFlatFee = getFlatFee;
exports.getFeePerVin = getFeePerVin;