/*  OpenTally: Open-source election vote counting
 *  Copyright © 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/>.
 */

#![allow(mutable_borrow_reservation_conflict)]

//#[cfg(target_arch = "wasm32")]
pub mod wasm;

use crate::numbers::Number;
use crate::election::{Candidate, CandidateState, CountCard, CountState, Parcel, Vote};

use wasm_bindgen::prelude::wasm_bindgen;

use std::cmp::max;
use std::collections::HashMap;
use std::ops;

#[wasm_bindgen]
pub struct STVOptions {
	pub round_tvs: Option<usize>,
	pub round_weights: Option<usize>,
	pub round_votes: Option<usize>,
	pub round_quota: Option<usize>,
	pub quota: QuotaType,
	pub quota_criterion: QuotaCriterion,
	pub quota_mode: QuotaMode,
	pub surplus: SurplusMethod,
	pub surplus_order: SurplusOrder,
	pub transferable_only: bool,
	pub exclusion: ExclusionMethod,
	pub bulk_exclude: bool,
	pub defer_surpluses: bool,
	pub pp_decimals: usize,
}

#[wasm_bindgen]
impl STVOptions {
	pub fn new(
		round_tvs: Option<usize>,
		round_weights: Option<usize>,
		round_votes: Option<usize>,
		round_quota: Option<usize>,
		quota: &str,
		quota_criterion: &str,
		quota_mode: &str,
		surplus: &str,
		surplus_order: &str,
		transferable_only: bool,
		exclusion: &str,
		bulk_exclude: bool,
		defer_surpluses: bool,
		pp_decimals: usize,
	) -> Self {
		return STVOptions {
			round_tvs,
			round_weights,
			round_votes,
			round_quota,
			quota: match quota {
				"droop" => QuotaType::Droop,
				"hare" => QuotaType::Hare,
				"droop_exact" => QuotaType::DroopExact,
				"hare_exact" => QuotaType::HareExact,
				_ => panic!("Invalid --quota"),
			},
			quota_criterion: match quota_criterion {
				"geq" => QuotaCriterion::GreaterOrEqual,
				"gt" => QuotaCriterion::Greater,
				_ => panic!("Invalid --quota-criterion"),
			},
			quota_mode: match quota_mode {
				"static" => QuotaMode::Static,
				"ers97" => QuotaMode::ERS97,
				_ => panic!("Invalid --quota-mode"),
			},
			surplus: match surplus {
				"wig" => SurplusMethod::WIG,
				"uig" => SurplusMethod::UIG,
				"eg" => SurplusMethod::EG,
				"meek" => SurplusMethod::Meek,
				_ => panic!("Invalid --surplus"),
			},
			surplus_order: match surplus_order {
				"by_size" => SurplusOrder::BySize,
				"by_order" => SurplusOrder::ByOrder,
				_ => panic!("Invalid --surplus-order"),
			},
			transferable_only,
			exclusion: match exclusion {
				"single_stage" => ExclusionMethod::SingleStage,
				"by_value" => ExclusionMethod::ByValue,
				"parcels_by_order" => ExclusionMethod::ParcelsByOrder,
				_ => panic!("Invalid --exclusion"),
			},
			bulk_exclude,
			defer_surpluses,
			pp_decimals,
		};
	}
}

impl STVOptions {
	// Not exported to WebAssembly
	pub fn describe<N: Number>(&self) -> String {
		let mut flags = Vec::new();
		flags.push(N::describe());
		if let Some(dps) = self.round_tvs { flags.push(format!("--round-tvs {}", dps)); }
		if let Some(dps) = self.round_weights { flags.push(format!("--round-weights {}", dps)); }
		if let Some(dps) = self.round_votes { flags.push(format!("--round-votes {}", dps)); }
		if let Some(dps) = self.round_quota { flags.push(format!("--round-quota {}", dps)); }
		if self.quota != QuotaType::Droop { flags.push(self.quota.describe()); }
		if self.quota_criterion != QuotaCriterion::GreaterOrEqual { flags.push(self.quota_criterion.describe()); }
		if self.quota_mode != QuotaMode::Static { flags.push(self.quota_mode.describe()); }
		if self.surplus != SurplusMethod::WIG { flags.push(self.surplus.describe()); }
		if self.surplus_order != SurplusOrder::BySize { flags.push(self.surplus_order.describe()); }
		if self.transferable_only { flags.push("--transferable-only".to_string()); }
		if self.exclusion != ExclusionMethod::SingleStage { flags.push(self.exclusion.describe()); }
		if self.pp_decimals != 2 { flags.push(format!("--pp-decimals {}", self.pp_decimals)); }
		return flags.join(" ");
	}
}

