#   pyRCV2: Preferential vote counting
#   Copyright © 2020–2021  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/>.

from pytest import approx
from pytest_steps import test_steps

import pyRCV2.blt
import pyRCV2.numbers
from pyRCV2.method.gregory import WIGSTVCounter
from pyRCV2.model import CandidateState, CountCompleted

def count_until_exclude(counter):
	while True:
		result = counter.step()
		if isinstance(result, CountCompleted):
			return result
		if result.comment.startswith('Exclusion of '):
			return result

@test_steps(*['Round {}'.format(i) for i in range(1, 31)])
def test_csm15():
	"""Compare count of CSM15.blt with model result at https://www.eveonline.com/article/qbtyyo/meet-the-new-council"""
	
	pyRCV2.numbers.set_numclass(pyRCV2.numbers.Native)
	
	with open('tests/data/CSM15.blt', 'r') as f:
		election = pyRCV2.blt.readBLT(f.read())
	
	assert len(election.candidates) == 40
	
	cands = {c.name: c for c in election.candidates}
	
	counter = WIGSTVCounter(election, {
		'exclusion': 'wright',
		'round_quota': 0,
	})
	
	# Round 1
	result = counter.reset()
	assert result.candidates[cands['Merkelchen']].votes.impl == 5325
	assert result.candidates[cands['Gobbins']].votes.impl == 3709
	assert result.candidates[cands['Vily']].votes.impl == 2789
	assert result.candidates[cands['Maria Taylor']].votes.impl == 2556
	assert result.candidates[cands['Brisc Rubal']].votes.impl == 1794
	assert result.candidates[cands['Ikarus Cesaille']].votes.impl == 1471
	assert result.candidates[cands['Phantomite']].votes.impl == 1462
	assert result.candidates[cands['Juvenius Drakonius']].votes.impl == 1375
	assert result.candidates[cands['Torvald Uruz']].votes.impl == 1297
	assert result.candidates[cands['Mike Azariah']].votes.impl == 1242
	assert result.candidates[cands['BlazingBunny']].votes.impl == 1042
	assert result.candidates[cands['ExookiZ']].votes.impl == 961
	assert result.candidates[cands['Loroseco Kross']].votes.impl == 938
	assert result.candidates[cands['Kenneth Feld']].votes.impl == 874
	assert result.candidates[cands['Jurius Doctor']].votes.impl == 747
	assert result.candidates[cands['Stitch Kaneland']].votes.impl == 698
	assert result.candidates[cands['DutchGunner']].votes.impl == 690
	assert result.candidates[cands['Insidious Sainthood']].votes.impl == 583
	assert result.candidates[cands['Alexis Finch']].votes.impl == 526
	assert result.candidates[cands['Steadyo']].votes.impl == 499
	assert result.candidates[cands['Innominate']].votes.impl == 493
	assert result.candidates[cands['Meredith en Thielles']].votes.impl == 477
	assert result.candidates[cands['Ironwulf']].votes.impl == 472
	assert result.candidates[cands['PJHustle']].votes.impl == 443
	assert result.candidates[cands['Darius Caliente']].votes.impl == 442
	assert result.candidates[cands['January Valentine']].votes.impl == 414
	assert result.candidates[cands['The Oz']].votes.impl == 407
	assert result.candidates[cands['Pandora Singularity']].votes.impl == 355
	assert result.candidates[cands['UAxDEATH']].votes.impl == 323
	assert result.candidates[cands['Komi Valentine']].votes.impl == 277
	assert result.candidates[cands['Murray Rothbardo']].votes.impl == 184
	assert result.candidates[cands['Xenuria']].votes.impl == 179
	assert result.candidates[cands['Prospektor Schipplock']].votes.impl == 177
	assert result.candidates[cands['Styxx']].votes.impl == 172
	assert result.candidates[cands['boernl']].votes.impl == 171
	assert result.candidates[cands['Dhuras']].votes.impl == 162
	assert result.candidates[cands['Rheaha Preynar']].votes.impl == 125
	assert result.candidates[cands['Kalen Tsero']].votes.impl == 113
	assert result.candidates[cands['Storm Delay']].votes.impl == 96
	assert result.candidates[cands['Leehams DaWildabeast']].votes.impl == 60
	
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Leehams DaWildabeast']].votes.impl == approx(63.208412)
	assert result.candidates[cands['Leehams DaWildabeast']].state == CandidateState.EXCLUDED
	yield 'Round 1'
	
	# Round 2
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Storm Delay']].votes.impl == approx(112.229279)
	assert result.candidates[cands['Storm Delay']].state == CandidateState.EXCLUDED
	yield 'Round 2'
	
	# Round 3
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Kalen Tsero']].votes.impl == approx(115.888433)
	assert result.candidates[cands['Kalen Tsero']].state == CandidateState.EXCLUDED
	yield 'Round 3'
	
	# Round 4
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Rheaha Preynar']].votes.impl == approx(133.122193)
	assert result.candidates[cands['Rheaha Preynar']].state == CandidateState.EXCLUDED
	yield 'Round 4'
	
	# Round 5
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Dhuras']].votes.impl == approx(170.123665)
	assert result.candidates[cands['Dhuras']].state == CandidateState.EXCLUDED
	yield 'Round 5'
	
	# Round 6
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['boernl']].votes.impl == approx(181.377165)
	assert result.candidates[cands['boernl']].state == CandidateState.EXCLUDED
	yield 'Round 6'
	
	# Round 7
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Styxx']].votes.impl == approx(184.463008)
	assert result.candidates[cands['Styxx']].state == CandidateState.EXCLUDED
	yield 'Round 7'
	
	# Round 8
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Murray Rothbardo']].votes.impl == approx(189.651433)
	assert result.candidates[cands['Murray Rothbardo']].state == CandidateState.EXCLUDED
	yield 'Round 8'
	
	# Round 9
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Xenuria']].votes.impl == approx(198.266912)
	assert result.candidates[cands['Xenuria']].state == CandidateState.EXCLUDED
	yield 'Round 9'
	
	# Round 10
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Prospektor Schipplock']].votes.impl == approx(213.386936)
	assert result.candidates[cands['Prospektor Schipplock']].state == CandidateState.EXCLUDED
	yield 'Round 10'
	
	# Round 11
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Komi Valentine']].votes.impl == approx(336.404306)
	assert result.candidates[cands['Komi Valentine']].state == CandidateState.EXCLUDED
	yield 'Round 11'
	
	# Round 12
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['UAxDEATH']].votes.impl == approx(357.732024)
	assert result.candidates[cands['UAxDEATH']].state == CandidateState.EXCLUDED
	yield 'Round 12'
	
	# Round 13
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Pandora Singularity']].votes.impl == approx(413.808353)
	assert result.candidates[cands['Pandora Singularity']].state == CandidateState.EXCLUDED
	yield 'Round 13'
	
	# Round 14
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['PJHustle']].votes.impl == approx(478.737771)
	assert result.candidates[cands['PJHustle']].state == CandidateState.EXCLUDED
	yield 'Round 14'
	
	# Round 15
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Ironwulf']].votes.impl == approx(528.845712)
	assert result.candidates[cands['Ironwulf']].state == CandidateState.EXCLUDED
	yield 'Round 15'
	
	# Round 16
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['January Valentine']].votes.impl == approx(557.154707)
	assert result.candidates[cands['January Valentine']].state == CandidateState.EXCLUDED
	yield 'Round 16'
	
	# Round 17
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Darius Caliente']].votes.impl == approx(620.307162)
	assert result.candidates[cands['Darius Caliente']].state == CandidateState.EXCLUDED
	yield 'Round 17'
	
	# Round 18
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Alexis Finch']].votes.impl == approx(630.517365)
	assert result.candidates[cands['Alexis Finch']].state == CandidateState.EXCLUDED
	yield 'Round 18'
	
	# Round 19
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['The Oz']].votes.impl == approx(673.241462)
	assert result.candidates[cands['The Oz']].state == CandidateState.EXCLUDED
	yield 'Round 19'
	
	# Round 20
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Steadyo']].votes.impl == approx(693.622387)
	assert result.candidates[cands['Steadyo']].state == CandidateState.EXCLUDED
	yield 'Round 20'
	
	# Round 21
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Insidious Sainthood']].votes.impl == approx(711.112702)
	assert result.candidates[cands['Insidious Sainthood']].state == CandidateState.EXCLUDED
	yield 'Round 21'
	
	# Round 22
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Meredith en Thielles']].votes.impl == approx(773.804223)
	assert result.candidates[cands['Meredith en Thielles']].state == CandidateState.EXCLUDED
	yield 'Round 22'
	
	# Round 23
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Stitch Kaneland']].votes.impl == approx(944.597498)
	assert result.candidates[cands['Stitch Kaneland']].state == CandidateState.EXCLUDED
	yield 'Round 23'
	
	# Round 24
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['DutchGunner']].votes.impl == approx(1056.847624)
	assert result.candidates[cands['DutchGunner']].state == CandidateState.EXCLUDED
	yield 'Round 24'
	
	# Round 25
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Jurius Doctor']].votes.impl == approx(1254.134642)
	assert result.candidates[cands['Jurius Doctor']].state == CandidateState.EXCLUDED
	yield 'Round 25'
	
	# Round 26
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['BlazingBunny']].votes.impl == approx(1448.527563)
	assert result.candidates[cands['BlazingBunny']].state == CandidateState.EXCLUDED
	yield 'Round 26'
	
	# Round 27
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['ExookiZ']].votes.impl == approx(1527.088745)
	assert result.candidates[cands['ExookiZ']].state == CandidateState.EXCLUDED
	yield 'Round 27'
	
	# Round 28
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Juvenius Drakonius']].votes.impl == approx(1614.455637)
	assert result.candidates[cands['Juvenius Drakonius']].state == CandidateState.EXCLUDED
	yield 'Round 28'
	
	# Round 29
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Ikarus Cesaille']].votes.impl == approx(1938.970656)
	assert result.candidates[cands['Ikarus Cesaille']].state == CandidateState.EXCLUDED
	yield 'Round 29'
	
	# Round 30
	result = count_until_exclude(counter)
	assert counter.step_results[-2].candidates[cands['Loroseco Kross']].votes.impl == approx(2306.134123)
	assert result.candidates[cands['Loroseco Kross']].state == CandidateState.EXCLUDED
	
	result = counter.step()
	assert result.comment == 'Bulk election'
	assert result.candidates[cands['Vily']].state == CandidateState.PROVISIONALLY_ELECTED
	assert result.candidates[cands['Phantomite']].state == CandidateState.PROVISIONALLY_ELECTED
	assert result.candidates[cands['Kenneth Feld']].state == CandidateState.PROVISIONALLY_ELECTED
	assert result.candidates[cands['Merkelchen']].state == CandidateState.PROVISIONALLY_ELECTED
	assert result.candidates[cands['Maria Taylor']].state == CandidateState.PROVISIONALLY_ELECTED
	assert result.candidates[cands['Brisc Rubal']].state == CandidateState.PROVISIONALLY_ELECTED
	assert result.candidates[cands['Gobbins']].state == CandidateState.PROVISIONALLY_ELECTED
	assert result.candidates[cands['Torvald Uruz']].state == CandidateState.PROVISIONALLY_ELECTED
	assert result.candidates[cands['Mike Azariah']].state == CandidateState.PROVISIONALLY_ELECTED
	assert result.candidates[cands['Innominate']].state == CandidateState.PROVISIONALLY_ELECTED
	
	result = counter.step()
	assert isinstance(result, CountCompleted)
	yield 'Round 30'
