//
// Oh Happy Day!
//

function readItem(_item,_asNumber) {
    let g_object;
    g_object = document.getElementById(_item + "Id");
    if (null != g_object) {
        if (_asNumber) {
            arrGlobals[_item] = Number(g_object.innerHTML);
        } else {
            arrGlobals[_item] = g_object.innerHTML;
        }
        if (arrGlobals['debugjs'] > 0) { console.log(_item + ': ' + arrGlobals[_item]); }
    } 
}

function readGlobals() {

    readItem('debugjs',true);
    readItem('siteURL',false);
    readItem('path', false);
    readItem('root', false);
    readItem('participantAddr', false);
    readItem('agentAddr', false);
    readItem('websiteAddr', false);
    readItem('GalleryAddr', false);

    // These two values are used for minting.
    readItem('agent', true);
    readItem('website', true);

    readItem('customTitle',false);
    readItem('customGallery', false);
    readItem('showHolderSection', true);

    readItem('galleryLimit',true);
    readItem('nftWidth', true);
    readItem('nftWidthMobile', true);

    // URL values
    readItem('contract', false);
    readItem('customContract', false);
    readItem('mobile', true);

    readItem('REQUEST_SCHEME', false);

    arrGlobals['visitor'] = null;
}
// NOTE: all global variables do NOT have 'Id' on the index, just the name.

// This script is the infrastructure to talk to SelNet compatible smart contracts on the
// Theta blockchain. It requires MetaMask. 
//
// If you launch your own Gallery Contract you'll want to replace the address below with your own.

document.getElementById('walletAddress').innerHTML = '<span id="connectId" style="line-height:40px">Connect</span>';
//arrGlobals['account'] = null; // TODO: Change g_account to this...
arrGlobals['BalanceTfuel'] = null; // amount of Tfuel in account
//let g_account = null;

// the participant contract address is used to simply check that the visitor
// holds a participant NFT. Does that throught balanceOf()
//arrGlobals['participantAddr'] = document.getElementById('participantAddrId').innerHTML;
arrGlobals['IsParticipant'] = false
/*
arrGlobals['ParticipantABI'] = [];
arrGlobals['ParticipantABI'].push({ "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }], "name": "balanceOf", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" });
// for talking to participant NFT
arrGlobals['ParticipantABI'].push({ "inputs": [], "name": "pricesRead", "outputs": [{ "internalType": "uint256", "name": "mintToWei", "type": "uint256" }, { "internalType": "uint256", "name": "mintWei", "type": "uint256" }, { "internalType": "uint256", "name": "burnWei", "type": "uint256" }], "stateMutability": "view", "type": "function" });
arrGlobals['ParticipantABI'].push({ "inputs": [], "name": "soulboundMint", "outputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "stateMutability": "payable", "type": "function" });
arrGlobals['ParticipantABI'].push({ "inputs": [], "name": "soulboundBurn", "outputs": [], "stateMutability": "payable", "type": "function" });
arrGlobals['ParticipantABI'].push({ "inputs": [], "name": "withdrawAll", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" });
*/
//arrGlobals['websiteAddr'] = document.getElementById('websiteAddrId').innerHTML;
/*
arrGlobals['IsWebsite'] = false
arrGlobals['WebsiteABI'] = [];
arrGlobals['WebsiteABI'].push({ "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }], "name": "balanceOf", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" });
//arrGlobals['WebsiteABI'].push({ "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }], "name": "balanceOf", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" });
// for talking to participant NFT
arrGlobals['WebsiteABI'].push({ "inputs": [], "name": "pricesRead", "outputs": [{ "internalType": "uint256", "name": "mintWei", "type": "uint256" }, { "internalType": "uint256", "name": "burnWei", "type": "uint256" }], "stateMutability": "view", "type": "function" });
arrGlobals['WebsiteABI'].push({ "inputs": [], "name": "soulboundMint", "outputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "stateMutability": "payable", "type": "function" });
arrGlobals['WebsiteABI'].push({ "inputs": [], "name": "soulboundBurn", "outputs": [], "stateMutability": "payable", "type": "function" });
arrGlobals['WebsiteABI'].push({ "inputs": [], "name": "withdrawAll", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" });
*/
// We have to check to see if the visitor is an agent in the network. To do this, we're going
// to have to resolve the wallet address in our agent contract.
//arrGlobals['agentAddr'] = document.getElementById('agentAddrId').innerHTML;
arrGlobals['IsAgent'] = false;
arrGlobals['AgentABI'] = [];
arrGlobals['AgentABI'].push({ "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }], "name": "balanceOf", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" });
arrGlobals['AgentABI'].push({ "inputs": [{ "internalType": "address", "name": "_lookup", "type": "address" }], "name": "regLookupPartner", "outputs": [{ "internalType": "uint256", "name": "agentId", "type": "uint256" }], "stateMutability": "view", "type": "function" });

//arrGlobals['DefaultGallery'] = document.getElementById("aultGalleryId").innerHTML;
arrGlobals['GalleryABI'] = [];
arrGlobals['GalleryABI'].push({ "inputs": [], "name": "countOf", "outputs": [{ "internalType": "uint256", "name": "count", "type": "uint256" }], "stateMutability": "view", "type": "function" });
arrGlobals['GalleryABI'].push({ "inputs": [{ "internalType": "uint256", "name": "_index", "type": "uint256" }], "name": "retrieve", "outputs": [{ "internalType": "address", "name": "contractAddr", "type": "address" }], "stateMutability": "view", "type": "function" });
/*
// building ABI that is used from IMetadataHash, IPay and ICount
arrGlobals['ABI'] = [];
// ERC-721
arrGlobals['ABI'].push({ "inputs": [], "name": "titled", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [], "name": "owner", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }], "name": "balanceOf", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "tokenURI", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" }], "name": "supportsInterface", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" });
// added to ERC-721 for Selene Network
arrGlobals['ABI'].push({ "inputs": [], "name": "IsSoulBound", "outputs": [{ "internalType": "bool", "name": "bSoulBound", "type": "bool" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "address", "name": "_theOwner", "type": "address" }], "name": "theTokenId", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" });

// IMetadataHashV1
arrGlobals['ABI'].push({ "inputs": [], "name": "projectDataCurrent", "outputs": [{ "internalType": "string", "name": "URI", "type": "string" }, { "internalType": "string", "name": "Project", "type": "string" }, { "internalType": "string", "name": "hash", "type": "string" }], "stateMutability": "view", "type": "function" });
// IPayV1
arrGlobals['ABI'].push({ "inputs": [], "name": "payMintPrice", "outputs": [{ "internalType": "uint256", "name": "mintWei", "type": "uint256" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "uint256", "name": "_AgentId", "type": "uint256" }, { "internalType": "uint256", "name": "_websiteId", "type": "uint256" }], "name": "payMint", "outputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "stateMutability": "payable", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [], "name": "payMintInfo", "outputs": [{ "internalType": "uint256", "name": "payAgentPennies", "type": "uint256" }, { "internalType": "uint256", "name": "payWebsitePennies", "type": "uint256" }, { "internalType": "uint256", "name": "inactivityDeadline", "type": "uint256" }, { "internalType": "uint256", "name": "minMintCount", "type": "uint256" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "address", "name": "_account", "type": "address" }], "name": "payAgentBalance", "outputs": [{ "internalType": "uint256", "name": "payBalanceWei", "type": "uint256" }, { "internalType": "uint256", "name": "payDeadline", "type": "uint256" }, { "internalType": "uint256", "name": "payCount", "type": "uint256" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [], "name": "payWithdraw", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [], "name": "payWithdrawTitled", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" });
// ICountV1
arrGlobals['ABI'].push({ "inputs": [], "name": "totalSupply", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" });
// IPennyOracleV1 (agent doesn't export this because it uses IPayV1)
arrGlobals['ABI'].push({ "inputs": [], "name": "pennyPriceTfuel", "outputs": [{ "internalType": "uint256", "name": "pennyTfuelWei", "type": "uint256" }, { "internalType": "string", "name": "priceOfTfuel", "type": "string" }], "stateMutability": "view", "type": "function" });
// for IMintV1
arrGlobals['ABI'].push({ "inputs": [], "name": "mintable", "outputs": [{ "internalType": "bool", "name": "bMintable", "type": "bool" }], "stateMutability": "view", "type": "function" });
// for IBurnV1
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "uint256", "name": "_tokenId", "type": "uint256" }], "name": "burn", "outputs": [], "stateMutability": "nonpayable", "type": "function" });




arrGlobals['ABI'].push({ "inputs": [], "name": "BURN_INDEX", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [], "name": "MINT_INDEX", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [], "name": "RENEW_INDEX", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [], "name": "SENDLOCK_INDEX", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "uint8", "name": "_index", "type": "uint8" }], "name": "payPrice", "outputs": [{ "internalType": "uint256", "name": "tfuelWei", "type": "uint256" }], "stateMutability": "view", "type": "function" });


arrGlobals['ABI'].push({ "inputs": [], "name": "memInfo", "outputs": [{ "internalType": "uint256", "name": "renewPennies", "type": "uint256" }, { "internalType": "uint256", "name": "penniesPerDay", "type": "uint256" }, { "internalType": "uint256", "name": "mintHours", "type": "uint256" }, { "internalType": "uint256", "name": "renewHours", "type": "uint256" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "uint256", "name": "_tokenId", "type": "uint256" }], "name": "memIsTimeLeft", "outputs": [{ "internalType": "bool", "name": "bActive", "type": "bool" }, { "internalType": "uint256", "name": "remainingPrePaidHours", "type": "uint256" }, { "internalType": "uint256", "name": "remainingIntrinsicHours", "type": "uint256" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "uint256", "name": "_tokenId", "type": "uint256" }], "name": "memBalance", "outputs": [{ "internalType": "uint256", "name": "BalanaceWei", "type": "uint256" }, { "internalType": "uint256", "name": "UnsettledWei", "type": "uint256" }], "stateMutability": "view", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "uint256", "name": "_tokenId", "type": "uint256" }], "name": "memWithdraw", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "uint256", "name": "_tokenId", "type": "uint256" }], "name": "memSettleTitled", "outputs": [], "stateMutability": "nonpayable", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "uint256", "name": "_tokenId", "type": "uint256" }, { "internalType": "uint256", "name": "_addHours", "type": "uint256" }], "name": "memGrantHours", "outputs": [], "stateMutability": "nonpayable", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [{ "internalType": "uint256", "name": "_tokenId", "type": "uint256" }], "name": "memRenew", "outputs": [], "stateMutability": "payable", "type": "function" });
arrGlobals['ABI'].push({ "inputs": [], "name": "memTreasuryBalance", "outputs": [{ "internalType": "uint256", "name": "BalanceWei", "type": "uint256" }], "stateMutability": "view", "type": "function" });

arrGlobals['ABI'].push({ "inputs": [{ "internalType": "address", "name": "_ownerAddr", "type": "address" }], "name": "tokenOf", "outputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "stateMutability": "view", "type": "function" });
*/