#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum QuotaType {
	Droop,
	Hare,
	DroopExact,
	HareExact,
}

impl QuotaType {
	fn describe(self) -> String {
		match self {
			QuotaType::Droop => "--quota droop",
			QuotaType::Hare => "--quota hare",
			QuotaType::DroopExact => "--quota droop_exact",
			QuotaType::HareExact => "--quota hare_exact",
		}.to_string()
	}
}

#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum QuotaCriterion {
	GreaterOrEqual,
	Greater,
}

impl QuotaCriterion {
	fn describe(self) -> String {
		match self {
			QuotaCriterion::GreaterOrEqual => "--quota-criterion geq",
			QuotaCriterion::Greater => "--quota-criterion gt",
		}.to_string()
	}
}

#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum QuotaMode {
	Static,
	ERS97,
}

impl QuotaMode {
	fn describe(self) -> String {
		match self {
			QuotaMode::Static => "--quota-mode static",
			QuotaMode::ERS97 => "--quota-mode ers97",
		}.to_string()
	}
}

#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum SurplusMethod {
	WIG,
	UIG,
	EG,
	Meek,
}

impl SurplusMethod {
	fn describe(self) -> String {
		match self {
			SurplusMethod::WIG => "--surplus wig",
			SurplusMethod::UIG => "--surplus uig",
			SurplusMethod::EG => "--surplus eg",
			SurplusMethod::Meek => "--surplus meek",
		}.to_string()
	}
}

#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum SurplusOrder {
	BySize,
	ByOrder,
}

impl SurplusOrder {
	fn describe(self) -> String {
		match self {
			SurplusOrder::BySize => "--surplus-order by_size",
			SurplusOrder::ByOrder => "--surplus-order by_order",
		}.to_string()
	}
}

#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum ExclusionMethod {
	SingleStage,
	ByValue,
	ParcelsByOrder,
}

impl ExclusionMethod {
	fn describe(self) -> String {
		match self {
			ExclusionMethod::SingleStage => "--exclusion single_stage",
			ExclusionMethod::ByValue => "--exclusion by_value",
			ExclusionMethod::ParcelsByOrder => "--exclusion parcels_by_order",
		}.to_string()
	}
}

pub fn count_init<N: Number>(mut state: &mut CountState<'_, N>, opts: &STVOptions) {
	distribute_first_preferences(&mut state);
	calculate_quota(&mut state, opts);
	elect_meeting_quota(&mut state, opts);
}

pub fn count_one_stage<'a, N: Number>(mut state: &mut CountState<'a, N>, opts: &STVOptions) -> bool
where
	for<'r> &'r N: ops::Sub<&'r N, Output=N>,
	for<'r> &'r N: ops::Div<&'r N, Output=N>,
	for<'r> &'r N: ops::Neg<Output=N>,
{
	state.logger.entries.clear();
	state.step_all();
	
	// Finish count
	if finished_before_stage(&state) {
		return true;
	}
	
	// Continue exclusions
	if continue_exclusion(&mut state, &opts) {
		calculate_quota(&mut state, opts);
		elect_meeting_quota(&mut state, opts);
		return false;
	}
	
	// Distribute surpluses
	if distribute_surpluses(&mut state, &opts) {
		calculate_quota(&mut state, opts);
		elect_meeting_quota(&mut state, opts);
		return false;
	}
	
	// Attempt bulk election
	if bulk_elect(&mut state) {
		return false;
	}
	
	// Exclude lowest hopeful
	if exclude_hopefuls(&mut state, &opts) {
		calculate_quota(&mut state, opts);
		elect_meeting_quota(&mut state, opts);
		return false;
	}
	
	todo!();
}

struct NextPreferencesResult<'a, N> {
	candidates: HashMap<&'a Candidate, NextPreferencesEntry<'a, N>>,
	exhausted: NextPreferencesEntry<'a, N>,
	total_ballots: N,
	total_votes: N,
}

struct NextPreferencesEntry<'a, N> {
	//count_card: Option<&'a CountCard<'a, N>>,
	votes: Vec<Vote<'a, N>>,
	num_ballots: N,
	num_votes: N,
}

