importScripts('opentally.js?v=GITVERSION');

var wasm = wasm_bindgen;
var wasmRaw;

// For asyncify
const DATA_ADDR = 16;
const DATA_START = DATA_ADDR + 8;
const DATA_END = 50 * 1024; // Needs to be increased compared with Asyncify default

async function initWasm() {
	wasmRaw = await wasm_bindgen('opentally_async.wasm?v=GITVERSION');
	
	new Int32Array(wasmRaw.memory.buffer, DATA_ADDR).set([DATA_START, DATA_END]);
	
	postMessage({'type': 'init', 'version': wasm.version()});
}
initWasm();

var reportStyle;
var numbers, election, opts, state, stageNum;

onmessage = function(evt) {
	try {
		if (evt.data.type === 'countElection') {
			errored = false;
			
			if (evt.data.numbers === 'fixed') {
				numbers = 'Fixed';
				wasm.fixed_set_dps(evt.data.decimals);
			} else if (evt.data.numbers === 'gfixed') {
				numbers = 'GuardedFixed';
				wasm.gfixed_set_dps(evt.data.decimals);
			} else if (evt.data.numbers === 'float64') {
				numbers = 'NativeFloat64';
			} else if (evt.data.numbers === 'rational') {
				numbers = 'Rational';
			} else {
				throw 'Unknown --numbers';
			}
			
			reportStyle = evt.data.reportStyle;
			
			// Init STV options
			opts = wasm.STVOptions.new.apply(null, evt.data.optsStr);
			
			// Validate options
			opts.validate();
			
			// Init election
			election = wasm['election_from_blt_' + numbers](evt.data.bltData);
			wasm['preprocess_election_' + numbers](election, opts);
			
			// Init constraints if applicable
			if (evt.data.conData) {
				wasm['election_load_constraints_' + numbers](election, evt.data.conData);
			}
			
			// Describe count
			postMessage({'type': 'describeCount', 'content': wasm['describe_count_' + numbers](evt.data.bltPath, election, opts)});
			
			// Init results table
			postMessage({'type': 'initResultsTable', 'content': wasm['init_results_table_' + numbers](election, opts, reportStyle)});
			
			// Step election
			state = wasm['CountState' + numbers].new(election);
			wasm['count_init_' + numbers](state, opts);
			
			postMessage({'type': 'updateResultsTable', 'result': wasm['update_results_table_' + numbers](1, state, opts, reportStyle)});
			postMessage({'type': 'updateStageComments', 'comment': wasm['update_stage_comments_' + numbers](state)});
			
			stageNum = 2;
			
			resumeCount();
			
		} else if (evt.data.type == 'userInput') {
			userInputBuffer = evt.data.response;
			
			// Rewind the stack
			// Asyncify will retrace the function calls in the stack until again reaching get_user_input
			wasmRaw.asyncify_start_rewind(DATA_ADDR);
			resumeCount();
		}
	} catch (ex) {
		if (errored) {
			// Panic already logged and sent to UI
		} else {
			throw ex;
		}
	}
}

function resumeCount() {
	for (;; stageNum++) {
		let isDone = wasm['count_one_stage_' + numbers](state, opts);
		
		if (wasmRaw.asyncify_get_state() !== 0) {
			// This stage caused a stack unwind in get_user_input so ignore the result
			// We will resume execution when a userInput message is received
			return;
		}
		
		if (isDone) {
			break;
		}
		
		postMessage({'type': 'updateResultsTable', 'result': wasm['update_results_table_' + numbers](stageNum, state, opts, reportStyle)});
		postMessage({'type': 'updateStageComments', 'comment': wasm['update_stage_comments_' + numbers](state)});
	}
	
	postMessage({'type': 'updateResultsTable', 'result': wasm['finalise_results_table_' + numbers](state, reportStyle)});
	postMessage({'type': 'finalResultSummary', 'summary': wasm['final_result_summary_' + numbers](state, opts)});
}

var errored = false;
function wasm_error(message) {
	postMessage({'type': 'errorMessage', 'message': message});
	errored = true;
}

var userInputBuffer = null;

function get_user_input(message) {
	if (userInputBuffer === null) {
		postMessage({'type': 'requireInput', 'message': message});
		
		// Record the current state of the stack
		wasmRaw.asyncify_start_unwind(DATA_ADDR);
		
		// No further WebAssembly will be executed and control will return to resumeCount
		return null;
	} else {
		// We have reached the point the stack was originally unwound, so resume normal execution
		wasmRaw.asyncify_stop_rewind();
		
		// Return the correct result to WebAssembly
		let userInput = userInputBuffer;
		userInputBuffer = null;
		return userInput;
	}
}
