/*  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/>.
 */

use super::{ExclusionMethod, NextPreferencesEntry, STVError, STVOptions, SumSurplusTransfersMode, SurplusMethod, SurplusOrder};
use super::sample;

use crate::constraints;
use crate::election::{Candidate, CandidateState, CountState, Parcel, StageKind, Vote};
use crate::numbers::Number;
use crate::ties;

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

/// Distribute first preference votes according to the Gregory method
pub fn distribute_first_preferences<N: Number>(state: &mut CountState<N>, opts: &STVOptions)
where
	for<'r> &'r N: ops::Sub<&'r N, Output=N>
{
	let votes = state.election.ballots.iter().map(|b| Vote {
		ballot: b,
		up_to_pref: 0,
	}).collect();
	
	let result = super::next_preferences(state, votes);
	
	// Transfer candidate votes
	for (candidate, entry) in result.candidates.into_iter() {
		let parcel = Parcel {
			votes: entry.votes,
			value_fraction: N::one(),
			source_order: 0,
		};
		let count_card = state.candidates.get_mut(candidate).unwrap();
		count_card.parcels.push(parcel);
		
		let mut vote_transfers = entry.num_ballots.clone();
		if let Some(dps) = opts.round_votes {
			vote_transfers.floor_mut(dps);
		}
		count_card.transfer(&vote_transfers);
		
		count_card.ballot_transfers += entry.num_ballots;
	}
	
	// Transfer exhausted votes
	let parcel = Parcel {
		votes: result.exhausted.votes,
		value_fraction: N::one(),
		source_order: 0,
	};
	state.exhausted.parcels.push(parcel);
	state.exhausted.transfer(&result.exhausted.num_ballots);
	state.exhausted.ballot_transfers += result.exhausted.num_ballots;
	
	// Calculate loss by fraction - if minivoters used
	if let Some(orig_total) = &state.election.total_votes {
		let mut total_votes = state.candidates.values().fold(N::new(), |acc, cc| acc + &cc.votes);
		total_votes += &state.exhausted.votes;
		let lbf = orig_total - &total_votes;
		
		state.loss_fraction.votes = lbf.clone();
		state.loss_fraction.transfers = lbf;
	}
	
	state.title = StageKind::FirstPreferences;
	state.logger.log_literal("First preferences distributed.".to_string());
}

/// Distribute the largest surplus according to the Gregory or random subset method, based on [STVOptions::surplus]
///
/// Returns `true` if any surpluses were distributed.
pub fn distribute_surpluses<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> Result<bool, STVError>
where
	for<'r> &'r N: ops::Add<&'r N, Output=N>,
	for<'r> &'r N: ops::Sub<&'r N, Output=N>,
	for<'r> &'r N: ops::Mul<&'r N, Output=N>,
	for<'r> &'r N: ops::Div<&'r N, Output=N>,
	for<'r> &'r N: ops::Neg<Output=N>
{
	let quota = state.quota.as_ref().unwrap();
	let has_surplus: Vec<&Candidate> = state.election.candidates.iter() // Present in order in case of tie
		.filter(|c| {
			let cc = &state.candidates[c];
			&cc.votes > quota && !cc.finalised
		})
		.collect();
	
	if !has_surplus.is_empty() {
		let total_surpluses = has_surplus.iter()
			.fold(N::new(), |acc, c| acc + &state.candidates[c].votes - quota);
		
		// Determine if surplues can be deferred
		if opts.defer_surpluses {
			if super::can_defer_surpluses(state, opts, &total_surpluses) {
				state.logger.log_literal(format!("Distribution of surpluses totalling {:.dps$} votes will be deferred.", total_surpluses, dps=opts.pp_decimals));
				return Ok(false);
			}
		}
		
		// Distribute top candidate's surplus
		let max_cands = match opts.surplus_order {
			SurplusOrder::BySize => {
				ties::multiple_max_by(&has_surplus, |c| &state.candidates[c].votes)
			}
			SurplusOrder::ByOrder => {
				ties::multiple_min_by(&has_surplus, |c| state.candidates[c].order_elected)
			}
		};
		let elected_candidate = if max_cands.len() > 1 {
			super::choose_highest(state, opts, max_cands, "Which candidate's surplus to distribute?")?
		} else {
			max_cands[0]
		};
		
		// If --no-immediate-elect, declare elected the candidate with the highest surplus
		if !opts.immediate_elect {
			let count_card = state.candidates.get_mut(elected_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![&elected_candidate.name]
			);
			
			constraints::update_constraints(state, opts);
		}
		
		match opts.surplus {
			SurplusMethod::WIG | SurplusMethod::UIG | SurplusMethod::EG => { distribute_surplus(state, &opts, elected_candidate); }
			SurplusMethod::Cincinnati | SurplusMethod::Hare => { sample::distribute_surplus(state, &opts, elected_candidate)?; }
			_ => unreachable!()
		}
		
		return Ok(true);
	}
	
	// If --no-immediate-elect, check for candidates with exactly a quota to elect
	// However, if --defer-surpluses, zero surplus is necessarily deferred so skip
	if !opts.immediate_elect && !opts.defer_surpluses {
		if super::elect_hopefuls(state, opts, false)? {
			return Ok(true);
		}
	}
	
	return Ok(false);
}