//
// The Theta network clock runs at about 521 blocks per hour.
//
arrGlobals['BlocksPerDay'] = (521 * 24);





// Want to support the last 20 projects
//let maxProjects = 20;
//let arrProjects = [{ "0": "" }, { "1": "" }, { "2": "" }, { "3": "" }, { "4": "" }, { "5": "" }, { "6": "" }, { "7": "" }, { "8": "" }, { "9": "" }, { "10": "" }, { "11": "" }, { "12": "" }, { "13": "" }, { "14": "" }, { "15": "" }, { "16": "" }, { "17": "" }, { "18": "" }, { "19": "" }];
let arrProjects = [];

// colors
let g_bkg = [];
g_bkg['active'] = '#b0f5c6';   // a green
g_bkg['inactive'] = '#f3f5ab'; // a yellow
g_bkg['standard'] = '#f0edeb'; // a grey


// can't have this load until the page is loaded... format looks like:
// (async () => { })();
document.addEventListener("DOMContentLoaded", async function (){
    console.log("In JavaScript default routine");

    readGlobals();

    // setup for projects
    for (let index = 0; index < arrGlobals['galleryLimit']; index++) {
        let item = String(index);
        arrProjects.push({ item: "" });
    }
    //console.log(arrProjects);

    if (window.ethereum) {

        await window.ethereum.request({ method: 'eth_accounts' });
        window.web3 = new Web3(window.ethereum);

        // first thing it to check chain id.
        var id = await web3.eth.getChainId();
        if (id != 361) {
            console.log("not Theta network");
            wrongNetwork();
        } else {
            console.log('correct network');

            let accounts = await web3.eth.getAccounts();
            arrGlobals['visitor'] = accounts[0];
            console.log("The Account: " + arrGlobals['visitor']);
            // if getAccounts is unsuccessful, we need to prompt user to connect MetaMask to
            // the website so that it can be used.

            if (arrGlobals['visitor'] === undefined) {
                console.log("Need to connect MetaMask");
                noConnection();

            } else {

                try {
                    await web3.eth.getBalance(arrGlobals['visitor']).then(g_accountBalanceWei => {
                        // When we get our response, we'll calculate our balance and display what we can.
                        console.log("Balance: " + g_accountBalanceWei);
                        balance = Number(BigInt(g_accountBalanceWei) / BigInt(10000000000000000)) / 100;
                        arrGlobals['BalanceTfuelWei'] = g_accountBalanceWei;
                        arrGlobals['BalanceTfuel'] = balance;
                        button = '<span style="font-size:1em;"><center><b>';
                        accountShort = arrGlobals['visitor'].substring(0, 5).concat('...', arrGlobals['visitor'].substring(38, 42));
                        button = button.concat(accountShort, ' </b><br> <img src=\'https://www.thetascan.io/tfuel_dapp.png\'><span style="font-size:0.90em;"> ', arrGlobals['BalanceTfuel'], ' </span></center></span>');
                        document.getElementById('walletAddress').innerHTML = button

                    });
                } catch (error) { }
/*
                arrGlobals['blockNumber'] = Number(await window.ethereum.request({ method: 'eth_blockNumber' }));
                //console.log('blockNumber: ' + arrGlobals['blockNumber']);
*/
                // now read the URL
                //console.log('remove readURL()');
                //readURL();

                // Want to determine if this visitor holds a participant NFT.
                let okPart = await partProject(window.web3);

                // This routine wants to determine if the visiting address is an agent in the system.
                // When filling out project agent info, we will assume that the visitor is an agent so
                // the information will be gathered if it's available.
                await visitorCheck(window.web3);

                // TODO: Here is the point where we should have a global variable object(array) that
                // can be displayed to show the display conditions (like URL info, agent and website ids, etc)
                //console.log("display page info here!");

                console.log(arrGlobals);
                //console.log(arrProjects);

                // HERE: At this point in the code, we want to know what the global state is. ie, we
                // should know if the visitor holds a participant and agent NFT. We should also know
                // if we're displaying a single contract or enumerating a gallery. 
                //console.log("now async");

                if (okPart) {
                    if (typeof arrGlobals['contract'] === 'undefined') {
                        //console.log('no contract specified.');
                        if (typeof arrGlobals['customContract'] === 'undefined') {
                            //console.log('no customContract specified either.');
                            // we have a gallery default, use it
                            document.getElementById("titleId").innerHTML = arrGlobals['customGallery'];
                            readGallery(window.web3);

                        } else {
                            // we have a custom contract, use it
                            document.getElementById("titleId").innerHTML = arrGlobals['customTitle'];
                            //console.log('cc: ' + arrGlobals['customContract'])
                            singleProject(window.web3, 0, arrGlobals['customContract']);
                        }
                    } else {
                        // we have user override, use it.
                        //console.log("Standard DSN Contract");
                        document.getElementById("titleId").innerHTML = "Standard DSN Contract";
                        singleProject(window.web3, 0, arrGlobals['contract']);
                    }
                } else {
                    console.log('display participant stuff');
                    singleProject(window.web3, 0, arrGlobals['participantAddr']);
                }
            }
        }

    } else {
        noMetamask();
    }

});


// If there was not a project override in the URL, we read the data from the Gallery contract.
async function readGallery(_web3) {
    contractGallery = new _web3.eth.Contract(arrGlobals['GalleryABI'], arrGlobals['GalleryAddr']);

    contractGallery.methods.countOf().call().then(theCount => {
        //console.log("theCount: " + theCount);

        // going to loop over this list and fetch the contract addresses. We know that 
        // the contract already has a couple items in it. Bail after five for now

        let nTop = theCount - 1;
        for (let index = 0; index < theCount; index++) {
            if (index >= arrGlobals['galleryLimit']) {
                // Only supports displaying the last 5 projects
                break;
            }
            contractGallery.methods.retrieve(nTop).call().then(theContract => {
                //console.log("contract Addr: " + theContract + " index: " + index);

                singleProject(_web3, index, theContract);
            });

            nTop--;
        }
    });
}

// This routine queries the participant NFT project to see if this address holds a token.
// Note that we serialize here because we want to know this before continuing.
async function partProject(_web3) {
    console.log('checking: ' + arrGlobals['visitor']);
    
    contractPart = new _web3.eth.Contract(arrGlobals['I']['IERC721']['ABI'], arrGlobals['participantAddr']);
    let resultCount = await contractPart.methods.balanceOf(arrGlobals['visitor']).call();

    let bRet = false;
    if (resultCount > 0) {
        console.log("Holds Participant");
        arrGlobals['IsParticipant'] = true;
        bRet = true;
    } else {
        console.log("No Participant");
        document.getElementById("noParticipantId").style.display = 'block';
    }
    showPage(-1);
    return bRet;
}

// This routine is used to check to see if the visitor to the website is part of the Agent system. 
// If the visitor is, it updates the footer to reflect this info.
async function visitorCheck(_web3) {
    contractAgent = new _web3.eth.Contract(arrGlobals['AgentABI'], arrGlobals['agentAddr']);

    // First check, does user hold an Agent NFT?

    let holdCount = await contractAgent.methods.balanceOf(arrGlobals['visitor']).call();
    if (holdCount >= 1) {

        // Founding Agents may have balances to withdraw
        arrGlobals['IsAgent'] = true;

        // A founding member may, or may not be registered. If they did, this will return their Id
        contractAgent.methods.regLookupPartner(arrGlobals['visitor']).call().then(AgentObj => {
            if (AgentObj != 0) {
                // If we have an Agent, we want to write that info on the page
                document.getElementById("visitingStatus").innerHTML = "Visitor: <i>You're part of the solution</i>, Founding Agent " + AgentObj + ".";

            } else {
                document.getElementById("visitingStatus").innerHTML = "Visitor: <i>You're part of the solution</i>, Founding Token Holder.";
            }
            showPage(-1);
        });

    } else {

        // the connected wallet does not hold an agent NFT, are they a registered agent?
        let AgentObj = await contractAgent.methods.regLookupPartner(arrGlobals['visitor']).call();

        if (AgentObj != 0) {
            document.getElementById("visitingStatus").innerHTML = "Visitor: <i>You're part of the solution</i>, Agent " + AgentObj + ".";
            arrGlobals['IsAgent'] = true;
            showPage(-1);
        }
    }

    showPage(-1);
}


function InfoError(_issue, _solution) {
    document.getElementById('InfoRowErrorId').innerHTML = _issue;
    document.getElementById('InfoRowSolutionId').innerHTML = _solution;
    document.getElementById('InfoRowId').style.display = "block"; // display the block
}

// This is only for the tfuel mint button
function handleButtonCB(_index, _state) {
    let cbFooObj = document.getElementById('buttonCallbackId');
    if (null != cbFooObj) {

        let cbFunc = cbFooObj.innerHTML;
        if ((null != cbFunc) && (cbFunc != '')) {
            let theId = "mintButtonId" + _index;

            if (null != cbFunc) {
                //console.log('call: ' + cbFunc + '("' + theId + '")');
                eval(cbFunc + '("' + theId + '","' + _state +'");');
            }
        }
    }
}

// ----------------------------------------------------------------------------------------------------
async function sha256(str) {
    var buffer = await crypto.subtle.digest("SHA-256", new TextEncoder("utf-8").encode(str));
    var hash = (Array.prototype.map.call(new Uint8Array(buffer), x => (('00' + x.toString(16)).slice(-2))).join('')).toString();
    //console.log(hash);
    return hash
}
async function sha1(str) {
    var buffer = await crypto.subtle.digest("SHA-1", new TextEncoder("utf-8").encode(str));
    var hash = (Array.prototype.map.call(new Uint8Array(buffer), x => (('00' + x.toString(16)).slice(-2))).join('')).toString();
    //console.log(hash);
    return hash
}


async function CoreHashCheck(_index, _expectedHash) {

    let projectObj = arrProjects[_index]['D']['projectObj'];
    // the data is a string
    //console.log("projectObj:");
    let strMetadata = JSON.stringify(projectObj);
    //console.log(strMetadata);

    // The 'crypto.subtle.digest() functionality is only available on https. Thus, we
    // need to validate that we're on a secure server before making this call.
    let theHash = '';
    if ((arrGlobals['REQUEST_SCHEME'] == 'https') || (arrGlobals['siteURL'] == 'http://localhost/amorstyle/') ) {
        //let theHash = await sha256(strMetadata);
        theHash = await sha1(strMetadata);
        //console.log("THE hash: ");
        //console.log(theHash);
    }

    // if this check passes, display the project
    let bRet = true;
    if (theHash == _expectedHash) {
        //console.log("hash matches");

        arrProjects[_index]["verified"] = true;
        // Modify the verify image to show that it's been verified.
        let theObj = document.getElementById("verify" + _index);
        if (null != theObj) {
            theObj.src = arrGlobals['root'] + '/images/verified_small.png';
        }

    } else {
        //console.log("hash does not match!");
        arrProjects[_index]["verified"] = false;
        bRet = false;
    }

    // Always show the projects.
    writeProject(_index);
    //console.log('Showing project');
    showPage(_index);
    return bRet;
}

