/*
    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 result, counter, ppDP, 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);
		}
		
		ppDP = evt.data.data.fixedDPs > 2 ? 2 : evt.data.data.fixedDPs;
		
		let election = py.pyRCV2.blt.readBLT(evt.data.data.data);
		postMessage({'type': 'init', 'election': {
			'candidates': election.candidates.map(c => c.py_name)
		}});
		
		// Create counter
		if (evt.data.data.transfers === 'uig') {
			counter = py.pyRCV2.method.base_stv.UIGSTVCounter(election, evt.data.data.options);
		} else if (evt.data.data.transfers === 'eg') {
			counter = py.pyRCV2.method.base_stv.EGSTVCounter(election, evt.data.data.options);
		} else if (evt.data.data.transfers === 'wright') {
			counter = py.pyRCV2.method.wright.WrightSTVCounter(election, evt.data.data.options);
		} else {
			counter = py.pyRCV2.method.base_stv.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'] === '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];
		}
		
		// Reset
		result = counter.reset();
		
		postMessage({'type': 'result', 'result': {
			'comment': result.comment,
			'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {
				'transfers': cc.transfers.pp(ppDP),
				'votes': cc.votes.pp(ppDP),
				'state': cc.state
			}]),
			'exhausted': {
				'transfers': result.exhausted.transfers.pp(ppDP),
				'votes': result.exhausted.votes.pp(ppDP)
			},
			'loss_fraction': {
				'transfers': result.loss_fraction.transfers.pp(ppDP),
				'votes': result.loss_fraction.votes.pp(ppDP)
			},
			'total': result.total.pp(ppDP),
			'quota': result.quota.pp(ppDP)
		}});
		
		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) {
			if (py.isinstance(ex, py.pyRCV2.ties.RequireInput)) {
				// Signals we require input to break a tie
				postMessage({'type': 'require_input', 'message': ex.message});
				break;
			} else {
				console.error(ex);
				throw ex;
			}
		}
		
		if (py.isinstance(result, py.pyRCV2.model.CountCompleted)) {
			postMessage({'type': 'done'});
			break;
		} else {
			postMessage({'type': 'result', 'result': {
				'comment': result.comment,
				'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {
					'transfers': cc.transfers.pp(ppDP),
					'votes': cc.votes.pp(ppDP),
					'state': cc.state
				}]),
				'exhausted': {
					'transfers': result.exhausted.transfers.pp(ppDP),
					'votes': result.exhausted.votes.pp(ppDP)
				},
				'loss_fraction': {
					'transfers': result.loss_fraction.transfers.pp(ppDP),
					'votes': result.loss_fraction.votes.pp(ppDP)
				},
				'total': result.total.pp(ppDP),
				'quota': result.quota.pp(ppDP)
			}});
		}
	}
}
