var circuit = {};
var provingKey = {};
var witnessCalc = {};
const abi = JSON.parse(`[{"inputs":[{"internalType":"address","name":"_depositVerifierContractAddr","type":"address"},{"internalType":"address","name":"_withdrawVerifierContractAddr","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"_commitment","type":"uint256"},{"internalType":"uint256","name":"_root","type":"uint256"},{"internalType":"uint256[2]","name":"a","type":"uint256[2]"},{"internalType":"uint256[2][2]","name":"b","type":"uint256[2][2]"},{"internalType":"uint256[2]","name":"c","type":"uint256[2]"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getCommitments","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"_address","type":"address"},{"internalType":"uint256","name":"nullifier","type":"uint256"},{"internalType":"uint256[2]","name":"a","type":"uint256[2]"},{"internalType":"uint256[2][2]","name":"b","type":"uint256[2][2]"},{"internalType":"uint256[2]","name":"c","type":"uint256[2]"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]`);
const miksiAddress = "0x4cc45573481A2977fcC0b9DD9f8c710201B5a5cd";
let metamask = false;

document.getElementById("contractAddr").innerHTML=`<a href="https://goerli.etherscan.io/address/`+miksiAddress+`" target="_blank" title="Miksi Smart Contract Address">`+miksiAddress+`</a>`;

function println(...s) {
  let r = "";
  for (let i=0; i<s.length; i++) {
    r = r + " " + s[i];
  }
  console.log(r);
  document.getElementById("logs").innerHTML += r + "<br>";
  document.getElementById("logs").scrollTop = document.getElementById("logs").scrollHeight;
}
function printerr(...s) {
  // println(s);
  let r = "";
  for (let i=0; i<s.length; i++) {
    r = r + " " + s[i];
  }
  console.log(r);
  document.getElementById("logs").innerHTML += `<span style="color:red;">Error: ` + r + `</span><br>`;
  document.getElementById("logs").scrollTop = document.getElementById("logs").scrollHeight;
}

function loadCircuit(circuitname) {
  fetch("circuits-files/"+circuitname+"-proving_key.bin").then( (response) => {
    return response.arrayBuffer();
  }).then( (b) => {
    provingKey[circuitname] = b;
    println("proving_key loaded for", circuitname);
  });

  fetch("circuits-files/"+circuitname+".wasm").then( (response) => {
    return response.arrayBuffer();
  }).then( (b) => {
    witnessCalc[circuitname] = b;
    console.log("w", b);
    println("witnessCalc loaded for", circuitname);
  });
}