async function IMetadataV1(_index) {
    arrProjects[_index]['I']['IMetadataV1']['Obj'].methods.projectDataCurrent().call().then(metadataObj => {

        // The base URL will be slash terminated in the contract. Thus, build the path out
        // here.
        let projectURL = metadataObj[0] + metadataObj[1] + ".json";
        //console.log(projectURL);
        arrProjects[_index]['D']['projectABIURL'] = metadataObj[0] + "project/" + metadataObj[1] + "_abi.json";
        //console.log(arrProjects[_index]['D']['projectABIURL']);
        //arrProjects[_index]["projectHash"] = metadataObj[2];
        //arrProjects[_index]["projectURL"] = projectURL;

        // fetch using php
        $url = arrGlobals['siteURL'] + 'dsn/?data=' + metadataObj[0] + metadataObj[1] + ".json";
        //console.log($url);
        fetch($url).then(response => {
            response.json().then(projectObj => {
                arrProjects[_index]['D']['projectObj'] = projectObj;

                CoreHashCheck(_index, metadataObj.hash);
            });
        });
    });
}

/*
function IsSendLock() external returns (bool bSendLock);
function paySendLockPrice() external view returns(uint256 sendLockWei);
function sendLockRegister(address to, uint256 tokenId) external payable;
function IsSoulBound() external returns (bool bSoulBound);
function payBurnPrice() external view returns(uint256 burnTfuelWei);
function payBurn(uint256 _tokenId) external payable ;
*/
// Handles: the above functions. 
async function IERC721ExV1(_index) {
    //console.log('Handling: IERC721ExV1');

    arrProjects[_index]['I']['IERC721ExV1']['Obj'].methods.IsSoulBound().call().then(bSoulBound => {
        //console.log('bSoulBound: ' + bSoulBound);
        arrProjects[_index]['D']['bSoulBound'] = bSoulBound;
        writeTransferState(_index);
        writeCollectableState(_index);
        showPage(_index);
    });

    arrProjects[_index]['I']['IERC721ExV1']['Obj'].methods.payBurnPrice().call().then(burnWei => {
        //console.log('burnWei: ' + burnWei);
        arrProjects[_index]['D']['prices']['burnPriceTfuelWei'] = burnWei;
        showPage(_index);
    });

    arrProjects[_index]['I']['IERC721ExV1']['Obj'].methods.IsSendLock().call().then(bSendLock => {
        console.log('bSendLock: ' + bSendLock);
        arrProjects[_index]['D']['bSendLock'] = bSendLock;
        showPage(_index);
    });

    arrProjects[_index]['I']['IERC721ExV1']['Obj'].methods.paySendLockPrice().call().then(sendLockWei => {
        //console.log('sendLockWei: ' + sendLockWei);
        arrProjects[_index]['D']['prices']['sendlockPriceTfuelWei'] = sendLockWei;
        showPage(_index);
    });
}

async function ICore(_index) {
    arrProjects[_index]['I']['ICore']['Obj'].methods.IsSoulBound().call().then(bSoulBound => {
        //arrProjects[_index]['D']['bSoulBound'] = false;
        //arrProjects[_index]['D']['bSingle'] = false;
        if (bSoulBound) {
            arrProjects[_index]['D']['bSoulBound'] = true;
            arrProjects[_index]['D']['bSingle'] = true;
            //
            // When the Selene Network NFT is soulBound, there can only be one minted.
            // Thus, rather than checking the balance, we're going to see if it
            // reports a token Id. If it does, we'll show the burn functinality and
            // have the token id for burning.
            //

            arrProjects[_index]['D']['theTokenId'] = parseInt(0);
            //console.log('calling: theTokenId()');
            arrProjects[_index]['I']['ICore']['Obj'].methods.theTokenId(arrGlobals['visitor']).call().then(tokenId => {
                //console.log('handling: theTokenId()');
                arrProjects[_index]['D']['theTokenId'] = parseInt(tokenId);
                showPage(_index);
            });
        }
        writeTransferState(_index);
        writeCollectableState(_index);
    });
}

// Either single or multiple. 
function writeCollectableState(_index) {
    let bSingle = arrProjects[_index]['D']['bSingle'];
    let bSoulBound = arrProjects[_index]['D']['bSoulBound'];

    const elem = document.getElementById("projCollStateId" + _index);
    if (elem != null) {

        if (bSoulBound) {
            document.getElementById("projCollStateId" + _index).innerHTML = "Single";
        } else {
            if (bSingle) {
                document.getElementById("projCollStateId" + _index).innerHTML = "Single";
            } else {
                document.getElementById("projCollStateId" + _index).innerHTML = "Multiple";
            }
        }
    }
}

// 
// Either soulbound or transferable. If bSoulBound is true, the token is non-transferable.
//
function writeTransferState(_index) {
    let bSoulBound = arrProjects[_index]['D']['bSoulBound'];

    const elem = document.getElementById("projTrxnStateId" + _index);
    if (elem != null) {

        if (bSoulBound) {
            document.getElementById("projTrxnStateId" + _index).innerHTML = "Soulbound";
        } else {
            document.getElementById("projTrxnStateId" + _index).innerHTML = "Transferable";
        }
    }
}

function writeCount(_index) {
    let totalNFTs = arrProjects[_index]['D']['totalNFTs'];

    const elem = document.getElementById("projTotalNFTsId" + _index);
    if (elem != null) {
        document.getElementById("projTotalNFTsId" + _index).innerHTML = totalNFTs;
    }
}

async function ICountV1(_index) {
    arrProjects[_index]['I']['ICountV1']['Obj'].methods.totalSupply().call().then(totalNFTs => {
        arrProjects[_index]['D']['totalNFTs'] = totalNFTs;
        writeCount(_index);
        showPage(_index);
    });
}

// Used by the mint toggle functionality and the visitor balance check
function writeDisableTfuel(_index) {
    let theObj = document.getElementById('mintButtonId' + _index);
    if (null != theObj) {
        theObj.disabled = true;
        theObj.style.opacity = '0.35';
    }
}
// used by the visitor balance check
function writeDisableBurn(_index) {
    let theObj = document.getElementById('burnButtonId' + _index);
    if (null != theObj) {
        theObj.disabled = true;
        theObj.style.opacity = '0.35';
    }
}

async function IMintV1(_index) {
    // If the NFT is not mintable, we need to disable the mint button
    arrProjects[_index]['I']['IMintV1']['Obj'].methods.mintable().call().then(bMintable => {
        arrProjects[_index]['W']['mintToggle'] = true;
        if (!bMintable) {
            // disable the button
            //console.log('disable the tfuel button');
            arrProjects[_index]['W']['mintState'] = false;
            writeDisableTfuel(_index);
            handleButtonCB(_index, 'disabled');
        } else {
            //console.log('enable the tfuel button');
            arrProjects[_index]['W']['mintState'] = true;
            handleButtonCB(_index, 'enabled');
        }
    });
} 

//
// This is the handler for the toggling of minting via IMintV1.
//
// Parameters:
// "inputs": [] 
// "name": "ownerLockToggle", 
// Returns:
// "outputs": []
async function mintToggle(_index) {
    console.log("In mintToggle handler: " + _index);
    console.log('HANDLER: ownerLockToggle() TEST');

    if (await IsCorrectNetwork()) {

        let currentGasPrice = await web3.eth.getGasPrice();

        let dataObj = document.getElementById("AdminTrxnId" + _index);
        document.body.style.cursor = 'wait';
        try {
            //console.log('TODO: Bring wallet call back');

            await arrProjects[_index]['I']['IMintV1']['Obj'].methods.ownerLockToggle().send({
                from: arrGlobals['visitor'],
                gasPrice: currentGasPrice
            }).on('error', (error) => {
                console.log(error);
            }).then(tx => {
                //console.log(tx);
                var transactionRef = '<a href="http://www.thetascan.io/hash/?hash=' + tx.transactionHash + '" target="_blank">Trxn Link & Hash</a>';
                console.log(transactionRef);
                dataObj.innerHTML = transactionRef;
            });

        } catch (error) {
            console.log('try&catch error');
            console.log(error.message);
            dataObj.innerHTML = error.message;
        }
        dataObj.style.display = "block";
        document.body.style.cursor = 'default';
    }
}


// Before we can show prices, we need to get the price of tfuel.
async function IPennyOracleV1(_index) {
    arrProjects[_index]['D']['pennyPriceObj'] = null;
    arrProjects[_index]['I']['IPennyOracleV1']['Obj'].methods.pennyPriceTfuel().call().then(pennyPriceObj => {

        arrProjects[_index]['D']['pennyPriceObj'] = pennyPriceObj;
        showPage(_index);
    });
}

async function ISelNet(_index) {
    arrProjects[_index]["D"]["owner"] = null;
    arrProjects[_index]["D"]["titled"] = null;

    arrProjects[_index]['I']['ISelNetV1']['Obj'].methods.owner().call().then(ownerAddr => {
        arrProjects[_index]["D"]["owner"] = ownerAddr;
        writeOwnerLink(_index);

        // if the owner equals the visitor, we set some globals
        if (ownerAddr.toLowerCase() == arrGlobals['visitor'].toLowerCase()) {
            //console.log("ISelNet: visitor is owner");
            arrProjects[_index]['W']['visitorIsContractOwner'] = true;
            arrProjects[_index]['W']['visitorIsContractAdmin'] = true;
        }
        showPage(_index);
    });
    arrProjects[_index]['I']['ISelNetV1']['Obj'].methods.titled().call().then(titledAddr => {
        arrProjects[_index]["D"]["titled"] = titledAddr;
        //console.log('Titled: ' + titledAddr);
        writeTitledLink(_index);

        // if the titled equals the visitor, we set some globals
        if (titledAddr.toLowerCase() == arrGlobals['visitor'].toLowerCase()) {
            //console.log("ISelNet: visitor is owner");
            arrProjects[_index]['W']['visitorIsContractTitled'] = true;
            arrProjects[_index]['W']['visitorIsContractAdmin'] = true;
        }
        showPage(_index);
    });

}