fn next_preferences<'a, N: Number>(state: &CountState<'a, N>, votes: Vec<Vote<'a, N>>) -> NextPreferencesResult<'a, N> {
	let mut result = NextPreferencesResult {
		candidates: HashMap::new(),
		exhausted: NextPreferencesEntry {
			votes: Vec::new(),
			num_ballots: N::new(),
			num_votes: N::new(),
		},
		total_ballots: N::new(),
		total_votes: N::new(),
	};
	
	for mut vote in votes.into_iter() {
		result.total_ballots += &vote.ballot.orig_value;
		result.total_votes += &vote.value;
		
		let mut next_candidate = None;
		
		for (i, preference) in vote.ballot.preferences.iter().enumerate().skip(vote.up_to_pref) {
			let candidate = &state.election.candidates[*preference];
			let count_card = state.candidates.get(candidate).unwrap();
			
			if let CandidateState::HOPEFUL | CandidateState::GUARDED = count_card.state {
				next_candidate = Some(candidate);
				vote.up_to_pref = i + 1;
				break;
			}
		}
		
		// Have to structure like this to satisfy Rust's borrow checker
		if let Some(candidate) = next_candidate {
			if result.candidates.contains_key(candidate) {
				let entry = result.candidates.get_mut(candidate).unwrap();
				entry.num_ballots += &vote.ballot.orig_value;
				entry.num_votes += &vote.value;
				entry.votes.push(vote);
			} else {
				let entry = NextPreferencesEntry {
					num_ballots: vote.ballot.orig_value.clone(),
					num_votes: vote.value.clone(),
					votes: vec![vote],
				};
				result.candidates.insert(candidate, entry);
			}
		} else {
			result.exhausted.num_ballots += &vote.ballot.orig_value;
			result.exhausted.num_votes += &vote.value;
			result.exhausted.votes.push(vote);
		}
	}
	
	return result;
}

fn distribute_first_preferences<N: Number>(state: &mut CountState<N>) {
	let votes = state.election.ballots.iter().map(|b| Vote {
		ballot: b,
		value: b.orig_value.clone(),
		up_to_pref: 0,
	}).collect();
	
	let result = next_preferences(state, votes);
	
	// Transfer candidate votes
	for (candidate, entry) in result.candidates.into_iter() {
		let parcel = entry.votes as Parcel<N>;
		let count_card = state.candidates.get_mut(candidate).unwrap();
		count_card.parcels.push(parcel);
		count_card.transfer(&entry.num_votes);
	}
	
	// Transfer exhausted votes
	let parcel = result.exhausted.votes as Parcel<N>;
	state.exhausted.parcels.push(parcel);
	state.exhausted.transfer(&result.exhausted.num_votes);
	
	state.kind = None;
	state.title = "First preferences".to_string();
	state.logger.log_literal("First preferences distributed.".to_string());
}

fn total_to_quota<N: Number>(mut total: N, seats: usize, opts: &STVOptions) -> N {
	match opts.quota {
		QuotaType::Droop | QuotaType::DroopExact => {
			total /= N::from(seats + 1);
		}
		QuotaType::Hare | QuotaType::HareExact => {
			total /= N::from(seats);
		}
	}
	
	if let Some(dps) = opts.round_quota {
		match opts.quota {
			QuotaType::Droop | QuotaType::Hare => {
				// Increment to next available increment
				let mut factor = N::from(10);
				factor.pow_assign(dps as i32);
				total *= &factor;
				total.floor_mut(0);
				total += N::one();
				total /= factor;
			}
			QuotaType::DroopExact | QuotaType::HareExact => {
				// Round up to next available increment if necessary
				total.ceil_mut(dps);
			}
		}
	}
	
	return total;
}

fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
	// Calculate quota
	if let None = state.quota {
		let mut log = String::new();
		
		// Calculate the total vote
		let total_vote = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes });
		log.push_str(format!("{:.dps$} usable votes, so the quota is ", total_vote, dps=opts.pp_decimals).as_str());
		
		let quota = total_to_quota(total_vote, state.election.seats, opts);
		
		log.push_str(format!("{:.dps$}.", quota, dps=opts.pp_decimals).as_str());
		state.quota = Some(quota);
		state.logger.log_literal(log);
	}
	
	if let QuotaMode::ERS97 = opts.quota_mode {
		// ERS97 rules
		
		// -------------------------
		// Reduce quota if allowable
		
		if state.num_elected == 0 {
			let mut log = String::new();
			
			// Calculate the total vote
			let total_vote = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes });
			log.push_str(format!("{:.dps$} usable votes, so the quota is reduced to ", total_vote, dps=opts.pp_decimals).as_str());
			
			let quota = total_to_quota(total_vote, state.election.seats, opts);
			
			if &quota < state.quota.as_ref().unwrap() {
				log.push_str(format!("{:.dps$}.", quota, dps=opts.pp_decimals).as_str());
				state.quota = Some(quota);
				state.logger.log_literal(log);
			}
		}
		
		// ------------------------------------
		// Calculate vote required for election
		
		if state.num_elected < state.election.seats {
			let mut log = String::new();
			
			// Calculate total active vote
			let total_active_vote = state.candidates.values().fold(N::zero(), |acc, cc| {
				match cc.state {
					CandidateState::ELECTED => { if &cc.votes > state.quota.as_ref().unwrap() { acc + &cc.votes - state.quota.as_ref().unwrap() } else { acc } }
					_ => { acc + &cc.votes }
				}
			});
			log.push_str(format!("Total active vote is {:.dps$}, so the vote required for election is ", total_active_vote, dps=opts.pp_decimals).as_str());
			
			let vote_req = total_to_quota(total_active_vote, state.election.seats - state.num_elected, opts);
			
			if &vote_req < state.quota.as_ref().unwrap() {
				// VRE is less than the quota
				if let Some(v) = &state.vote_required_election {
					if &vote_req != v {
						log.push_str(format!("{:.dps$}.", vote_req, dps=opts.pp_decimals).as_str());
						state.vote_required_election = Some(vote_req);
						state.logger.log_literal(log);
					}
				} else {
					log.push_str(format!("{:.dps$}.", vote_req, dps=opts.pp_decimals).as_str());
					state.vote_required_election = Some(vote_req);
					state.logger.log_literal(log);
				}
			} else {
				// VRE is not less than the quota, so use the quota
				state.vote_required_election = state.quota.clone();
			}
		}
	} else {
		// No ERS97 rules
		if let None = state.vote_required_election {
			state.vote_required_election = state.quota.clone();
		}
	}
}

fn meets_quota<N: Number>(quota: &N, count_card: &CountCard<N>, opts: &STVOptions) -> bool {
	match opts.quota_criterion {
		QuotaCriterion::GreaterOrEqual => {
			return count_card.votes >= *quota;
		}
		QuotaCriterion::Greater => {
			return count_card.votes > *quota;
		}
	}
}

fn elect_meeting_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
	let vote_req = state.vote_required_election.as_ref().unwrap(); // Have to do this or else the borrow checker gets confused
	
	let mut cands_meeting_quota: Vec<&Candidate> = state.election.candidates.iter()
		.filter(|c| { let cc = state.candidates.get(c).unwrap(); cc.state == CandidateState::HOPEFUL && meets_quota(vote_req, cc, opts) })
		.collect();
	
	if cands_meeting_quota.len() > 0 {
		// Sort by votes
		cands_meeting_quota.sort_unstable_by(|a, b| state.candidates.get(a).unwrap().votes.partial_cmp(&state.candidates.get(b).unwrap().votes).unwrap());
		
		// Declare elected in descending order of votes
		for candidate in cands_meeting_quota.into_iter().rev() {
			let count_card = state.candidates.get_mut(candidate).unwrap();
			count_card.state = CandidateState::ELECTED;
			state.num_elected += 1;
			count_card.order_elected = state.num_elected as isize;
			state.logger.log_smart(
				"{} meets the quota and is elected.",
				"{} meet the quota and are elected.",
				vec![&candidate.name]
			);
			
			if opts.quota_mode == QuotaMode::ERS97 {
				// Vote required for election may have changed
				calculate_quota(state, opts);
			}
		}
		
		if opts.quota_mode == QuotaMode::ERS97 {
			// Repeat in case vote required for election has changed
			//calculate_quota(state, opts);
			elect_meeting_quota(state, opts);
		}
	}
}

fn can_defer_surpluses<N: Number>(state: &CountState<N>, opts: &STVOptions, total_surpluses: &N) -> bool
where
	for<'r> &'r N: ops::Sub<&'r N, Output=N>
{
	// Do not defer if this could change the last 2 candidates
	let mut hopefuls: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter()
		.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL || cc.state == CandidateState::GUARDED)
		.collect();
	hopefuls.sort_unstable_by(|(_, cc1), (_, cc2)| cc1.votes.cmp(&cc2.votes));
	if total_surpluses > &(&hopefuls[1].1.votes - &hopefuls[0].1.votes) {
		return false;
	}
	
	// Do not defer if this could affect a bulk exclusion
	if opts.bulk_exclude {
		let to_exclude = hopefuls_to_bulk_exclude(state, opts);
		let num_to_exclude = to_exclude.len();
		if num_to_exclude > 0 {
			let total_excluded = to_exclude.into_iter()
				.fold(N::new(), |acc, c| acc + &state.candidates.get(c).unwrap().votes);
			if total_surpluses > &(&hopefuls[num_to_exclude + 1].1.votes - &total_excluded) {
				return false;
			}
		}
	}
	return true;
}

