/* 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 .
*/
#![allow(mutable_borrow_reservation_conflict)]
use crate::numbers::Number;
use crate::election::{Candidate, CandidateState, CountCard, CountState, Parcel, Vote};
use std::collections::HashMap;
use std::ops::{Neg, Sub};
struct NextPreferencesResult<'a, N> {
candidates: HashMap<&'a Candidate, NextPreferencesEntry<'a, N>>,
exhausted: NextPreferencesEntry<'a, N>,
total_ballots: N,
}
struct NextPreferencesEntry<'a, N> {
//count_card: Option<&'a CountCard<'a, N>>,
votes: Vec>,
num_ballots: N,
num_votes: N,
}
fn next_preferences<'a, N: Number>(state: &CountState<'a, N>, votes: Vec>) -> 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(),
};
for mut vote in votes.into_iter() {
result.total_ballots += &vote.ballot.orig_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;
}
pub fn distribute_first_preferences(state: &mut CountState) {
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;
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;
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());
}
pub fn calculate_quota(state: &mut CountState) {
let mut log = String::new();
// Calculate the total vote
state.quota = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes });
log.push_str(format!("{:.2} usable votes, so the quota is ", state.quota).as_str());
// TODO: Different quotas
state.quota /= N::from(state.election.seats + 1);
// TODO: Different rounding rules
state.quota += N::one();
state.quota.floor_mut();
log.push_str(format!("{:.2}.", state.quota).as_str());
state.logger.log_literal(log);
}
fn meets_quota(quota: &N, count_card: &CountCard) -> bool {
// TODO: Different quota rules
return count_card.votes >= *quota;
}
pub fn elect_meeting_quota(state: &mut CountState) {
let quota = &state.quota; // Have to do this or else the borrow checker gets confused
let mut cands_meeting_quota: Vec<(&&Candidate, &mut CountCard)> = state.candidates.iter_mut()
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL && meets_quota(quota, cc))
.collect();
if cands_meeting_quota.len() > 0 {
// Sort by votes
cands_meeting_quota.sort_unstable_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap());
// Declare elected in descending order of votes
for (candidate, count_card) in cands_meeting_quota.into_iter().rev() {
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]
);
}
}
}
pub fn distribute_surpluses(state: &mut CountState) -> bool
where
for<'r> &'r N: Sub<&'r N, Output=N>,
for<'r> &'r N: Neg