//
// For the Participant and Website NFTs, we need to get the balance on the contract
// to display.
//
//function balanceOf(address owner) external view returns (uint256 balance);
//function ownerOf(uint256 tokenId) external view returns (address owner);
//function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
//function safeTransferFrom(address from, address to, uint256 tokenId) external;
//function transferFrom(address from, address to, uint256 tokenId) external;
//function approve(address to, uint256 tokenId) external;
//function setApprovalForAll(address operator, bool approved) external;
//function getApproved(uint256 tokenId) external view returns (address operator);
//function isApprovedForAll(address owner, address operator) external view returns (bool);
//
// Only function that we're interested in here is the balanceOf() we want to know
// how many of the tokens the visitor holds.
async function IERC721(_index) {
/*
    // these first two get the balance held on the Participant and website contracts.
    if (arrProjects[_index]["contractAddr"].toLowerCase() == arrGlobals['participantAddr'].toLowerCase()) {
        arrProjects[_index]['I']['IERC721']['Obj'].methods.balanceOf(arrProjects[_index]["contractAddr"]).call().then(contractBal => {
            //console.log("contractBal:" + contractBal);
            arrProjects[_index]['D']['contractBalance'] = contractBal;
            writeContractBalance(_index);
        });
    } else if (arrProjects[_index]["contractAddr"].toLowerCase() == arrGlobals['websiteAddr'].toLowerCase()) {
        arrProjects[_index]['I']['IERC721']['Obj'].methods.balanceOf(arrProjects[_index]["contractAddr"]).call().then(contractBal => {
            //console.log("contractBal:" + contractBal);
            arrProjects[_index]['D']['contractBalance'] = contractBal;
            writeContractBalance(_index);
        });
    }
*/
    // This gets the visitor balance.
    arrProjects[_index]['I']['IERC721']['Obj'].methods.balanceOf(arrGlobals['visitor']).call().then(balance => {
        //console.log("balanceOf: " + balance);
        arrProjects[_index]['D']['balanceOf'] = balance;
        //contractBalance(_index);
    });

}

// if the visitor is not the titled, build a link to the titled wallet
function writeTitledLink(_index) {
    let theTitled = arrProjects[_index]["D"]["titled"];

    let theAddr = shortText(theTitled);
    let theLink = arrGlobals['root'] + 'wallet/?account=' + theTitled;
    let theHTML = 'Project Titled: <a href="' + theLink + '" target=_self>' + theAddr + '</a>';
    let theObj = document.getElementById("titledLinkLinkId" + _index);
    if (null != theObj) {
        theObj.innerHTML = theHTML;
        //console.log('wroteTitledLink');
    }
}

// if the visitor is not the owner, build a link to the owner wallet
function writeOwnerLink(_index) {
    let theOwner = arrProjects[_index]["D"]["owner"];

    let theAddr = shortText(theOwner);
    let theLink = arrGlobals['root'] + 'wallet/?account=' + theOwner;
    let theHTML = 'Project Owner: <a href="' + theLink + '" target=_self>' + theAddr + '</a>';
    let theObj = document.getElementById("ownerLinkLinkId" + _index);
    if (null != theObj) {
        theObj.innerHTML = theHTML;
    }
}


// Handles: IsSoulbound(), penniesRead(), penniesSet(), pricesRead(), theTokenId()
// In the Agent contract IsSoulbound() returns false and theTokenId() is not supported
// because it's not soulbound. The only function that we need at load is pricesRead().
//
// For now, we want to get the balance and see if the visitor is registered. If they
// are both, they are founding partner. If they are registered, they are agent.
async function IAgentV1(_index) {
    console.log("IAgentV1 not implimented yet");
    console.log("IAgentV1 NEED pricesRead()");

    // TODO: Build Agent functionality into project!
}

//
// This routine queries for supported interfaces on the contract.
// TODO: This should be converted into a tree. If a newer interest is 
// a superset of a bunch of older interfaces, one call gets what is 
// needed rather than a bunch.
//
async function queryInterfaces(_web3, _index, _contractAddr) {

    // I - All the interfaces supported by the contract are placed in this branch. If the 
    //     contract supports an interface, this code will call the functions that would
    //     provide information.
    arrProjects[_index]['I'] = [];

    // D - When handling the response from the contract, the handler will save the gathered
    //     data items to this location. 
    arrProjects[_index]['D'] = [];
    // This will hold all the different prices for button handlers
    // mintPriceTfuel & mintPriceTfuelWei
    // burnPriceTfuel & burnPriceTfuelWei
    // sendlockPriceTfuel & sendlockPriceTfuelWei
    // renewPriceTfuel & renewPriceTfuelWei
    arrProjects[_index]['D']['prices'] = [];


    // W - When handling the display, if the required data is available, the code will
    //     use it to write to the page what is needed for each section. Once a section is 
    //     complete, this section will be used. ie., ...['W']['Project'] = true;
    arrProjects[_index]['W'] = [];
    //
    // Project   - Requires: IMetadataV1, Optioinal: ICountV1
    // Mint      - Requires: IPennyOracleV1 & (IPayV1 or ISelNet), Optional: IMintV1
    // Burn      - Requires: IPennyOracleV1 & (IPayV1 or ISelNet)
    // Custom    - 
    // Membership- Requires: IMemV1
    // Holdings  - 
    // Agent     - Requires: 
    // Admin     - Requires: ISelNet
    //

    // Every project gets some default settings
    arrProjects[_index]['D']['bSingle'] = false;
    writeTransferState(_index);
    arrProjects[_index]['D']['bSoulBound'] = false;
    writeCollectableState(_index);


    let theContract = new _web3.eth.Contract(arrGlobals['I']['IERC165']['ABI'], _contractAddr);
    if (theContract != null) {

        // Every Selene Network contract supports ISelNet (owner and titled)
        arrProjects[_index]['I']['ISelNetV1'] = [];
        arrProjects[_index]['I']['ISelNetV1']['supported'] = true;
        arrProjects[_index]['I']['ISelNetV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['ISelNetV1']['ABI'], _contractAddr);
        if (null != arrProjects[_index]['I']['ISelNetV1']['Obj']) {
            ISelNet(_index);
        }

        // Note that every contract needs to export the penny oracle functions. This is what is used to 
        // help determine what the prices need to be for interacting with the contract.
        arrProjects[_index]['I']['IPennyOracleV1'] = [];
        arrProjects[_index]['I']['IPennyOracleV1']['supported'] = true;
        arrProjects[_index]['I']['IPennyOracleV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IPennyOracleV1']['ABI'], _contractAddr);
        if (null != arrProjects[_index]['I']['IPennyOracleV1']['Obj']) {
            IPennyOracleV1(_index);
        }

        // Check for additional interfaces.

        arrProjects[_index]['I']['IERC721'] = [];
        theContract.methods.supportsInterface(arrGlobals['I']['IERC721']['id']).call().then(bIERC721 => {
            arrProjects[_index]['I']['IERC721']['supported'] = bIERC721;
            arrProjects[_index]['I']['IERC721']['Obj'] = null;
            if (bIERC721) {
                arrProjects[_index]['I']['IERC721']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IERC721']['ABI'], _contractAddr);
                if (null != arrProjects[_index]['I']['IERC721']['Obj']) {
                    if (arrGlobals['debugjs']) {
                        console.log('calling IERC721');
                    }
                    IERC721(_index); // used to get balanceOf() on Participant and Website contracts.
                }
            }
        });

        arrProjects[_index]['I']['IERC721Metadata'] = [];
        theContract.methods.supportsInterface(arrGlobals['I']['IERC721Metadata']['id']).call().then(bIERC721Metadata => {
            arrProjects[_index]['I']['IERC721Metadata']['supported'] = bIERC721Metadata;
            arrProjects[_index]['I']['IERC721Metadata']['Obj'] = null;
            if (bIERC721Metadata) {
                arrProjects[_index]['I']['IERC721Metadata']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IERC721Metadata']['ABI'], _contractAddr);
                if (null != arrProjects[_index]['I']['IERC721Metadata']['Obj']) {

                    // Note that this routine uses the tokenURI function on the contract in order
                    // to display the tokens that the visitor holds. Thus, can't call it until
                    // we've allocated the object.
                    holdsNFTs(_index);
                    showPage(_index);
                }
            } else {
                console.log('does not support IERC721Metadata');
            }

        });

        arrProjects[_index]['I']['IMetadataV1'] = [];
        theContract.methods.supportsInterface(arrGlobals['I']['IMetadataV1']['id']).call().then(bIMetadataV1 => {
            arrProjects[_index]['I']['IMetadataV1']['supported'] = bIMetadataV1;
            arrProjects[_index]['I']['IMetadataV1']['Obj'] = null;
            if (bIMetadataV1) {
                arrProjects[_index]['I']['IMetadataV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IMetadataV1']['ABI'], _contractAddr);
                if (null != arrProjects[_index]['I']['IMetadataV1']['Obj']) {
                    if (arrGlobals['debugjs']) {
                        console.log('calling IMetadataV1');
                    }
                    IMetadataV1(_index);
                }
            }
        });

        arrProjects[_index]['I']['ICountV1'] = [];
        theContract.methods.supportsInterface(arrGlobals['I']['ICountV1']['id']).call().then(bICountV1 => {
            arrProjects[_index]['I']['ICountV1']['supported'] = bICountV1;
            arrProjects[_index]['I']['ICountV1']['Obj'] = null;
            if (bICountV1) {
                arrProjects[_index]['I']['ICountV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['ICountV1']['ABI'], _contractAddr);
                if (null != arrProjects[_index]['I']['ICountV1']['Obj']) {
                    if (arrGlobals['debugjs']) {
                        console.log('calling ICountV1');
                    }
                    ICountV1(_index);
                }
            }
        });
        arrProjects[_index]['I']['IPayV1'] = [];
        theContract.methods.supportsInterface(arrGlobals['I']['IPayV1']['id']).call().then(bIPayV1 => {
            arrProjects[_index]['I']['IPayV1']['supported'] = bIPayV1;
            arrProjects[_index]['I']['IPayV1']['Obj'] = null;
            if (bIPayV1) {
                arrProjects[_index]['I']['IPayV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IPayV1']['ABI'], _contractAddr);
                if (null != arrProjects[_index]['I']['IPayV1']['Obj']) {
                    if (arrGlobals['debugjs']) {
                        console.log('calling IPayV1');
                    }
                    IPayV1(_index);
                }
            } 
        });

        arrProjects[_index]['I']['IMintV1'] = [];
        theContract.methods.supportsInterface(arrGlobals['I']['IMintV1']['id']).call().then(bIMintV1 => {
            arrProjects[_index]['I']['IMintV1']['supported'] = bIMintV1;
            arrProjects[_index]['I']['IMintV1']['Obj'] = null;
            if (bIMintV1) {
                arrProjects[_index]['I']['IMintV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IMintV1']['ABI'], _contractAddr);
                if (null != arrProjects[_index]['I']['IMintV1']['Obj']) {
                    if (arrGlobals['debugjs']) {
                        console.log('calling IMintV1');
                    }
                    IMintV1(_index);
                }
            }
            // TODO: Test custom code handleButtonCB() functionality.
/*
            else {
                console.log("TODO: test custom contract!");
                console.log('no support for IMintV1');
                // If the project has registered a callback, give them a chance.
                // Give project chance to overright the contract
                handleButtonCB(_index, 'enabled');
            }
*/
        });

        arrProjects[_index]['I']['IBurnV1'] = [];
        theContract.methods.supportsInterface(arrGlobals['I']['IBurnV1']['id']).call().then(bIBurnV1 => {
            arrProjects[_index]['I']['IBurnV1']['supported'] = bIBurnV1;
            arrProjects[_index]['I']['IBurnV1']['Obj'] = null;
            if (bIBurnV1) {
                arrProjects[_index]['I']['IBurnV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IBurnV1']['ABI'], _contractAddr);
                if (null != arrProjects[_index]['I']['IBurnV1']['Obj']) {
                    //IBurnV1(_index);
                    arrProjects[_index]["IBurnV1"] = 'enabled';
                }
            }
        });

        arrProjects[_index]['I']['IMemV1'] = [];
        theContract.methods.supportsInterface(arrGlobals['I']['IMemV1']['id']).call().then(bIMemV1 => {
            arrProjects[_index]['I']['IMemV1']['supported'] = bIMemV1;
            arrProjects[_index]['I']['IMemV1']['Obj'] = null;
            if (bIMemV1) {
                arrProjects[_index]['I']['IMemV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IMemV1']['ABI'], _contractAddr);
                if (null != arrProjects[_index]['I']['IMemV1']['Obj']) {
                    if (arrGlobals['debugjs']) {
                        console.log('calling IMemV1');
                    }
                    IMemV1(_index);
                }
            } 
        });

        arrProjects[_index]['I']['IPayV2'] = [];
        theContract.methods.supportsInterface(arrGlobals['I']['IPayV2']['id']).call().then(bIPayV2 => {
            arrProjects[_index]['I']['IPayV2']['supported'] = bIPayV2;
            arrProjects[_index]['I']['IPayV2']['Obj'] = null;
            if (bIPayV2) {
                arrProjects[_index]['I']['IPayV2']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IPayV2']['ABI'], _contractAddr);
                if (null != arrProjects[_index]['I']['IPayV2']['Obj']) {
                    if (arrGlobals['debugjs']) {
                        console.log('calling IPayV2');
                    }
                    IPayV2(_index);
                }
            }
        });

        arrProjects[_index]['I']['IERC721ExV1'] = [];
        theContract.methods.supportsInterface(arrGlobals['I']['IERC721ExV1']['id']).call().then(bIERC721ExV1 => {
            arrProjects[_index]['I']['IERC721ExV1']['supported'] = bIERC721ExV1;
            arrProjects[_index]['I']['IERC721ExV1']['Obj'] = null;
            if (bIERC721ExV1) {
                arrProjects[_index]['I']['IERC721ExV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IERC721ExV1']['ABI'], _contractAddr);
                if (null != arrProjects[_index]['I']['IERC721ExV1']['Obj']) {
                    if (arrGlobals['debugjs']) {
                        console.log('calling IERC721ExV1');
                    }
                    IERC721ExV1(_index);
                }
            }
        });

        arrProjects[_index]['I']['IERC721ExV1M'] = [];
        theContract.methods.supportsInterface(arrGlobals['I']['IERC721ExV1M']['id']).call().then(bIERC721ExV1M => {
            arrProjects[_index]['I']['IERC721ExV1M']['supported'] = bIERC721ExV1M;
            arrProjects[_index]['I']['IERC721ExV1M']['Obj'] = null;
            //arrProjects[_index]['I']['IERC721ExV1M'] = bIERC721ExV1M;
            if (bIERC721ExV1M) {
                arrProjects[_index]['I']['IERC721ExV1M']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IERC721ExV1M']['ABI'], _contractAddr);
                if (null != arrProjects[_index]['I']['IERC721ExV1M']['Obj']) {
                    if (arrGlobals['debugjs']) {
                        console.log('calling IERC721ExV1M');
                    }
                    IERC721ExV1M(_index);
                }
            }
        });
    }

    // Special case some old contracts
    arrProjects[_index]['I']['ICore'] = [];
    arrProjects[_index]['I']['IWebV1'] = [];
    arrProjects[_index]['I']['IPartV1'] = [];
    arrProjects[_index]['I']['IAgentV1'] = [];

    arrProjects[_index]['I']['ICore']['supported'] = false;
    arrProjects[_index]['I']['IWebV1']['supported'] = false;
    arrProjects[_index]['I']['IPartV1']['supported'] = false;
    arrProjects[_index]['I']['IAgentV1']['supported'] = false;
    arrProjects[_index]['I']['ICore']['Obj'] = null;
    arrProjects[_index]['I']['IWebV1']['Obj'] = null;
    arrProjects[_index]['I']['IPartV1']['Obj'] = null;
    arrProjects[_index]['I']['IAgentV1']['Obj'] = null;

    // Now, override if we have a known contract address
    //console.log("contract: " + _contractAddr);
    //console.log("agentAddr: " + arrGlobals['agentAddr']);
    if (_contractAddr.toLowerCase() === arrGlobals['websiteAddr'].toLowerCase()) {
        arrProjects[_index]['I']['ICore']['supported'] = true;
        arrProjects[_index]['I']['IWebV1']['supported'] = true;

        arrProjects[_index]['I']['ICore']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['ICore']['ABI'], _contractAddr);
        if (null != arrProjects[_index]['I']['ICore']['Obj']) {
            if (arrGlobals['debugjs']) {
                console.log('calling ICore');
            }
            ICore(_index);
        }

        arrProjects[_index]['I']['IWebV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IWebV1']['ABI'], _contractAddr);
        if (null != arrProjects[_index]['I']['IWebV1']['Obj']) {
            if (arrGlobals['debugjs']) {
                console.log('calling IWebV1');
            }
            IWebV1(_index);
        }

    } else if (_contractAddr.toLowerCase() === arrGlobals['participantAddr'].toLowerCase()) {
        arrProjects[_index]['I']['ICore']['supported'] = true;
        arrProjects[_index]['I']['IPartV1']['supported'] = true;

        arrProjects[_index]['I']['ICore']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['ICore']['ABI'], _contractAddr);
        if (null != arrProjects[_index]['I']['ICore']) {
            if (arrGlobals['debugjs']) {
                console.log('calling ICore');
            }
            ICore(_index);
        }

        arrProjects[_index]['I']['IPartV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IPartV1']['ABI'], _contractAddr);
        if (null != arrProjects[_index]['I']['IPartV1']['Obj']) {
            if (arrGlobals['debugjs']) {
                console.log('calling IPartV1');
            }
            IPartV1(_index);
        }

    } else if (_contractAddr.toLowerCase() === arrGlobals['agentAddr'].toLowerCase()) {
        arrProjects[_index]['I']['IAgentV1']['supported'] = true;

        arrProjects[_index]['I']['IAgentV1']['Obj'] = new _web3.eth.Contract(arrGlobals['I']['IAgentV1']['ABI'], _contractAddr);
        if (null != arrProjects[_index]['I']['IAgentV1']['Obj']) {
            console.log('calling IAgentV1');
            IAgentV1(_index);
        }
    } 
    return true;
}