fn distribute_surpluses<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> bool
where
	for<'r> &'r N: ops::Sub<&'r N, Output=N>,
	for<'r> &'r N: ops::Neg<Output=N>
{
	let quota = state.quota.as_ref().unwrap();
	let mut has_surplus: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter()
		.filter(|(_, cc)| &cc.votes > quota)
		.collect();
	let total_surpluses = has_surplus.iter()
		.fold(N::new(), |acc, (_, cc)| acc + &cc.votes - quota);
		
	if has_surplus.len() > 0 {
		// Determine if surplues can be deferred
		if opts.defer_surpluses {
			if can_defer_surpluses(state, opts, &total_surpluses) {
				state.logger.log_literal(format!("Distribution of surpluses totalling {:.2} votes will be deferred.", total_surpluses));
				return false;
			}
		}
		
		match opts.surplus_order {
			SurplusOrder::BySize => {
				// Compare b with a to sort high-low
				has_surplus.sort_unstable_by(|a, b| b.1.votes.partial_cmp(&a.1.votes).unwrap());
			}
			SurplusOrder::ByOrder => {
				has_surplus.sort_unstable_by(|a, b| a.1.order_elected.partial_cmp(&b.1.order_elected).unwrap());
			}
		}
		
		// Distribute top candidate's surplus
		// TODO: Handle ties
		let elected_candidate = has_surplus.first_mut().unwrap().0;
		distribute_surplus(state, &opts, elected_candidate);
		
		return true;
	}
	return false;
}

/// Return the denominator of the transfer value
fn calculate_transfer_denom<N: Number>(surplus: &N, result: &NextPreferencesResult<N>, transferable_votes: &N, weighted: bool, transferable_only: bool) -> Option<N>
where
	for<'r> &'r N: ops::Sub<&'r N, Output=N>
{
	if transferable_only {
		let total_units = if weighted { &result.total_votes } else { &result.total_ballots };
		let exhausted_units = if weighted { &result.exhausted.num_votes } else { &result.exhausted.num_ballots };
		let transferable_units = total_units - exhausted_units;
		
		if transferable_votes > surplus {
			return Some(transferable_units);
		} else {
			return None;
		}
	} else {
		if weighted {
			return Some(result.total_votes.clone());
		} else {
			return Some(result.total_ballots.clone());
		}
	}
}

fn reweight_vote<N: Number>(
	num_votes: &N,
	num_ballots: &N,
	surplus: &N,
	weighted: bool,
	transfer_value: &Option<N>,
	transfer_denom: &Option<N>,
	round_tvs: Option<usize>,
	rounding: Option<usize>) -> N
{
	let mut result;
	
	match transfer_denom {
		Some(v) => {
			if let Some(_) = round_tvs {
				// Rounding requested: use the rounded transfer value
				if weighted {
					result = num_votes.clone() * transfer_value.as_ref().unwrap();
				} else {
					result = num_ballots.clone() * transfer_value.as_ref().unwrap();
				}
			} else {
				// Avoid unnecessary rounding error by first multiplying by the surplus
				if weighted {
					result = num_votes.clone() * surplus / v;
				} else {
					result = num_ballots.clone() * surplus / v;
				}
			}
		}
		None => {
			result = num_votes.clone();
		}
	}
	
	// Round down if requested
	if let Some(dps) = rounding {
		result.floor_mut(dps);
	}
	
	return result;
}

