import sha256 from 'crypto-js/sha256.js';
import { initialize } from 'zokrates-js';

const ZEROSTR = '0';

/* Converts a string into a stringified hexadecimal number */
function str2hex(string) {
	return string.split('')
	             .map(c => c.charCodeAt(0).toString(16).padStart(2, '0'))
	             .join('');
}

/* Converts a stringified hexadecimal number into a string */
function hex2str(hex) {
	return hex.split(/(\w\w)/g)
	          .filter(p => !!p)
	          .map(c => String.fromCharCode(parseInt(c, 16)))
	          .join('');
}

/* Prefix each element of a string a array with 0x */
function map_0xprefix(arr) {
	return arr.map(s => '0x' + s);
}

/* Convert a string to a byte array */
function str2asciiarr(s) {
	let charCodeArr = [];
	for (let i = 0; i < s.length; i++) {
		let code = s.charCodeAt(i);
		charCodeArr.push(code);
	}

	return charCodeArr;
}

/* Convert a byte array to a string */
function asciiarr2str(arr) {
	return String.fromCharCode(...arr);
}


/*
 * Expand a simple number into a stringified u32[8] array for ZoKrates.
 */
function expand_number(number) {
	const revstr = s => Array.from(s).reverse().join('');
	const split8 = s => s.match(/.{1,8}/g);
	const parts = split8(revstr(number.toString(16)))
		      .map(p => '0x' + revstr(p))
		      .map(p => parseInt(p, 16).toString())
		      .reverse();

	return [...Array(8 - parts.length).fill(ZEROSTR), ...parts];
}


/* Hard coded zokrates program source code */
const zokSrc = `
import "hashes/sha256/sha256Padded.zok" as sha256;
from "utils/casts.zok" import cast;

def main(public u32[8] hash,public u32[5] address,private u8[64] flag){
     u8[20] addr8 = cast(address);
     u32[8] genHash = sha256(flag);
     log("Hash: {} {} {} {} {} {} {} {}",genHash[0],genHash[1],genHash[2],genHash[3],genHash[4],genHash[5],genHash[6],genHash[7]);
     assert(genHash == hash);
     return;
}
`;


/* Get the proving key from the local server */
const PROVING_KEY_URI = 'http://localhost:8080/proving.key';
const proving_key = await (await fetch(PROVING_KEY_URI)).text();

function submitFlag(flag) {
	initialize().then((defaultProvider) => {
		const zokProvider = defaultProvider.withOptions({
			backend: 'ark',
			scheme: 'gm17',
		});

		const artefacts = zokProvider.compile(zokSrc);

		const flag_ascii = str2asciiarr(flag);
		const flag_ascii_padded = flag_ascii.concat(new Array(64 - flag_ascii.length).fill(0));
		const flag_padded = asciiarr2str(flag_ascii_padded);

		const hash = sha256(flag_padded).toString(); /* TODO get this from contract */
		const addr = 'f6e3a49fca2eb57f286d516fa60154ebfd10d5ad'; /* TODO get this from metamask */

		const flag_split = map_0xprefix(str2hex(flag).match(/.{1,2}/g));
		const flag_split_padded = flag_split.concat(new Array(64 - flag_split.length).fill('0x0'));
		const hash_split = map_0xprefix(hash.match(/.{1,8}/g));
		const addr_split = map_0xprefix(addr.match(/.{1,8}/g));

		// witness computation
		const { witness, output } = zokProvider.computeWitness(artefacts, [hash_split, addr_split, flag_split_padded]);

		// generate proof
		const proof = zokProvider.generateProof(artefacts.program, witness, proving_key);

		console.log(proof);
	});
}

document.getElementById('submitFlagBtn').addEventListener('click', () => {
	submitFlag(document.getElementById('flagbox').value);
});