/*
The Participant contract supports:
IERC165, IERC721, IERCy21Metadata, 
IsSoulBound() - will return true
penniesRead()
penniesSet()
pricesRead() - different than website
soulboundBurn()
soulboundMint()
soulboundMintTo()
theTokenId() - works
withdrawAll()

The website contract supports:
IsSoulBound() - will return true
penniesRead()
penniesSet()
pricesRead() - different than participant
soulboundBurn()
soulboundMint()
soulboundMintTo()
theTokenId() - works
withdrawAll()

The agent V1 contract - also supports IPayV1
IsSoulBound() - will return false
penniesRead()
penniesSet()
pricesRead()
theTokenId() - but it's not soulbound

// Custom code will add these...
isAgentId()
regAgent()
regExtend()
regLookupPartner()
regResolve()
regSlotsRemaining()
regTotalAgents()
sendLockRegister()

interface IRegisterV1 {
    //function regPrice() external view returns(uint256 priceWei);
    function regAgent(uint256 _agentId, address _partnerAddr) external payable returns(uint256 partnerId);
    function regLookupPartner(address _lookupAddr) external view returns(uint256 agentId);
    function regResolve(uint256 _agentId) external view returns(address founderAddr, address partnerAddr);
    function regSlotsRemaining(uint256 _agentId) external view returns(uint256 count);
}

*/

//
// This routine is called if the contract address is provided on the URL. It will fetch
// the project file associated with the project
//
async function singleProject(_web3, _index, _contractAddr) {

    arrProjects[_index]["contractAddr"] = _contractAddr;
    console.log(arrProjects[_index]);
    contractBalanace(_index);

    let bDone = await queryInterfaces(_web3, _index, _contractAddr);

    // build a contract with the functions that we're going to call.
    //console.log(arrGlobals['ABI']);
/*
    contract = new _web3.eth.Contract(arrGlobals['ABI'], _contractAddr);
    arrProjects[_index]["contractObj"] = contract;
*/
    //console.log(_index+ " contractObj created");
    //console.log(arrProjects);

/*
    // Now we need to special case the participant and website NFTs. They both 
    // overrode the mint functionality in order to provide burn functionality.
    if (arrProjects[_index]["contractAddr"] == arrGlobals['participantAddr']) {

        //console.log('handle special part');
        // This reads the prices for the index object.
        handleSpecial(_index, true);
    } else if (arrProjects[_index]["contractAddr"] == arrGlobals['websiteAddr']) {

        //console.log('handle special website');
        // This reads the prices for the index object.
        handleSpecial(_index, false);
    }
*/

//    showPage(_index);
}

async function contractBalanace(_index) {
    // This gets the balance on the contract
    window.web3.eth.getBalance(arrProjects[_index]["contractAddr"]).then(ownerBalance => {
        //console.log('contractBalance: ' + ownerBalance);
        arrProjects[_index]['D']['contractBalance'] = ownerBalance;
        writeContractBalance(_index);
    });
}

async function displayCopy(_index) {
    let theObj = document.getElementById("projectShare" + _index);
    if (null != theObj) {
        theObj.innerHTML = 'Copied';
        await delay(1000);
        theObj.innerHTML = '';
    }
}

async function copyURI(_index) {

    //console.log('_index: '+ _index);
    // share link
    let shareLink = window.location.origin + window.location.pathname + "?contract=" + arrProjects[_index]["contractAddr"];
    //console.log(shareLink);

    navigator.clipboard.writeText(shareLink).then(() => {
        //console.log('clipboard successfully set');
        displayCopy(_index);

    }, () => {
        //console.log('clipboard write failed');
    });

}

async function displayABI(_index) {
    //console.log('flash the image here');
    let theSrc = document.getElementById("projABIimg" + _index).src;
    let theCopiedSrc = theSrc.replace('.png', '_copied.png');
    document.getElementById("projABIimg" + _index).src = theCopiedSrc;
    await delay(1000);
    document.getElementById("projABIimg" + _index).src = theSrc;
}

async function copyABI(_index) {

    let shareLink = document.getElementById("projectABI" + _index).innerHTML;
    //console.log(shareLink);

    $url = arrGlobals['siteURL'] + 'dsn/?data=' + shareLink;
    //console.log($url);
    fetch($url).then(response => {
        response.json().then(projectObj => {
            //console.log(JSON.stringify(projectObj));
            navigator.clipboard.writeText(JSON.stringify(projectObj)).then(() => {
                //console.log('clipboard successfully set');
                displayABI(_index);

            }, () => {
                //console.log('clipboard write failed');
            });
        });
    });
}