fn distribute_surplus<N: Number>(state: &mut CountState<N>, opts: &STVOptions, elected_candidate: &Candidate)
where
	for<'r> &'r N: ops::Sub<&'r N, Output=N>,
	for<'r> &'r N: ops::Neg<Output=N>
{
	let count_card = state.candidates.get(elected_candidate).unwrap();
	let surplus = &count_card.votes - state.quota.as_ref().unwrap();
	
	let votes;
	match opts.surplus {
		SurplusMethod::WIG | SurplusMethod::UIG => {
			// Inclusive Gregory
			votes = state.candidates.get(elected_candidate).unwrap().parcels.concat();
		}
		SurplusMethod::EG => {
			// Exclusive Gregory
			// Should be safe to unwrap() - or else how did we get a quota!
			votes = state.candidates.get_mut(elected_candidate).unwrap().parcels.pop().unwrap();
		}
		SurplusMethod::Meek => {
			todo!();
		}
	}
	
	// Count next preferences
	let result = next_preferences(state, votes);
	
	state.kind = Some("Surplus of");
	state.title = String::from(&elected_candidate.name);
	
	// Transfer candidate votes
	// TODO: Refactor??
	let is_weighted = match opts.surplus {
		SurplusMethod::WIG => { true }
		SurplusMethod::UIG | SurplusMethod::EG => { false }
		SurplusMethod::Meek => { todo!() }
	};
	
	let transferable_votes = &result.total_votes - &result.exhausted.num_votes;
	let transfer_denom = calculate_transfer_denom(&surplus, &result, &transferable_votes, is_weighted, opts.transferable_only);
	let mut transfer_value;
	match transfer_denom {
		Some(ref v) => {
			transfer_value = Some(surplus.clone() / v);
			
			// Round down if requested
			if let Some(dps) = opts.round_tvs {
				transfer_value.as_mut().unwrap().floor_mut(dps);
			}
			
			state.logger.log_literal(format!("Surplus of {} distributed at value {:.dps2$}.", elected_candidate.name, transfer_value.as_ref().unwrap(), dps2=max(opts.pp_decimals, 2)));
		}
		None => {
			transfer_value = None;
			state.logger.log_literal(format!("Surplus of {} distributed at values received.", elected_candidate.name));
		}
	}
	
	let mut checksum = N::new();
	
	for (candidate, entry) in result.candidates.into_iter() {
		let mut parcel = entry.votes as Parcel<N>;
		
		// Reweight votes
		for vote in parcel.iter_mut() {
			vote.value = reweight_vote(&vote.value, &vote.ballot.orig_value, &surplus, is_weighted, &transfer_value, &transfer_denom, opts.round_tvs, opts.round_weights);
		}
		
		let count_card = state.candidates.get_mut(candidate).unwrap();
		count_card.parcels.push(parcel);
		
		let candidate_transfers = reweight_vote(&entry.num_votes, &entry.num_ballots, &surplus, is_weighted, &transfer_value, &transfer_denom, opts.round_tvs, opts.round_votes);
		count_card.transfer(&candidate_transfers);
		checksum += candidate_transfers;
	}
	
	// Transfer exhausted votes
	let parcel = result.exhausted.votes as Parcel<N>;
	state.exhausted.parcels.push(parcel);
	
	let mut exhausted_transfers;
	if opts.transferable_only {
		if transferable_votes > surplus {
			// No ballots exhaust
			exhausted_transfers = N::new();
		} else {
			exhausted_transfers = &surplus - &transferable_votes;
		}
	} else {
		exhausted_transfers = reweight_vote(&result.exhausted.num_votes, &result.exhausted.num_ballots, &surplus, is_weighted, &transfer_value, &transfer_denom, opts.round_tvs, opts.round_votes);
	}
	
	if let Some(dps) = opts.round_votes {
		exhausted_transfers.floor_mut(dps);
	}
	state.exhausted.transfer(&exhausted_transfers);
	checksum += exhausted_transfers;
	
	// Finalise candidate votes
	let count_card = state.candidates.get_mut(elected_candidate).unwrap();
	count_card.transfers = -&surplus;
	count_card.votes.assign(state.quota.as_ref().unwrap());
	checksum -= surplus;
	
	// Update loss by fraction
	state.loss_fraction.transfer(&-checksum);
}

fn bulk_elect<N: Number>(state: &mut CountState<N>) -> bool {
	if state.election.candidates.len() - state.num_excluded <= state.election.seats {
		state.kind = None;
		state.title = "Bulk election".to_string();
		
		// Bulk elect all remaining candidates
		let mut hopefuls: Vec<(&&Candidate, &mut CountCard<N>)> = state.candidates.iter_mut()
			.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL)
			.collect();
		
		// TODO: Handle ties
		hopefuls.sort_unstable_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap());
		
		for (candidate, count_card) in hopefuls.into_iter() {
			count_card.state = CandidateState::ELECTED;
			state.num_elected += 1;
			count_card.order_elected = state.num_elected as isize;
			
			state.logger.log_smart(
				"{} is elected to fill the remaining vacancy.",
				"{} are elected to fill the remaining vacancies.",
				vec![&candidate.name]
			);
		}
		
		return true;
	}
	return false;
}