/// Return the denominator of the surplus fraction
///
/// Returns `None` if the value of transferable votes <= surplus (i.e. all transferable votes are transferred at values received).
fn calculate_surplus_denom<'n, N: Number>(surplus: &N, transferable_ballots: &'n N, transferable_votes: &'n N, total_ballots: &'n N, total_votes: &'n N, weighted: bool, transferable_only: bool) -> Option<&'n N>
where
	for<'r> &'r N: ops::Sub<&'r N, Output=N>
{
	if transferable_only {
		let transferable_units = if weighted { transferable_votes } else { transferable_ballots };
		
		if transferable_votes > surplus {
			return Some(transferable_units);
		} else {
			return None;
		}
	} else {
		if weighted {
			return Some(total_votes);
		} else {
			return Some(total_ballots);
		}
	}
}

/// Return the reweighted value fraction of a parcel/vote after being transferred
fn reweight_value_fraction<N: Number>(
	value_fraction: &N,
	surplus: &N,
	weighted: bool,
	surplus_fraction: &Option<N>,
	surplus_denom: &Option<&N>,
	round_tvs: Option<usize>) -> N
{
	let result;
	
	match surplus_denom {
		Some(v) => {
			if let Some(_) = round_tvs {
				// Rounding requested: use the rounded transfer value
				if weighted {
					result = value_fraction.clone() * surplus_fraction.as_ref().unwrap();
				} else {
					result = surplus_fraction.as_ref().unwrap().clone();
				}
			} else {
				// Avoid unnecessary rounding error by first multiplying by the surplus
				if weighted {
					result = value_fraction.clone() * surplus / *v;
				} else {
					result = surplus.clone() / *v;
				}
			}
		}
		None => {
			result = value_fraction.clone();
		}
	}
	
	return result;
}

/// Compute the number of votes to credit to a continuing candidate during a surplus transfer, based on [STVOptions::sum_surplus_transfers]
fn sum_surplus_transfers<N: Number>(entry: &NextPreferencesEntry<N>, orig_value_fraction: &N, surplus: &N, is_weighted: bool, surplus_fraction: &Option<N>, surplus_denom: &Option<&N>, _state: &mut CountState<N>, opts: &STVOptions) -> N
where
	for<'r> &'r N: ops::Mul<&'r N, Output=N>,
	for<'r> &'r N: ops::Div<&'r N, Output=N>,
{
	match opts.sum_surplus_transfers {
		SumSurplusTransfersMode::ByValue => {
			// Calculate transfer across all votes in this parcel
			let mut result = N::new();
			for vote in entry.votes.iter() {
				result += &vote.ballot.orig_value;
			}
			result *= reweight_value_fraction(orig_value_fraction, surplus, is_weighted, surplus_fraction, surplus_denom, opts.round_surplus_fractions);
			return result;
		}
		SumSurplusTransfersMode::PerBallot => {
			// Sum transfer per each individual ballot
			// TODO: This could be moved to distribute_surplus to avoid looping over the votes and calculating transfer values twice
			let mut new_value_fraction = reweight_value_fraction(orig_value_fraction, surplus, is_weighted, surplus_fraction, surplus_denom, opts.round_surplus_fractions);
			if let Some(dps) = opts.round_votes {
				new_value_fraction.floor_mut(dps);
			}
			
			let mut result = N::new();
			for vote in entry.votes.iter() {
				let mut vote_value = &new_value_fraction * &vote.ballot.orig_value;
				if let Some(dps) = opts.round_votes {
					vote_value.floor_mut(dps);
				}
				result += vote_value;
			}
			return result;
		}
	}
}