async function deposit(circuitname) {
  if (!metamask) {
    toastr.error("Please connect Metamask by clicking on the button at the top of the page");
    return;
  }
  if (window.ethereum.networkVersion!='5') {
    toastr.warning("Please switch to Göerli");
    alert("Please switch to Göerli");
    return;
  }
  document.getElementById("depositRes").innerHTML = `
    Please wait. Generating the zk-proof and making the deposit...
  `;
  console.log("circuit:", circuitname);

  // TODO
  println("generate random secret & nullifier");
  const secret = miksi.randBigInt().toString();
  const nullifier = miksi.randBigInt().toString();
  console.log("S N", secret, nullifier);

  println("get commitments from the miksi Smart Contract");
  let res = await miksiContract.methods.getCommitments().call();
  console.log("res", res);
  const commitments = res[0];
  const key = res[2];
  console.log("commitments", commitments);
  console.log("key", key);
  // getCommitments from the tree

  // calculate witness
  println("rebuild the Merkle Tree & calculate witness for deposit");
  console.log(witnessCalc[circuitname]);
  const cw = await miksi.calcDepositWitness(witnessCalc[circuitname], secret, nullifier, commitments, key).catch((e) => {
    toastr.error(e);
    printerr(e);
  });

  const witness = cw.witness;
  const publicInputs = cw.publicInputs;
  console.log("w", witness);
  console.log("publicInputs", publicInputs);

  // generate proof
  console.log(provingKey[circuitname]);
  println("generate zkSNARK Groth16 proof for deposit");
  const start = new Date().getTime();
  const proof = await window.groth16GenProof(witness.buffer, provingKey[circuitname]);
  const end = new Date().getTime();
  const time = end - start;
  println("circuit " + circuitname + " took " + time + "ms to compute");
  console.log(proof);
  console.log("proof", JSON.stringify(proof));


  // send tx
  const accounts = await web3.eth.getAccounts();
  const sender = accounts[0];
  console.log("SENDER", sender);

  console.log("sc call data",
    publicInputs.commitment,
    publicInputs.root.toString(),
    [proof.pi_a[0], proof.pi_a[1]],
    [
      [proof.pi_b[0][1], proof.pi_b[0][0]],
      [proof.pi_b[1][1], proof.pi_b[1][0]]
    ],
    [proof.pi_c[0], proof.pi_c[1]],
  );
  println("send publicInputs & zkProof to the miksi Smart Contract for the deposit");
  miksiContract.methods.deposit(
    publicInputs.commitment,
    publicInputs.root.toString(),
    [proof.pi_a[0], proof.pi_a[1]],
    [
      [proof.pi_b[0][1], proof.pi_b[0][0]],
      [proof.pi_b[1][1], proof.pi_b[1][0]]
    ],
    [proof.pi_c[0], proof.pi_c[1]],
  ).send(
    {from: sender, value: 1000000000000000000},
    function(error, transactionHash){
      if (error!=undefined) {
        console.log(error);
        toastr.error(error);
        printerr(JSON.stringify(error));
      } else {
        let link = `<a href="https://goerli.etherscan.io/tx/`+transactionHash+`" target="_blank">
              https://goerli.etherscan.io/tx/`+transactionHash+`</a>`;
        println(link);
      }
    });

  // print secret & nullifier
  let jw = {
    secret: secret,
    nullifier: nullifier,
    key: key
  };
  console.log("jw", JSON.stringify(jw));
  document.getElementById("depositRes").innerHTML = `
  </br>
    <input class="form-control" onClick="this.select();" readonly value='`+JSON.stringify(jw)+`'>
    </input>
</br>
<b>Please store the data above in a safe place (anyone who has it will be able to withdraw your deposited ETH from the contract).</b>
  `;
}