// This routine calls payAgentBalance() which is only found on the IPayV1 interface.
// If the contract doesn't support that, we don't get the balance for the address.
//
// AgentBalance calls through the IPayV1 interface to get the balance of the _Addr.
// This routine should only be called if the IPayV1 interface is supported.
// 
async function AgentBalance(_index, _Addr, _name) {
    //console.log('AgentBalance: ' + _index);
    //console.log('have IPayV1, getting balance');
    arrProjects[_index]['D'][_name] = 0;
    arrProjects[_index]['I']['IPayV1']['Obj'].methods.payAgentBalance(_Addr).call().then(bal => {
        //arrProjects[_index][_name] = null;
        arrProjects[_index]['D'][_name] = bal;
        //console.log('Have ' + _name + ' now: ' + bal);

        // Finally have the data.
        arrProjects[_index]['W'][_name] = true;

        showPage(_index);
    });
}

// write the contract balance to the page.
//
// This function should only be called when processing a participant or website contract.
//
// The handler should be withdrawAll() and will only answer owner or titled.
function writeContractBalance(_index) {

    //console.log('writeContractBalance: ' + _index);
    let contractBal = arrProjects[_index]['D']['contractBalance'];

    let theBalance = Number(BigInt(contractBal) / BigInt(10000000000000000)) / 100;
    const elem = document.getElementById("ownerBalanceId" + _index);
    if (elem != null) {
        elem.innerHTML = "Current Contract Balance: " + theBalance + " tfuel.";
        if (arrGlobals['debugjs']) {
            console.log(elem.innerHTML);
        }

        let dataItem = document.getElementById("ownerButtonLegacyId" + _index);
        if (contractBal > 0) {
            dataItem.disabled = false;
            //console.log('Added EventListener: withdrawAllHdr()');
            // We only want to set this handler once
            dataItem.addEventListener('click', function () { withdrawAllHdr(_index) }, false);
        }
        arrProjects[_index]['W']['ownerWithdrawLegacy'] = true;
    }
}


//TODO: Should only be called once
function writeOwnerPayBalance(_index) {
    // only write this if we have an owner balance.
    console.log('writeOwnerPayBalance: ' + _index);

    let agentPayObj = arrProjects[_index]['D']['agentPayObj'];

    let theBalance = Number(BigInt(agentPayObj.payBalanceWei) / BigInt(10000000000000000)) / 100;
    const elem = document.getElementById("ownerBalanceId" + _index);
    if (elem != null) {
        elem.innerHTML = "Current Available Balance: " + theBalance + " tfuel.";
        console.log(elem.innerHTML);

        let dataItem = document.getElementById("ownerButtonIPayV1Id" + _index);
        if (null != dataItem) {
            if (agentPayObj.payBalanceWei > 0) {
                dataItem.disabled = false;
                // We only want to set this handler once
                dataItem.addEventListener('click', function () { payWithdrawTitled(_index) }, false);
            }
        }
        arrProjects[_index]['W']['ownerWithdrawIPayV1'] = true;
    }
}


// returns a string less then 40 characters long
function shortText(_theStr) {
    if (arrGlobals['mobile'] == 1) {
        // want short names on mobile.
        let theText = _theStr;
        let theEnd = (_theStr.length);
        let linktxt = _theStr;
        if (theEnd > 34) {
            linktxt = theText.substring(0, 24) + '...' + theText.substring((theEnd - 8), theEnd);
        }
        return linktxt;
    }
    return _theStr;
}

//
// The name, description and image are required. Everything else should be
// checked against the JSON data.
//
function writeProject(_index) {
    let theObj = null;
    let projectObj = arrProjects[_index]['D']['projectObj'];
    //console.log('index: '+ _index);
    //console.log(projectObj);
    const elem = document.getElementById("projectName" + _index);
    if (elem != null) {

        // required: name
        elem.innerHTML = projectObj.name;

        // optional: whitepaper
        if (typeof projectObj['whitepaper'] != 'undefined' && projectObj['whitepaper'] != null) {
            // have whitepaper keyword
            let whitePaperLink = "Whitepaper: <a href='" + projectObj.whitepaper + "' >" + shortText(projectObj.whitepaper) + "</a>";
            //document.getElementById("projectWhitepaper" + _index).innerHTML = whitePaperLink;
            theObj = document.getElementById("projectWhitepaper" + _index);
            if (null != theObj) {
                theObj.innerHTML = whitePaperLink;
                theObj.style.display = "block";
            }
        }

        // optional: description
        if (typeof projectObj['description'] != 'undefined' && projectObj['description'] != null) {
            theObj = document.getElementById("projectDescription" + _index);
            if (null != theObj) {
                theObj.innerHTML = projectObj.description;
                theObj.style.display = "block";
            }
        }

        // Optional: external_url
        if (typeof projectObj['external_url'] != 'undefined' && projectObj['external_url'] != null) {
            theObj = document.getElementById("projectMoreInfo" + _index);
            if (null != theObj) {
                theObj.innerHTML = "More Info: <a href='" + projectObj.external_url + "' >" + shortText(projectObj.external_url) + "</a>";
                theObj.style.display = "block";
            }
        }
        // Optional: creator
        if (typeof projectObj['creator'] != 'undefined' && projectObj['creator'] != null) {
            theObj = document.getElementById("projectCreator" + _index);
            if (null != theObj) {
                theObj.innerHTML = "Creator: " + projectObj.creator;
                theObj.style.display = "block";
            }
        }
        // Optional: artist, 
        if (typeof projectObj['artist'] != 'undefined' && projectObj['artist'] != null) {
            theObj = document.getElementById("projectArtist" + _index);
            if (null != theObj) {
                theObj.innerHTML = "Artist: " + projectObj.artist;
                theObj.style.display = "block";
            }
        }       

        // Optional: video_real or video_id, can't have both.
        // video id and reel functionality slips in here.
        if (typeof projectObj.video_reel !== 'undefined' &&
            projectObj.video_reel !== null &&
            projectObj.video_reel != '') {

            let thehyperlink = arrGlobals['siteURL'] + 'dsn/reel/?reel=' + projectObj.video_reel;
            let Link = '<a href="' + thehyperlink + '" target="_self">Collection</a>';

            let videoObj = document.getElementById("projectReel" + _index);
            if (null != videoObj) {
                videoObj.innerHTML = "Video Reel: " + Link;
                videoObj.style.display = "block";
            }
        } else if (typeof projectObj.video_id !== 'undefined' &&
            projectObj.video_id !== null &&
            projectObj.video_id != '') {

            //https://amorstyle.com/dsn/video/?id=video_8yyib7gg8a1dke8m06khxg440c
            // Link to video functionality is [site] 
            let theVideoURL = arrGlobals['siteURL'] + 'dsn/video/?id=' + projectObj.video_id;
            let Link = '<a href="' + theVideoURL + '" target="_self">' + shortText(projectObj.video_id) + '</a>';

            let videoObj = document.getElementById("projectVideoId" + _index);
            if (null != videoObj) {
                videoObj.innerHTML = "Video_Id: " + Link;
                videoObj.style.display = "block";
            }
        } else {
            // show nothing
        }

        // Now build ABI and contract link
        document.getElementById("projectContract" + _index).innerHTML = "<a href='https://explorer.thetatoken.org/account/" + arrProjects[_index]["contractAddr"] + "' >" + shortText(arrProjects[_index]["contractAddr"]) + "</a>";

        // TODO: Should make ABI link a line in the project JSON file.
        // build link for ABI
        let imgObj = document.getElementById("projectABI" + _index);
        if (null != imgObj) {
            imgObj.innerHTML = arrProjects[_index]['D']['projectABIURL'];
        }

        // TODO: Come back to this.
        let imageHref = projectObj.whitepaper;
        //document.getElementById("projectImageLink" + _index).innerHTML = '<a href="' + imageHref + '" ><img height="auto" width="100%" src="' + projectObj.image + '" ></a>';

        // When writing the project image, we want to create a section behind the 
        // image that has a light gray background.
        let theSecHead = '<center><div style="background-color:#f0f0f0;max-height: 250px;max-width: 250px;padding-top: 7px;border-radius: 5px;" width="100%" height="auto">';
        let theSecTail = '</div></center>';
        document.getElementById("projectImageLink" + _index).innerHTML = theSecHead + '<img height="auto" width="96%" src="' + projectObj.image + '" >' + theSecTail;

        // build a useful link for the share icon.
        // "projectShare"+_index
        document.getElementById("projectImageLink" + _index).href = '';

        // The project section should be ready to display now.
        arrProjects[_index]['W']['project'] = true;
        //console.log('done with project, show it');
        showPage(_index);
    }
    else {
        console.log('no projectName element');
    }
}