fn hopefuls_to_bulk_exclude<'a, N: Number>(state: &CountState<'a, N>, _opts: &STVOptions) -> Vec<&'a Candidate> {
	let mut excluded_candidates = Vec::new();
	
	let mut hopefuls: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter()
		.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL)
		.collect();
	
	// Sort by votes
	// TODO: Handle ties
	hopefuls.sort_unstable_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap());
	
	let total_surpluses = state.candidates.iter()
		.filter(|(_, cc)| &cc.votes > state.quota.as_ref().unwrap())
		.fold(N::new(), |agg, (_, cc)| agg + &cc.votes - state.quota.as_ref().unwrap());
	
	// Attempt to exclude as many candidates as possible
	for i in 0..hopefuls.len() {
		let try_exclude = &hopefuls[0..hopefuls.len()-i];
		
		// Do not exclude if this splits tied candidates
		if i != 0 && try_exclude.last().unwrap().1.votes == hopefuls[hopefuls.len()-i].1.votes {
			continue;
		}
		
		// Do not exclude if this leaves insufficient candidates
		if state.num_elected + hopefuls.len() - try_exclude.len() < state.election.seats {
			continue;
		}
		
		// Do not exclude if this could change the order of exclusion
		let total_votes = try_exclude.into_iter().fold(N::new(), |agg, (_, cc)| agg + &cc.votes);
		if i != 0 && total_votes + &total_surpluses > hopefuls[hopefuls.len()-i].1.votes {
			continue;
		}
		
		for (c, _) in try_exclude.into_iter() {
			excluded_candidates.push(**c);
		}
		break;
	}
	
	return excluded_candidates;
}

fn exclude_hopefuls<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> bool
where
	for<'r> &'r N: ops::Div<&'r N, Output=N>,
{
	let mut excluded_candidates: Vec<&Candidate> = Vec::new();
	
	// Attempt a bulk exclusion
	if opts.bulk_exclude {
		excluded_candidates = hopefuls_to_bulk_exclude(state, opts);
	}
	
	// Exclude lowest ranked candidate
	if excluded_candidates.len() == 0 {
		let mut hopefuls: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter()
		.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL)
		.collect();
		
		// Sort by votes
		// TODO: Handle ties
		hopefuls.sort_unstable_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap());
		
		excluded_candidates = vec![&hopefuls.first().unwrap().0];
	}
	
	let mut names: Vec<&str> = excluded_candidates.iter().map(|c| c.name.as_str()).collect();
	names.sort();
	state.kind = Some("Exclusion of");
	state.title = names.join(", ");
	state.logger.log_smart(
		"No surpluses to distribute, so {} is excluded.",
		"No surpluses to distribute, so {} are excluded.",
		names
	);
	
	exclude_candidates(state, opts, excluded_candidates);
	
	return true;
}

fn continue_exclusion<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> bool
where
	for<'r> &'r N: ops::Div<&'r N, Output=N>,
{
	// Cannot filter by raw vote count, as candidates may have 0.00 votes but still have recorded ballot papers
	let mut excluded_with_votes: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter()
		//.filter(|(_, cc)| cc.state == CandidateState::EXCLUDED && !cc.votes.is_zero())
		.filter(|(_, cc)| cc.state == CandidateState::EXCLUDED && cc.parcels.iter().any(|p| p.len() > 0))
		.collect();
	
	if excluded_with_votes.len() > 0 {
		excluded_with_votes.sort_unstable_by(|a, b| a.1.order_elected.partial_cmp(&b.1.order_elected).unwrap());
		
		let order_excluded = excluded_with_votes.first().unwrap().1.order_elected;
		let excluded_candidates: Vec<&Candidate> = excluded_with_votes.into_iter()
			.filter(|(_, cc)| cc.order_elected == order_excluded)
			.map(|(c, _)| *c)
			.collect();
		
		let mut names: Vec<&str> = excluded_candidates.iter().map(|c| c.name.as_str()).collect();
		names.sort();
		state.kind = Some("Exclusion of");
		state.title = names.join(", ");
		state.logger.log_smart(
			"Continuing exclusion of {}.",
			"Continuing exclusion of {}.",
			names
		);
		
		exclude_candidates(state, opts, excluded_candidates);
		return true;
	}
	
	return false;
}