async function withdraw(circuitname) {
  if (!metamask) {
    toastr.error("Please connect Metamask by clicking on the button at the top of the page");
    return;
  }
  if (window.ethereum.networkVersion!='5') {
    toastr.warning("Please switch to Göerli");
    alert("Please switch to Göerli");
    return;
  }
  document.getElementById("withdrawRes").innerHTML = `
    Please wait. Generating the zk-proof and making the withdrawal...
  `;
  console.log("circuit:", circuitname);
  let jw;
  try {
     jw = JSON.parse(document.getElementById("jsonWithdraw").value); 
  } catch(e) {
    toastr.error("Error reading secret & nullifier: " + e);
  }
  const secret = jw.secret;
  const nullifier = jw.nullifier;
  const key = jw.key;
  console.log(secret, nullifier);
  println("calculate commitment for the secret & nullifier");
  const commitment = miksi.calcCommitment(secret, nullifier);

  // getCommitments from the tree
  println("get commitments from the miksi Smart Contract");
  let res = await miksiContract.methods.getCommitments().call();
  console.log("res", res);
  const commitments = res[0];
  console.log("commitments", commitments);

  // calculate witness
  console.log(witnessCalc[circuitname]);
  const addr = document.getElementById("withdrawAddress").value;
  if (addr==undefined) {
    toastr.error("No withdraw address defined");
    return;
  }
  if (!web3.utils.isAddress(addr)) {
    toastr.error("Error with withdraw address");
    return;
  }
  println("rebuild the Merkle Tree & calculate witness for withdraw");
  const cw = await miksi.calcWithdrawWitness(witnessCalc[circuitname], secret, nullifier, commitments, addr, key).catch((e) => {
    toastr.error(e);
    printerr(e);
  });

  const witness = cw.witness;
  const publicInputs = cw.publicInputs;
  console.log("w", witness);
  console.log("publicInputs", publicInputs);

  // generate proof
  console.log(provingKey[circuitname]);
  println("generate zkSNARK Groth16 proof for withdraw");
  const start = new Date().getTime();
  const proof = await window.groth16GenProof(witness.buffer, provingKey[circuitname]);
  const end = new Date().getTime();
  const time = end - start;
  println("circuit " + circuitname + " took " + time + "ms to compute");
  console.log(proof);


  // send tx
  const accounts = await web3.eth.getAccounts();
  const sender = accounts[0];
  console.log("SENDER", sender);

  console.log("sc call data",
    publicInputs.address,
    publicInputs.nullifier,
    [proof.pi_a[0], proof.pi_a[1]],
    [
      [proof.pi_b[0][1], proof.pi_b[0][0]],
      [proof.pi_b[1][1], proof.pi_b[1][0]]
    ],
    [proof.pi_c[0], proof.pi_c[1]],
  );
  println("send publicInputs & zkProof to the miksi Smart Contract for the withdraw");
  miksiContract.methods.withdraw(
    publicInputs.address,
    publicInputs.nullifier,
    [proof.pi_a[0], proof.pi_a[1]],
    [
      [proof.pi_b[0][1], proof.pi_b[0][0]],
      [proof.pi_b[1][1], proof.pi_b[1][0]]
    ],
    [proof.pi_c[0], proof.pi_c[1]],
  ).send(
    {from: sender},
    function(error, transactionHash){
      if (error!=undefined) {
        console.log(error);
        toastr.error(error);
        printerr(JSON.stringify(error));
      } else {
        let link = `<a href="https://goerli.etherscan.io/tx/`+transactionHash+`" target="_blank">
              https://goerli.etherscan.io/tx/`+transactionHash+`</a>`;
         println(link);
	 document.getElementById("withdrawRes").innerHTML = 'Success :)'
      }
    });
}

loadCircuit("deposit");
loadCircuit("withdraw");




let miksiContract;

async function connectMetamask() {
  const ethEnabled = () => {
    if (window.web3) {
      window.web3 = new Web3(window.web3.currentProvider);
      window.ethereum.enable();
      return true;
    }
    return false;
  }

  if (!ethEnabled()) {
    toastr.warning("Please install Metamask to use miksi");
    alert("Please install MetaMask to use miksi");
    return;
  } else if (window.ethereum.networkVersion!='5') {
    toastr.warning("Please switch to Göerli");
    alert("Please switch to Göerli");
    return;
  } else {
    metamask = true;
  }

  console.log("abi", abi);
  miksiContract = new web3.eth.Contract(abi, miksiAddress);
  console.log("miksiContract", miksiContract);
  toastr.info("Metamask connected. Miksi contract: " + miksiAddress);
  println("Metamask connected. Miksi contract: ", miksiAddress);

  const acc = await web3.eth.getAccounts();
  const addr = acc[0];
  web3.eth.getBalance(addr, function(err, res){
    console.log("current address balance:", JSON.stringify(res));
  });
  const miksiBalance = await web3.eth.getBalance(miksiAddress);

  let html = "<b>miksi</b> Smart Contract current balance: " + miksiBalance/1000000000000000000 + " ETH<br>";
  let res = await miksiContract.methods.getCommitments().call();
  const commitments = res[0];
  const key = res[2];
  html += "number of commitments: " + commitments.length + "<br>";
  html += "current key: " + key + "<br>";
  document.getElementById("stats").innerHTML = html;

}