// Something has completed it's fetch, if we have data that another fetch was
// dependent upon, let's go get it now.
// returns:
// true means that some other call will post showPage()
// false means continue to showPage();
function fetchMore(_index) {

    let bRet = false;

    // Perform Owner Check.
    if (typeof arrProjects[_index]['D']['owner'] !== 'undefined' && arrProjects[_index]['D']['owner'] !== null ) {
        //console.log('visitor: ' + arrGlobals['visitor']);
        //console.log('owner: ' + arrProjects[_index]['D']['owner']);
        if (arrGlobals['visitor'].toLowerCase() == arrProjects[_index]['D']['owner'].toLowerCase()) {
            //console.log('visitor is owner!');

            if (typeof arrProjects[_index]['I']['IPayV1']['Obj'] !== 'undefined' && arrProjects[_index]['I']['IPayV1']['Obj'] != null) {
                //console.log('have IPayV1');
                if (typeof arrProjects[_index]['D']['titled'] !== 'undefined' && arrProjects[_index]['D']['titled'] != null) {
                    //console.log('have titled');
                    if (typeof arrProjects[_index]['D']['titledPayObj'] === 'undefined'
                        || arrProjects[_index]['D']['titledPayObj'] == null) {
                        //console.log('get titled Balance');
                        AgentBalance(_index, arrProjects[_index]['D']['titled'], 'titledPayObj');
                        bRet = true;
                    }
                }
            }

            //TODO: What about the owner balances for participant and website contracts which are not IPayV1?
            // They get settled in the balanceOf with IERC721

            arrProjects[_index]['W']['visitorIsContractOwner'] = true;
            arrProjects[_index]['W']['visitorIsContractAdmin'] = true;
        } 
    } 
    // Has our query for the Titled address returned?
    if (typeof arrProjects[_index]['D']['titled'] !== 'undefined' && arrProjects[_index]['D']['titled'] !== null) {
        // Now check to see if the visitor is the titled.
        if (arrGlobals['visitor'].toLowerCase() == arrProjects[_index]['D']['titled'].toLowerCase()) {
            //console.log('visitor is titled!');

            // We're waiting for the call to payAgentBalance() to finish, when it does, we'll
            // just copy it over to the titledPayObj.
            //arrProjects[_index]['D']['titledPayObj']
            if (typeof arrProjects[_index]['D']['agentPayObj'] !== 'undefined' && arrProjects[_index]['D']['agentPayObj'] != null) {
                if (typeof arrProjects[_index]['D']['titledPayObj'] === 'undefined' || arrProjects[_index]['D']['titledPayObj'] == null) {

                    if (typeof arrProjects[_index]['W']['titledPayObj'] === 'undefined' || arrProjects[_index]['W']['titledPayObj'] == false) {
                        console.log('visitor is titled!');
                        arrProjects[_index]['D']['titledPayObj'] = arrProjects[_index]['D']['agentPayObj'];
                        writeTitledPayInfo(_index);
                        bRet = false;
                    }
                }
            }

            arrProjects[_index]['W']['visitorIsContractTitled'] = true;
            arrProjects[_index]['W']['visitorIsContractAdmin'] = true;
        } 
    } 

    // When providing the payWithdrawTitled() functionality, if the visitor is either the owner
    // or titled, they will be able to call the code. So, if the visitor is the titled, then we
    // know the balance on the contract is the agentPayObj. If the visitor is the owner and NOT
    // the titled, then we have to fetch the titledPayObj. In either case, we'll need to write 
    // the information to the page.

    if (typeof arrProjects[_index]['D']['titled'] !== 'undefined' && arrProjects[_index]['D']['titled'] !== null) {
        if (typeof arrProjects[_index]['D']['owner'] !== 'undefined' && arrProjects[_index]['D']['owner'] !== null) {
            //console.log('have both titled and owner, are they equal?');

            // If the visotor is NOT titled, we have to fetch a titledPayObj to use.

            if (arrProjects[_index]['D']['titled'].toLowerCase() == arrProjects[_index]['D']['owner'].toLowerCase()) {
                //console.log('yes, equal');
                // as soon as we get the agentPayObj, we'll copy it to the titledPayObj
                if (typeof arrProjects[_index]['D']['agentPayObj'] !== 'undefined' && arrProjects[_index]['D']['agentPayObj'] != null) {
                    if (typeof arrProjects[_index]['D']['titledPayObj'] === 'undefined' || arrProjects[_index]['D']['titledPayObj'] == null) {

                        if (typeof arrProjects[_index]['W']['titledPayObj'] === 'undefined' || arrProjects[_index]['W']['titledPayObj'] == false) {
                            arrProjects[_index]['D']['titledPayObj'] = arrProjects[_index]['D']['agentPayObj'];
                            writeTitledPayInfo(_index);
                            //console.log('HERE ONCE HERE titled==owner');
                            bRet = false;
                        }
                    }
                }

            } else {
                if (arrProjects[_index]['D']['owner'].toLowerCase() == arrGlobals['visitor'].toLowerCase()) {
                    //console.log('Visitor is owner');

                    if (typeof arrProjects[_index]['D']['payObj'] !== 'undefined' &&
                        arrProjects[_index]['D']['payObj'] != null) {

                        if (typeof arrProjects[_index]['D']['titledPayObj'] !== 'undefined' &&
                            arrProjects[_index]['D']['titledPayObj'] != null) {

                            if (typeof arrProjects[_index]['W']['titledPayObj'] === 'undefined' || arrProjects[_index]['W']['titledPayObj'] == false) {
                                writeTitledPayInfo(_index);
                                console.log('HERE ONCE HERE titled!=owner');
                                bRet = false;
                            }
                        }
                    }
                }
            }
        }
    }

    // We can get the pennyPriceObj object from IPennyOracleV1
    if ((typeof arrProjects[_index]['D']['pennyPriceObj'] !== 'undefined') && (arrProjects[_index]['D']['pennyPriceObj'] !== null)) {

        // We can get a price object from IWebV1, IPartV1 or IPayV1.
        // Perform PriceObj Check for mint prices on participant and website.
        if (typeof arrProjects[_index]['D']['priceObj'] !== 'undefined' && arrProjects[_index]['D']['priceObj'] !== null) {

            //console.log('Act on priceObj!');

            if (typeof arrProjects[_index]['W']['mint'] === 'undefined' || (arrProjects[_index]['W']['mint'] == null)) {
                //console.log('processing mint');
                calculateMintCost(_index);
                writePartMint(_index);
                arrProjects[_index]['W']['mint'] = true;
                bRet = false;
            }
            if (typeof arrProjects[_index]['W']['burn'] === 'undefined' || (arrProjects[_index]['W']['burn'] == null)) {
                //console.log('processing burn');
                calculateBurnCost(_index);
                writePartBurn(_index);
                arrProjects[_index]['W']['burn'] = true;
                bRet = false;
            }
        } 

        // this call requires the arrProjects[_index]['D']['prices']['mintPriceTfuelWei']
        //console.log('Have mintTfuelWei: ' + arrProjects[_index]['D']['prices']['mintPriceTfuelWei']);
        if (typeof arrProjects[_index]['D']['prices']['mintPriceTfuelWei'] !== 'undefined' && arrProjects[_index]['D']['prices']['mintPriceTfuelWei'] !== null) {
            //console.log('Have mintTfuelWei: ' + arrProjects[_index]['D']['prices']['mintPriceTfuelWei']);

            if (typeof arrProjects[_index]['W']['mint'] === 'undefined' || (arrProjects[_index]['W']['mint'] == null)) {
                //console.log('processing mint');
                calculateMintNormal(_index);
                arrProjects[_index]['W']['mint'] = true;
                bRet = false;
            }
        }

        // this call requires the arrProjects[_index]['D']['prices']['burnPriceTfuelWei']
        //console.log('Have burnWei: ' + arrProjects[_index]['D']['prices']['burnPriceTfuelWei']);
        if (typeof arrProjects[_index]['D']['prices']['burnPriceTfuelWei'] !== 'undefined' && arrProjects[_index]['D']['prices']['burnPriceTfuelWei'] !== null) {
            //console.log('Have mintTfuelWei: ' + arrProjects[_index]['D']['prices']['burnPriceTfuelWei']);
            if (typeof arrProjects[_index]['W']['burn'] === 'undefined' || (arrProjects[_index]['W']['burn'] == null)) {
                //console.log('processing burn');
                calculateBurnNormal(_index);
                arrProjects[_index]['W']['burn'] = true;
                bRet = false;
            }
        }

    }

    // arrProjects[_index]['D']['memTokenId']
    // arrProjects[_index]['D']['memInfoObj']
    //
    // The membership functionality requires the tokenId from IERC721ExV1M and the memInfoObj from
    // IMemV1. Once we have them both, we can invoke:
    // IMemIsTimeLeft(_index);
    // IMemBalance(_index);
    // to get the rest of the information about the specific token id.
    //
    if (typeof arrProjects[_index]['D']['memInfoObj'] !== 'undefined' && arrProjects[_index]['D']['memInfoObj'] !== null) {
        if (typeof arrProjects[_index]['D']['memTokenId'] !== 'undefined' &&
            arrProjects[_index]['D']['memTokenId'] !== null ) {

            if (arrProjects[_index]['D']['memTokenId'] > 0) {
                if (typeof arrProjects[_index]['W']['istimeLeft'] === 'undefined' || (arrProjects[_index]['W']['istimeLeft'] == null)) {
                    console.log('processing timeLeft');
                    IMemIsTimeLeft(_index); // Async, thus it has to set a trigger for show
                    arrProjects[_index]['W']['istimeLeft'] = true;
                    bRet = false;
                }
            }
            if (typeof arrProjects[_index]['W']['memBal'] === 'undefined' || (arrProjects[_index]['W']['memBal'] == null)) {
                console.log('processing memBal');
                IMemBalance(_index); // Async, thus it has to set a trigger for show
                arrProjects[_index]['W']['memBal'] = true;
                bRet = false;
            }
            arrProjects[_index]['W']['mem'] = true;
        }
    }

    if (typeof arrProjects[_index]['D']['memBalanceObj'] !== 'undefined' &&
        arrProjects[_index]['D']['memBalanceObj'] !== null) {
        if (typeof arrProjects[_index]['D']['memTokenId'] !== 'undefined' &&
            arrProjects[_index]['D']['memTokenId'] !== null) {
            //console.log('calling: writeEnableMemButtons()');
            writeEnableMemButtons(_index);
            bRet = false;
        }
    }
    if (typeof arrProjects[_index]['D']['memBalanceObj'] !== 'undefined' &&
        arrProjects[_index]['D']['memBalanceObj'] !== null) {
        if (typeof arrProjects[_index]['D']['theTokenId'] !== 'undefined' &&
            arrProjects[_index]['D']['theTokenId'] !== null) {
            //console.log('calling: writeHolderMemButtons()');
            writeHolderMemButtons(_index);
            bRet = false;
        }
    }

    return bRet;
}


function holdsBurnableToken(_index) {
    if (typeof arrProjects[_index]['D']['theTokenId'] !== 'undefined' &&
        arrProjects[_index]['D']['theTokenId'] != null &&
        parseInt(arrProjects[_index]['D']['theTokenId']) > 0) {
        //console.log('holdsBurnableToken() theTokenId: ' + arrProjects[_index]['D']['theTokenId'] + ', returning true');
        return true;
    } 
    //console.log('holdsBurnableToken() returning false');
    return false;
}
function holdsToken(_index) {
    if (typeof arrProjects[_index]['D']['balanceOf'] !== 'undefined' &&
        arrProjects[_index]['D']['balanceOf'] != null && 
        parseInt(arrProjects[_index]['D']['balanceOf']) > 0 )
    {
        //console.log('holdsToken() balanceOf: ' + parseInt(arrProjects[_index]['D']['balanceOf']) + ', returning true');
        return true;
    }
    else if (typeof arrProjects[_index]['D']['theTokenId'] !== 'undefined' &&
        arrProjects[_index]['D']['theTokenId'] != null &&
        parseInt(arrProjects[_index]['D']['theTokenId']) > 0)
    {
        //console.log('holdsToken() memTokenId: ' + arrProjects[_index]['D']['memTokenId'] + ', returning true');
        return true;
    } 
/*
    else if (typeof arrProjects[_index]['D']['memTokenId'] !== 'undefined' &&
        arrProjects[_index]['D']['memTokenId'] != null &&
        parseInt(arrProjects[_index]['D']['memTokenId']) > 0) {
        //console.log('holdsToken() memTokenId: ' + arrProjects[_index]['D']['memTokenId'] + ', returning true');
        return true;
    } 
*/
    //console.log('holdsToken() returning false');
    return false;
}
function allowsMultiple(_index) {
    if (typeof arrProjects[_index]['D']['bSingle'] !== 'undefined' &&
        arrProjects[_index]['D']['bSingle'] == true) {
        //console.log('allowsMultiple(): bSingle is true, thus returning false' );
        return false;
    } else if (typeof arrProjects[_index]['D']['bSoulBound'] !== 'undefined' &&
        arrProjects[_index]['D']['bSoulBound'] == true) {
        //console.log('allowsMultiple(): bSoulBound is true, thus returning false');
        return false;
    }
    //console.log('allowsMultiple(): returning true');
    return true;
}

