/*
    pyRCV2: Preferential vote counting
    Copyright © 2020  Lee Yingtong Li (RunasSudo)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

importScripts('vendor/BigInt_BigRat-a5f89e2.min.js', 'vendor/big-6.0.0.min.js', 'vendor/sjcl-1.0.8.min.js', 'bundle.js');

let stage, result, counter, ppDPs, tiesPrompt;

onmessage = function(evt) {
	if (evt.data.type === 'init') {
		// Set settings
		if (evt.data.data.numbers === 'native') {
			py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Native);
		}
		if (evt.data.data.numbers === 'rational') {
			py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Rational);
		}
		if (evt.data.data.numbers === 'fixed') {
			py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Fixed);
			py.pyRCV2.numbers.set_dps(evt.data.data.fixedDPs);
		}
		
		ppDPs = evt.data.data.ppDPs;
		
		let election = py.pyRCV2.blt.readBLT(evt.data.data.data);
		let total_ballots = py.pyRCV2.numbers.Num(0);
		for (let b of election.ballots) {
			total_ballots.__iadd__(b.value);
		}
		
		// Create counter
		if (evt.data.data.transfers === 'uig') {
			counter = py.pyRCV2.method.gregory.UIGSTVCounter(election, evt.data.data.options);
		} else if (evt.data.data.transfers === 'eg') {
			counter = py.pyRCV2.method.gregory.EGSTVCounter(election, evt.data.data.options);
		} else if (evt.data.data.transfers === 'meek') {
			counter = py.pyRCV2.method.meek.MeekSTVCounter(election, evt.data.data.options);
		} else {
			counter = py.pyRCV2.method.gregory.WIGSTVCounter(election, evt.data.data.options);
		}
		
		if (evt.data.data.options['ties'] === 'backwards_random') {
			counter.options['ties'] = [py.pyRCV2.ties.TiesBackwards(counter), py.pyRCV2.ties.TiesRandom(evt.data.data.seed)];
		} else if (evt.data.data.options['ties'] === 'forwards_random') {
			counter.options['ties'] = [py.pyRCV2.ties.TiesForwards(counter), py.pyRCV2.ties.TiesRandom(evt.data.data.seed)];
		} else if (evt.data.data.options['ties'] === 'random') {
			counter.options['ties'] = [py.pyRCV2.ties.TiesRandom(evt.data.data.seed)];
		} else if (evt.data.data.options['ties'] === 'prompt') {
			tiesPrompt = py.pyRCV2.ties.TiesPrompt();
			counter.options['ties'] = [tiesPrompt];
		}
		
		postMessage({
			'type': 'init',
			'election': {
				'name': election.py_name,
				'candidates': election.candidates.map(c => c.py_name),
				'seats': election.seats,
			},
			'total_ballots': total_ballots.pp(0),
			'options': counter.describe_options(),
		});
		
		// Reset
		stage = 1;
		try {
			result = counter.reset();
		} catch (ex) {
			handleException(ex);
			return;
		}
		
		if (py.isinstance(result, py.pyRCV2.model.CountCompleted)) {
			postMessage({'type': 'done', 'result': resultToJS(result)});
			return;
		} else {
			postMessage({'type': 'result', 'result': resultToJS(result)});
		}
		
		stepElection();
	} else if (evt.data.type === 'require_input') {
		// Data received from the user
		tiesPrompt.buffer = evt.data.input;
		stepElection();
	}
}

function stepElection() {
	while (true) {
		try {
			result = counter.step();
		} catch (ex) {
			handleException(ex);
			return;
		}
		
		stage += 1;
		
		if (py.isinstance(result, py.pyRCV2.model.CountCompleted)) {
			postMessage({'type': 'done', 'result': resultToJS(result)});
			break;
		} else {
			postMessage({'type': 'result', 'result': resultToJS(result)});
		}
	}
}

function handleException(ex) {
	if (py.isinstance(ex, py.pyRCV2.ties.RequireInput)) {
		// Signals we require input to break a tie
		postMessage({'type': 'require_input', 'message': ex.message});
	} else if (py.isinstance(ex, py.pyRCV2.method.base_stv.STVException)) {
		console.error(ex);
		postMessage({'type': 'stv_exception', 'message': ex.message});
	} else {
		console.error(ex);
		throw ex;
	}
}

function resultToJS(result) {
	return {
		'stage': stage,
		'comment': result.comment,
		'logs': result.logs,
		'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {
			'transfers': cc.transfers.pp(ppDPs),
			'votes': cc.votes.pp(ppDPs),
			'state': cc.state,
			'order_elected': cc.order_elected,
		}]),
		'exhausted': {
			'transfers': result.exhausted.transfers.pp(ppDPs),
			'votes': result.exhausted.votes.pp(ppDPs)
		},
		'loss_fraction': {
			'transfers': result.loss_fraction.transfers.pp(ppDPs),
			'votes': result.loss_fraction.votes.pp(ppDPs)
		},
		'total': result.total.pp(ppDPs),
		'quota': result.quota.pp(ppDPs),
		'vote_required_election': result.vote_required_election === null ? null : result.vote_required_election.pp(ppDPs),
	};
}
