/*  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 crate::logger::Logger;
use crate::numbers::Number;

use std::collections::HashMap;

pub struct Election<N> {
	pub name: String,
	pub seats: usize,
	pub candidates: Vec<Candidate>,
	pub ballots: Vec<Ballot<N>>,
}

impl<N: Number> Election<N> {
	pub fn from_blt<I: Iterator<Item=String>>(mut lines: I) -> Self {
		// Read first line
		let line = lines.next().expect("Unexpected EOF");
		let mut bits = line.split(" ");
		let num_candidates = bits.next().expect("Syntax Error").parse().expect("Syntax Error");
		let seats: usize = bits.next().expect("Syntax Error").parse().expect("Syntax Error");
		
		// Initialise the Election object
		let mut election = Election {
			name: String::new(),
			seats: seats,
			candidates: Vec::with_capacity(num_candidates),
			ballots: Vec::new(),
		};
		
		// Read ballots
		for line in &mut lines {
			if line == "0" {
				break;
			}
			
			let mut bits = line.split(" ");
			let value = N::parse(bits.next().expect("Syntax Error"));
			
			let mut ballot = Ballot {
				orig_value: value,
				preferences: Vec::new(),
			};
			
			for preference in bits {
				if preference != "0" {
					let preference = preference.parse::<usize>().expect("Syntax Error");
					ballot.preferences.push(preference - 1);
				}
			}
			
			election.ballots.push(ballot);
		}
		
		// Read candidates
		for line in lines.by_ref().take(num_candidates) {
			let mut line = &line[..];
			if line.starts_with("\"") && line.ends_with("\"") {
				line = &line[1..line.len()-1];
			}
			
			election.candidates.push(Candidate { name: line.to_string() });
		}
		
		// Read name
		let line = lines.next().expect("Syntax Error");
		let mut line = &line[..];
		if line.starts_with("\"") && line.ends_with("\"") {
			line = &line[1..line.len()-1];
		}
		election.name.push_str(line);
		
		return election;
	}
}

#[derive(PartialEq, Eq, Hash)]
pub struct Candidate {
	pub name: String,
}

#[derive(Clone)]
pub struct CountState<'a, N> {
	pub election: &'a Election<N>,
	
	pub candidates: HashMap<&'a Candidate, CountCard<'a, N>>,
	pub exhausted: CountCard<'a, N>,
	pub loss_fraction: CountCard<'a, N>,
	
	pub quota: Option<N>,
	pub vote_required_election: Option<N>,
	
	pub num_elected: usize,
	pub num_excluded: usize,
	
	pub kind: Option<&'a str>,
	pub title: String,
	pub logger: Logger<'a>,
}

impl<'a, N: Number> CountState<'a, N> {
	pub fn new(election: &'a Election<N>) -> Self {
		let mut state = CountState {
			election: &election,
			candidates: HashMap::new(),
			exhausted: CountCard::new(),
			loss_fraction: CountCard::new(),
			quota: None,
			vote_required_election: None,
			num_elected: 0,
			num_excluded: 0,
			kind: None,
			title: String::new(),
			logger: Logger { entries: Vec::new() },
		};
		
		for candidate in election.candidates.iter() {
			state.candidates.insert(candidate, CountCard::new());
		}
		
		return state;
	}
	
	pub fn step_all(&mut self) {
		for (_, count_card) in self.candidates.iter_mut() {
			count_card.step();
		}
		self.exhausted.step();
		self.loss_fraction.step();
	}
}

#[allow(dead_code)]
pub enum CountStateOrRef<'a, N> {
	State(CountState<'a, N>), // NYI: May be used e.g. for tie-breaking or rollback-based constraints
	Ref(&'a CountState<'a, N>),
}

impl<'a, N> CountStateOrRef<'a, N> {
	pub fn from(state: &'a CountState<N>) -> Self {
		return Self::Ref(state);
	}
	
	pub fn as_ref(&self) -> &CountState<N> {
		match self {
			CountStateOrRef::State(state) => &state,
			CountStateOrRef::Ref(state) => state,
		}
	}
}

pub struct StageResult<'a, N> {
	pub kind: Option<&'a str>,
	pub title: &'a String,
	pub logs: Vec<String>,
	pub state: CountStateOrRef<'a, N>,
}

#[derive(Clone)]
pub struct CountCard<'a, N> {
	pub state: CandidateState,
	pub order_elected: isize,
	
	pub orig_votes: N,
	pub transfers: N,
	pub votes: N,
	
	pub parcels: Vec<Parcel<'a, N>>,
}

impl<'a, N: Number> CountCard<'a, N> {
	pub fn new() -> Self {
		return CountCard {
			state: CandidateState::HOPEFUL,
			order_elected: 0,
			orig_votes: N::new(),
			transfers: N::new(),
			votes: N::new(),
			parcels: Vec::new(),
		};
	}
	
	//pub fn votes(&'a self) -> N {
	//	return self.orig_votes.clone() + &self.transfers;
	//}
	
	pub fn transfer(&mut self, transfer: &'_ N) {
		self.transfers += transfer;
		self.votes += transfer;
	}
	
	pub fn step(&mut self) {
		self.orig_votes = self.votes.clone();
		self.transfers = N::new();
	}
}

pub type Parcel<'a, N> = Vec<Vote<'a, N>>;

#[derive(Clone)]
pub struct Vote<'a, N> {
	pub ballot: &'a Ballot<N>,
	pub value: N,
	pub up_to_pref: usize,
}

pub struct Ballot<N> {
	pub orig_value: N,
	pub preferences: Vec<usize>,
}

#[allow(dead_code)]
#[derive(PartialEq)]
#[derive(Clone)]
pub enum CandidateState {
	HOPEFUL,
	GUARDED,
	ELECTED,
	DOOMED,
	EXCLUDED,
}