//
// every time data is fetched, this routine will be called to display the sections that are
// ready to be shown.
function showPage(_index) {

    //console.log("index: "+_index);
    //
    // This -1 check is only to dislay the "not participant yet" info.
    if (_index == -1) {
        // Only called this way from the Participant Fetch
        if (arrGlobals['IsParticipant']) {
            //console.log("toggle participant section off");
            document.getElementById('noParticipantId').style.display = "none";
        } else {
            document.getElementById('noParticipantId').style.display = "block"; // display the block
            //console.log("sell a participant NFT");
        }
        return; // can't use this index
    }

    if (typeof arrProjects[_index] === 'undefined' || arrProjects[_index] === null) {
        return; // to early
    }

    // Because this routine gets called after data is fetched, we will check to see
    // if there is more data to fetch. If there is, we'll short out and go fetch it.
    if (fetchMore(_index)) {
        //console.log("fetchMore returned true");
        return;
    }
    //console.log("fetchMore returned false, full pass");

/*
    if (typeof arrProjects[_index]["visitorIsOwner"] !== 'undefined' && arrProjects[_index]["visitorIsOwner"] !== null) {
        showBlock("ownerLineId", _index);
    }
*/

    //TODO: For testing, show everything at the start.
    //showBlock("projectRowId", _index);

    // Only want to show the overall project after the picture loads.
    //arrProjects[_index]['W']['project']
    if (typeof arrProjects[_index]['W']['project'] !== 'undefined' && arrProjects[_index]['W']['project'] == true) {
        //console.log('yep, showing: '+_index);
        showBlock("projectRowId", _index);
    }

    //TODO: Impliment Single functionality
    //
    // When showing the mint and burn sections, we need a little more information about the state of the
    // user. We know that if we have 'mint' or 'burn' set true, that functionality is available
    // on the contract. So, ...
    //
    // If the contract is 'bSingle', there can only be one. This needs to be applied to 'mint'.
    //
    // If the contract is 'bSoulBound' we either get a 'mint' or a 'burn' but not both.
    //
    // both cases depend upon knowing if the account is currently holding a token. This gets complicated
    // with memberships because 'balanceOf' will be zero on expired contracts. Thus, for memberships (or
    // contracts marked 'bSingle'), we'll need to check to see if 'memTokenId' is nonzero. 
    //
    // Impliment mint or burn for Participant and website and projects that are single.
    if (typeof arrProjects[_index]['W']['mint'] !== 'undefined' && arrProjects[_index]['W']['mint'] == true) {

        let bVisitorHoldsToken = holdsToken(_index);
        let vContractIsMultiple = allowsMultiple(_index);

        // now we know if the contract can mint another.
        if (!bVisitorHoldsToken) {
            // if they don't have one, offer it.
            showBlock("mintRowId", _index);
        } else if (vContractIsMultiple) {
            // if it allows for multiple, offer it.
            showBlock("mintRowId", _index);
        } else {
            hideBlock("mintRowId", _index);
        }
    }
    if (typeof arrProjects[_index]['W']['burn'] !== 'undefined' && arrProjects[_index]['W']['burn'] == true) {
        let bVisitorHoldsToken = holdsBurnableToken(_index);
        let vContractIsMultiple = allowsMultiple(_index);

        //console.log('bVisitorHoldsToken: ' + bVisitorHoldsToken);
        //console.log('vContractIsMultiple: ' + vContractIsMultiple);
        //
        // for burning, the visitor must be holding a token. But, if the contract allow for 
        // holding multiple, a simple burn will not work because they will have to provide
        // the token id to burn.
        //
        if (bVisitorHoldsToken && !vContractIsMultiple) {
            showBlock("burnRowId", _index);
        } else {
            hideBlock("burnRowId", _index);
        }
    }

    // Membership stuff.
    //
    // We have five different sections that will be shown:
    //  memInfoLine // Always show
    //  memEvaluateLine // Always show
    //  memRenewLine; // Only show if there is a tokenId
    //  memStatusLine // Only show if there is a tokenId
    //  memOwnerLine // Next: Owner Section allows for memSettledTitled() and memGrantHours()
    //
    if (typeof arrProjects[_index]['W']['mem'] !== 'undefined' &&
        arrProjects[_index]['W']['mem'] !== null &&
        arrProjects[_index]['W']['mem'] == true) {

        // memInfoLine is shown by default
        // memEvaluateLine is shown by default

        if (typeof arrProjects[_index]['W']['istimeLeft'] !== 'undefined' && arrProjects[_index]['W']['istimeLeft'] !== null) {
            if (typeof arrProjects[_index]['W']['memBal'] !== 'undefined' && arrProjects[_index]['W']['memBal'] !== null) {
                // show memRenewLine
                showBlock("memRenewId", _index);
                // show memStatusLine
                showBlock("memStatusId", _index);
            }
        }

        // show memOwnerLine
        // arrProjects[_index]['W']['visitorIsContractOwner']
        if (typeof arrProjects[_index]['W']['visitorIsContractAdmin'] !== 'undefined' && arrProjects[_index]['W']['visitorIsContractAdmin'] == true) {

            if (typeof arrProjects[_index]['D']['memTokenId'] !== 'undefined' &&
                arrProjects[_index]['D']['memTokenId'] != null &&
                parseInt(arrProjects[_index]['D']['memTokenId']) > 0) {

                showBlock("memOwnerId", _index);
            }
        }

        // Show the larger memRowId
        showBlock("memRowId", _index);
    }

    // Agent stuff. If the visitor is an agent of the network, we show them agent related information
    //arrGlobals['IsAgent']
    if (arrGlobals['IsAgent']) {
        // and there has to be payMintInfo to show.
        // arrProjects[_index]['W']['payMintInfo']
        if (typeof arrProjects[_index]['W']['payMintInfo'] !== 'undefined' && arrProjects[_index]['W']['payMintInfo'] == true) {
            showBlock("agentRowId", _index);
        }
    }
    //
    // If the visitor is the contract owner, we will enable the admin section for interacting with the
    // smart contract.
    //
    if (typeof arrProjects[_index]['W']['visitorIsContractOwner'] !== 'undefined' &&
        arrProjects[_index]['W']['visitorIsContractOwner'] == true) {
        //console.log('Show Admin section'+_index);

        if (typeof arrProjects[_index]['W']['mintToggle'] !== 'undefined' && arrProjects[_index]['W']['mintToggle'] == true) {
            showBlock("AdminRowId", _index);
        }

        // Now, if the ownerBal or titledBal are set, this visitor should be able to withdraw those funds
        if (typeof arrProjects[_index]['W']['ownerBal'] !== 'undefined' && arrProjects[_index]['W']['ownerBal'] == true) {
            console.log('TODO: Does this work? showing ownerLineId owner');
            showBlock("ownerLineId", _index);
        }
        if (typeof arrProjects[_index]['W']['titledBal'] !== 'undefined' && arrProjects[_index]['W']['titledBal'] == true) {
            console.log('TODO: Does this work? showing ownerLineId titled');
            showBlock("ownerLineId", _index);
        }

        // The visitor will have an agentPayObj if they hold a balance on an IPayV1 contract
        if (typeof arrProjects[_index]['D']['agentPayObj'] !== 'undefined' && arrProjects[_index]['D']['agentPayObj'] != null) {
            //console.log('TODO: Does this work? showing ownerLineId titled');
            showBlock("ownerLineId", _index);
        }

        // used for participant and website contracts
        if (typeof arrProjects[_index]['D']['contractBalance'] !== 'undefined' && arrProjects[_index]['D']['contractBalance'] != null) {
            //console.log('showing ownerLineId titled');
            showBlock("ownerLineId", _index);
        }

//        hideBlock("ownerLinkRowId", _index); // owner link
    }
    // visitor is not the owner, let's show the owner link
//    if (typeof arrProjects[_index]['W']['visitorIsContractOwner'] === 'undefined' || arrProjects[_index]['W']['visitorIsContractOwner'] == false) {
//        showBlock("ownerLinkRowId", _index); // owner link
//    }

    //
    // If the visitor is an admin (registered as either owner or titled), theAdminSec will
    // be shown.
    //
    if (typeof arrProjects[_index]['W']['visitorIsContractAdmin'] !== 'undefined' &&
        arrProjects[_index]['W']['visitorIsContractAdmin'] == true) {

        // The contract may or may not support mint toggling. If it does, we'll enable it.
        if (typeof arrProjects[_index]['W']['mintToggle'] !== 'undefined' &&
            arrProjects[_index]['W']['mintToggle'] == true) {
            showBlock("AdminLinkId", _index); 
        }

        // Now we need to show the correct button for the contract
        let withdrawIPayV1 = false;
        let withdrawLegacy = false;
        if (typeof arrProjects[_index]['W']['ownerWithdrawIPayV1'] !== 'undefined' &&
            arrProjects[_index]['W']['ownerWithdrawIPayV1'] == true) {
            // we have IPayV1 withdraw
            withdrawIPayV1 = true;
        }
        if (typeof arrProjects[_index]['W']['ownerWithdrawLegacy'] !== 'undefined' &&
            arrProjects[_index]['W']['ownerWithdrawLegacy'] == true) {
            // we have Legacy withdraw
            withdrawLegacy = true;
        }
        if (withdrawIPayV1 || (withdrawIPayV1 && withdrawLegacy)) {
            showBlock('ownerLineId',_index);
            showInline("ownerButtonIPayV1Id", _index);
            hideBlock("ownerButtonLegacyId", _index);
            //console.log('path one');
        } else {
            if (withdrawLegacy) {
                showBlock('ownerLineId', _index);
                showInline("ownerButtonLegacyId", _index);
                hideBlock("ownerButtonIPayV1Id", _index);
                //console.log('path two');
            }
        }

        showBlock("AdminRowId", _index); 
    }

    //
    // These two checks mange the TitleLinkRow.
    //
    if (typeof arrProjects[_index]['W']['visitorIsContractAdmin'] !== 'undefined' &&
        arrProjects[_index]['W']['visitorIsContractAdmin'] == true) {
        hideBlock("titledLinkRowId", _index); // titled link
    }
    if (typeof arrProjects[_index]['W']['visitorIsContractTitled'] === 'undefined' ||
        arrProjects[_index]['W']['visitorIsContractTitled'] == false) {
        showBlock("titledLinkRowId", _index); // titled link
    }

}

function showBlock(_nameId, _index) {
    //console.log("showing: " + _nameId + _index);
    const elem = document.getElementById((_nameId + _index));
    if (elem != null) {
        elem.style.display = "block";
    }
}
function showInline(_nameId, _index) {
    //console.log("showing: " + _nameId + _index);
    const elem = document.getElementById((_nameId + _index));
    if (elem != null) {
        elem.style.display = "inline";
    }
}
function hideBlock(_nameId, _index) {
    //console.log("hiding: " + _nameId + _index);
    const elem = document.getElementById((_nameId + _index));
    if (elem != null) {
        elem.style.display = "none";
    }
}

async function IsCorrectNetwork() {
    var id = await web3.eth.getChainId();
    if (id != 361) {
        alert('MetaMask is not connected to the Theta Network. Please reconnect to the Theta Network.');
        return false;
    }
    return true;
}