fn exclude_candidates<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, excluded_candidates: Vec<&'a Candidate>)
where
	for<'r> &'r N: ops::Div<&'r N, Output=N>,
{
	// Used to give bulk excluded candidate the same order_elected
	let order_excluded = state.num_excluded + 1;
	
	for excluded_candidate in excluded_candidates.iter() {
		let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
		
		// Rust borrow checker is unhappy if we try to put this in exclude_hopefuls ??!
		if count_card.state != CandidateState::EXCLUDED {
			count_card.state = CandidateState::EXCLUDED;
			state.num_excluded += 1;
			count_card.order_elected = -(order_excluded as isize);
		}
	}
	
	// Determine votes to transfer in this stage
	let mut votes = Vec::new();
	let mut votes_remain;
	let mut checksum = N::new();
	
	match opts.exclusion {
		ExclusionMethod::SingleStage => {
			// Exclude in one round
			for excluded_candidate in excluded_candidates.iter() {
				let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
				votes.append(&mut count_card.parcels.concat());
				count_card.parcels.clear();
				
				// Update votes
				let votes_transferred = votes.iter().fold(N::new(), |acc, v| acc + &v.value);
				checksum -= &votes_transferred;
				count_card.transfer(&-votes_transferred);
			}
			votes_remain = false;
		}
		ExclusionMethod::ByValue => {
			// Exclude by value
			let max_value = excluded_candidates.iter()
				.map(|c| state.candidates.get(c).unwrap().parcels.iter()
					.map(|p| p.iter().map(|v| &v.value / &v.ballot.orig_value).max().unwrap())
					.max().unwrap())
				.max().unwrap();
			
			votes_remain = false;
			
			for excluded_candidate in excluded_candidates.iter() {
				let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
				
				// Filter out just those votes with max_value
				let mut remaining_votes = Vec::new();
				
				let cand_votes = count_card.parcels.concat();
				
				let mut votes_transferred = N::new();
				for vote in cand_votes.into_iter() {
					if &vote.value / &vote.ballot.orig_value == max_value {
						votes_transferred += &vote.value;
						votes.push(vote);
					} else {
						remaining_votes.push(vote);
					}
				}
				
				if remaining_votes.len() > 0 {
					votes_remain = true;
				}
				
				// Leave remaining votes with candidate (as one parcel)
				count_card.parcels = vec![remaining_votes];
				
				// Update votes
				checksum -= &votes_transferred;
				count_card.transfer(&-votes_transferred);
			}
		}
		ExclusionMethod::ParcelsByOrder => {
			// Exclude by parcel by order
			if excluded_candidates.len() > 1 {
				panic!("--exclusion parcels_by_order is incompatible with --bulk-exclude");
			}
			
			let count_card = state.candidates.get_mut(excluded_candidates.first().unwrap()).unwrap();
			votes = count_card.parcels.remove(0);
			votes_remain = count_card.parcels.len() > 0;
			
			// Update votes
			let votes_transferred = votes.iter().fold(N::new(), |acc, v| acc + &v.value);
			checksum -= &votes_transferred;
			count_card.transfer(&-votes_transferred);
		}
	}
	
	if votes.len() > 0 {
		let value = &votes[0].value / &votes[0].ballot.orig_value;
		
		// Count next preferences
		let result = next_preferences(state, votes);
		
		if let ExclusionMethod::SingleStage = opts.exclusion {
			state.logger.log_literal(format!("Transferring {:.0} ballot papers, totalling {:.dps$} votes.", result.total_ballots, result.total_votes, dps=opts.pp_decimals));
		} else {
			state.logger.log_literal(format!("Transferring {:.0} ballot papers, totalling {:.dps$} votes, received at value {:.dps2$}.", result.total_ballots, result.total_votes, value, dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
		}
		
		// Transfer candidate votes
		for (candidate, entry) in result.candidates.into_iter() {
			let parcel = entry.votes as Parcel<N>;
			let count_card = state.candidates.get_mut(candidate).unwrap();
			count_card.parcels.push(parcel);
			
			// Round transfers
			let mut candidate_transfers = entry.num_votes;
			if let Some(dps) = opts.round_votes {
				candidate_transfers.floor_mut(dps);
			}
			count_card.transfer(&candidate_transfers);
			checksum += candidate_transfers;
		}
		
		// Transfer exhausted votes
		let parcel = result.exhausted.votes as Parcel<N>;
		state.exhausted.parcels.push(parcel);
		
		let mut exhausted_transfers = result.exhausted.num_votes;
		if let Some(dps) = opts.round_votes {
			exhausted_transfers.floor_mut(dps);
		}
		state.exhausted.transfer(&exhausted_transfers);
		checksum += exhausted_transfers;
	}
	
	if !votes_remain {
		// Finalise candidate votes
		for excluded_candidate in excluded_candidates.into_iter() {
			let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
			checksum -= &count_card.votes;
			count_card.transfers -= &count_card.votes;
			count_card.votes = N::new();
		}
		
		if let ExclusionMethod::SingleStage = opts.exclusion {
		} else {
			state.logger.log_literal("Exclusion complete.".to_string());
		}
	}
	
	// Update loss by fraction
	state.loss_fraction.transfer(&-checksum);
}

fn finished_before_stage<N: Number>(state: &CountState<N>) -> bool {
	if state.num_elected >= state.election.seats {
		return true;
	}
	return false;
}