/// Distribute the surplus of a given candidate according to the Gregory method, based on [STVOptions::surplus]
fn distribute_surplus<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, elected_candidate: &'a Candidate)
where
	for<'r> &'r N: ops::Add<&'r N, Output=N>,
	for<'r> &'r N: ops::Sub<&'r N, Output=N>,
	for<'r> &'r N: ops::Mul<&'r N, Output=N>,
	for<'r> &'r N: ops::Div<&'r N, Output=N>,
	for<'r> &'r N: ops::Neg<Output=N>
{
	state.title = StageKind::SurplusOf(&elected_candidate);
	state.logger.log_literal(format!("Surplus of {} distributed.", elected_candidate.name));
	
	let count_card = &state.candidates[elected_candidate];
	let surplus = &count_card.votes - state.quota.as_ref().unwrap();
	
	// Determine which votes to examine
	
	let mut parcels;
	match opts.surplus {
		SurplusMethod::WIG | SurplusMethod::UIG => {
			// Inclusive Gregory
			parcels = Vec::new();
			parcels.append(&mut state.candidates.get_mut(elected_candidate).unwrap().parcels);
		}
		SurplusMethod::EG => {
			// Exclusive Gregory
			// Should be safe to unwrap() - or else how did we get a quota!
			parcels = vec![state.candidates.get_mut(elected_candidate).unwrap().parcels.pop().unwrap()];
		}
		_ => unreachable!()
	}
	
	// Count votes
	
	let mut parcels_next_prefs= Vec::new();
	
	let mut transferable_ballots = N::new();
	let mut transferable_votes = N::new();
	
	let mut exhausted_ballots = N::new();
	let mut exhausted_votes = N::new();
	
	for parcel in parcels {
		// Count next preferences
		let result = super::next_preferences(state, parcel.votes);
		
		for (_, entry) in result.candidates.iter() {
			transferable_ballots += &entry.num_ballots;
			transferable_votes += &entry.num_ballots * &parcel.value_fraction;
		}
		
		exhausted_ballots += &result.exhausted.num_ballots;
		exhausted_votes += &result.exhausted.num_ballots * &parcel.value_fraction;
		
		parcels_next_prefs.push((parcel.value_fraction, result));
	}
	
	// Calculate surplus fraction
	
	let is_weighted = match opts.surplus {
		SurplusMethod::WIG => { true }
		SurplusMethod::UIG | SurplusMethod::EG => { false }
		_ => unreachable!()
	};
	
	let total_ballots = &transferable_ballots + &exhausted_ballots;
	let total_votes = &transferable_votes + &exhausted_votes;
	
	let count_card = state.candidates.get_mut(elected_candidate).unwrap();
	count_card.ballot_transfers = -&total_ballots;
	
	let surplus_denom = calculate_surplus_denom(&surplus, &transferable_ballots, &transferable_votes, &total_ballots, &total_votes, is_weighted, opts.transferable_only);
	let mut surplus_fraction;
	match surplus_denom {
		Some(v) => {
			surplus_fraction = Some(surplus.clone() / v);
			
			// Round down if requested
			if let Some(dps) = opts.round_surplus_fractions {
				surplus_fraction.as_mut().unwrap().floor_mut(dps);
			}
			
			if opts.transferable_only {
				if transferable_ballots == N::one() {
					state.logger.log_literal(format!("Transferring 1 transferable ballot, totalling {:.dps$} transferable votes, with surplus fraction {:.dps2$}.", transferable_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
				} else {
					state.logger.log_literal(format!("Transferring {:.0} transferable ballots, totalling {:.dps$} transferable votes, with surplus fraction {:.dps2$}.", transferable_ballots, transferable_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
				}
			} else {
				if total_ballots == N::one() {
					state.logger.log_literal(format!("Transferring 1 ballot, totalling {:.dps$} votes, with surplus fraction {:.dps2$}.", total_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
				} else {
					state.logger.log_literal(format!("Transferring {:.0} ballots, totalling {:.dps$} votes, with surplus fraction {:.dps2$}.", total_ballots, total_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
				}
			}
		}
		None => {
			surplus_fraction = None;
			
			// This can only happen if --transferable-only
			if transferable_ballots == N::one() {
				state.logger.log_literal(format!("Transferring 1 transferable ballot, totalling {:.dps$} transferable votes, at values received.", transferable_votes, dps=opts.pp_decimals));
			} else {
				state.logger.log_literal(format!("Transferring {:.0} transferable ballots, totalling {:.dps$} transferable votes, at values received.", transferable_ballots, transferable_votes, dps=opts.pp_decimals));
			}
		}
	}
	
	// Reweight and transfer parcels
	
	let mut candidate_transfers: HashMap<&Candidate, N> = HashMap::new();
	for candidate in state.election.candidates.iter() {
		candidate_transfers.insert(candidate, N::new());
	}
	let mut exhausted_transfers = N::new();
	
	for (value_fraction, result) in parcels_next_prefs {
		for (candidate, entry) in result.candidates.into_iter() {
			// Record transfers
			// TODO: Is there a better way of writing this?
			let transfers_orig = candidate_transfers.remove(candidate).unwrap();
			let transfers_add = sum_surplus_transfers(&entry, &value_fraction, &surplus, is_weighted, &surplus_fraction, &surplus_denom, state, opts);
			candidate_transfers.insert(candidate, transfers_orig + transfers_add);
			
			// Transfer candidate votes
			let parcel = Parcel {
				votes: entry.votes,
				value_fraction: reweight_value_fraction(&value_fraction, &surplus, is_weighted, &surplus_fraction, &surplus_denom, opts.round_surplus_fractions),
				source_order: state.num_elected + state.num_excluded,
			};
			let count_card = state.candidates.get_mut(candidate).unwrap();
			count_card.ballot_transfers += parcel.num_ballots();
			count_card.parcels.push(parcel);
		}
		
		// Record exhausted votes
		if opts.transferable_only {
			if transferable_votes > surplus {
				// No ballots exhaust
			} else {
				exhausted_transfers += &surplus - &transferable_votes;
			}
		} else {
			exhausted_transfers += sum_surplus_transfers(&result.exhausted, &value_fraction, &surplus, is_weighted, &surplus_fraction, &surplus_denom, state, opts);
		}
		
		// Transfer exhausted votes
		let parcel = Parcel {
			votes: result.exhausted.votes,
			value_fraction: value_fraction, // TODO: Reweight exhausted votes
			source_order: state.num_elected + state.num_excluded,
		};
		state.exhausted.ballot_transfers += parcel.num_ballots();
		state.exhausted.parcels.push(parcel);
	}
	
	let mut checksum = N::new();
	
	// Credit transferred votes
	// ballot_transfers updated above
	for (candidate, mut votes) in candidate_transfers {
		if let Some(dps) = opts.round_votes {
			votes.floor_mut(dps);
		}
		let count_card = state.candidates.get_mut(candidate).unwrap();
		count_card.transfer(&votes);
		checksum += votes;
	}
	
	// Credit exhausted 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;
	
	count_card.finalised = true; // Mark surpluses as done
	
	// Update loss by fraction
	state.loss_fraction.transfer(&-checksum);
}

/// Perform one stage of a candidate exclusion according to the Gregory method, based on [STVOptions::exclusion]
pub fn exclude_candidates<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, excluded_candidates: Vec<&'a Candidate>)
where
	for<'r> &'r N: ops::Mul<&'r N, Output=N>,
	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);
			
			constraints::update_constraints(state, opts);
		}
	}
	
	// Determine votes to transfer in this stage
	let mut parcels = 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();
				count_card.ballot_transfers = -count_card.num_ballots();
				count_card.finalised = true;
				
				parcels.append(&mut count_card.parcels);
				
				// Update votes
				checksum -= &count_card.votes;
				count_card.transfers = -count_card.votes.clone();
				count_card.votes = N::new();
			}
			votes_remain = false;
		}
		ExclusionMethod::ByValue => {
			// Exclude by value
			let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter()
				.filter(|c| { let cc = &state.candidates[*c]; !cc.finalised && !cc.parcels.is_empty() })
				.collect();
			
			if excluded_with_votes.is_empty() {
				votes_remain = false;
			} else {
				// If candidates to exclude still having votes, select only those with the greatest value
				let max_value = excluded_with_votes.iter()
					.map(|c| state.candidates[*c].parcels.iter()
						.map(|p| &p.value_fraction)
						.max().unwrap())
					.max().unwrap()
					.clone();
				
				votes_remain = false;
				
				let mut votes = Vec::new();
				
				for excluded_candidate in excluded_with_votes.iter() {
					let count_card = state.candidates.get_mut(*excluded_candidate).unwrap();
					let mut cc_parcels = Vec::new();
					cc_parcels.append(&mut count_card.parcels);
					
					// Filter out just those votes with max_value
					let mut remaining_parcels = Vec::new();
					
					for mut parcel in cc_parcels {
						if parcel.value_fraction == max_value {
							count_card.ballot_transfers -= parcel.num_ballots();
							
							let votes_transferred = parcel.num_votes();
							votes.append(&mut parcel.votes);
							
							// Update votes
							checksum -= &votes_transferred;
							count_card.transfer(&-votes_transferred);
						} else {
							remaining_parcels.push(parcel);
						}
					}
					
					if !remaining_parcels.is_empty() {
						votes_remain = true;
					}
					
					// Leave remaining votes with candidate
					count_card.parcels = remaining_parcels;
				}
				
				// Group all votes of one value in single parcel
				parcels.push(Parcel {
					votes: votes,
					value_fraction: max_value,
					source_order: 0, // source_order is unused in this mode
				});
			}
		}
		ExclusionMethod::BySource => {
			// Exclude by source candidate
			let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter()
				.filter(|c| { let cc = &state.candidates[*c]; !cc.finalised && !cc.parcels.is_empty() })
				.collect();
			
			if excluded_with_votes.is_empty() {
				votes_remain = false;
			} else {
				// If candidates to exclude still having votes, select only those from the earliest elected/excluded source candidate
				let min_order = excluded_with_votes.iter()
					.map(|c| state.candidates[*c].parcels.iter()
						.map(|p| p.source_order)
						.min().unwrap())
					.min().unwrap();
				
				votes_remain = false;
				
				for excluded_candidate in excluded_with_votes.iter() {
					let count_card = state.candidates.get_mut(*excluded_candidate).unwrap();
					let mut cc_parcels = Vec::new();
					cc_parcels.append(&mut count_card.parcels);
					
					// Filter out just those votes with min_order
					let mut remaining_parcels = Vec::new();
					
					for parcel in cc_parcels {
						if parcel.source_order == min_order {
							count_card.ballot_transfers -= parcel.num_ballots();
							
							let votes_transferred = parcel.num_votes();
							parcels.push(parcel);
							
							// Update votes
							checksum -= &votes_transferred;
							count_card.transfer(&-votes_transferred);
						} else {
							remaining_parcels.push(parcel);
						}
					}
					
					if !remaining_parcels.is_empty() {
						votes_remain = true;
					}
					
					// Leave remaining votes with candidate
					count_card.parcels = remaining_parcels;
				}
			}
		}
		ExclusionMethod::ParcelsByOrder => {
			// Exclude by parcel by order
			if excluded_candidates.len() > 1 && excluded_candidates.iter().any(|c| !state.candidates[c].parcels.is_empty()) {
				// TODO: We can probably support this actually
				panic!("--exclusion parcels_by_order is incompatible with multiple exclusions");
			}
			
			let count_card = state.candidates.get_mut(excluded_candidates[0]).unwrap();
			
			if count_card.parcels.is_empty() {
				votes_remain = false;
			} else {
				parcels.push(count_card.parcels.remove(0));
				votes_remain = !count_card.parcels.is_empty();
				
				count_card.ballot_transfers -= parcels.first().unwrap().num_ballots();
				
				// Update votes
				let votes_transferred = parcels.first().unwrap().num_votes();
				checksum -= &votes_transferred;
				count_card.transfer(&-votes_transferred);
			}
		}
		_ => panic!()
	}
	
	let mut total_ballots = N::new();
	let mut total_votes = N::new();
	
	let value = match parcels.first() { Some(p) => Some(p.value_fraction.clone()), _ => None };
	
	let mut candidate_transfers: HashMap<&Candidate, N> = HashMap::new();
	for candidate in state.election.candidates.iter() {
		candidate_transfers.insert(candidate, N::new());
	}
	let mut exhausted_transfers = N::new();
	
	for parcel in parcels {
		// Count next preferences
		let result = super::next_preferences(state, parcel.votes);
		
		total_ballots += &result.total_ballots;
		total_votes += &result.total_ballots * &parcel.value_fraction;
		
		// Transfer candidate votes
		for (candidate, entry) in result.candidates.into_iter() {
			let parcel = Parcel {
				votes: entry.votes,
				value_fraction: parcel.value_fraction.clone(),
				source_order: state.num_elected + state.num_excluded,
			};
			
			// Record transfers
			let transfers_orig = candidate_transfers.remove(candidate).unwrap();
			candidate_transfers.insert(candidate, transfers_orig + &entry.num_ballots * &parcel.value_fraction);
			
			let count_card = state.candidates.get_mut(candidate).unwrap();
			count_card.ballot_transfers += parcel.num_ballots();
			count_card.parcels.push(parcel);
		}
		
		// Transfer exhausted votes
		let parcel = Parcel {
			votes: result.exhausted.votes,
			value_fraction: parcel.value_fraction,
			source_order: state.num_elected + state.num_excluded,
		};
		
		// Record transfers
		state.exhausted.ballot_transfers += parcel.num_ballots();
		exhausted_transfers += &result.exhausted.num_ballots * &parcel.value_fraction;
		state.exhausted.parcels.push(parcel);
		
		// TODO: Detailed transfers logs
	}
	
	if let ExclusionMethod::SingleStage = opts.exclusion {
		if total_ballots == N::one() {
			state.logger.log_literal(format!("Transferring 1 ballot, totalling {:.dps$} votes.", total_votes, dps=opts.pp_decimals));
		} else {
			state.logger.log_literal(format!("Transferring {:.0} ballots, totalling {:.dps$} votes.", total_ballots, total_votes, dps=opts.pp_decimals));
		}
	} else {
		if total_ballots.is_zero() {
			state.logger.log_literal(format!("Transferring 0 ballots, totalling {:.dps$} votes.", 0, dps=opts.pp_decimals));
		} else if total_ballots == N::one() {
			state.logger.log_literal(format!("Transferring 1 ballot, totalling {:.dps$} votes, received at value {:.dps2$}.", total_votes, value.unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
		} else {
			state.logger.log_literal(format!("Transferring {:.0} ballots, totalling {:.dps$} votes, received at value {:.dps2$}.", total_ballots, total_votes, value.unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
		}
	}
	
	// Credit transferred votes
	// ballot_transfers updated above
	for (candidate, mut votes) in candidate_transfers {
		if let Some(dps) = opts.round_votes {
			votes.floor_mut(dps);
		}
		let count_card = state.candidates.get_mut(candidate).unwrap();
		count_card.transfer(&votes);
		checksum += votes;
	}
	
	// Credit exhausted 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();
			count_card.finalised = true;
		}
		
		if opts.exclusion != ExclusionMethod::SingleStage {
			state.logger.log_literal("Exclusion complete.".to_string());
		}
	}
	
	// Update loss by fraction
	state.loss_fraction.transfer(&-checksum);
}

/// Perform one stage of a candidate exclusion according to the Wright method
pub fn wright_exclude_candidates<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, excluded_candidates: Vec<&'a Candidate>)
where
	for<'r> &'r N: ops::Sub<&'r N, Output=N>,
	for<'r> &'r N: ops::Mul<&'r N, Output=N>,
	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);
		}
		
		constraints::update_constraints(state, opts);
	}
	
	// Reset count
	for (_, count_card) in state.candidates.iter_mut() {
		if count_card.order_elected > 0 {
			count_card.order_elected = 0;
		}
		count_card.parcels.clear();
		count_card.votes = N::new();
		count_card.transfers = N::new();
		count_card.state = match count_card.state {
			CandidateState::Withdrawn => CandidateState::Withdrawn,
			CandidateState::Excluded => CandidateState::Excluded,
			_ => CandidateState::Hopeful,
		};
		
		if count_card.state == CandidateState::Excluded {
			count_card.finalised = true;
		} else {
			count_card.finalised = false;
		}
	}
	
	state.exhausted.votes = N::new();
	state.exhausted.transfers = N::new();
	state.loss_fraction.votes = N::new();
	state.loss_fraction.transfers = N::new();
	
	state.num_elected = 0;
	
	let orig_title = state.title.clone();
	
	// Redistribute first preferences
	super::distribute_first_preferences(state, opts);
	
	state.title = orig_title;
	
	// Trigger recalculation of quota within stv::count_one_stage
	state.quota = None;
	state.vote_required_election = None;
}
