/*  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::election::{Candidate, CandidateState, CountCard, CountState, CountStateOrRef, Election, StageResult};
use crate::numbers::{NativeFloat64, Number};
use crate::stv;

extern crate console_error_panic_hook;

use wasm_bindgen::prelude::wasm_bindgen;

// Logging

#[wasm_bindgen]
extern "C" {
	#[wasm_bindgen(js_namespace = console)]
	fn log(s: &str);
}
macro_rules! cprintln {
	($($t:tt)*) => (
		#[allow(unused_unsafe)]
		unsafe { log(&format_args!($($t)*).to_string()) }
	)
}

// Exported functions

#[wasm_bindgen]
pub fn election_from_blt_float64(text: String) -> ElectionFloat64 {
	// Install panic! hook
	console_error_panic_hook::set_once();
	
	let election: Election<NativeFloat64> = Election::from_blt(text.lines().map(|s| s.to_string()).into_iter());
	return ElectionFloat64(election);
}

#[wasm_bindgen]
pub fn count_init_float64(state: &mut CountStateFloat64, opts: &STVOptions) {
	stv::count_init(&mut state.0, &opts.0);
}

#[wasm_bindgen]
pub fn count_one_stage_float64(state: &mut CountStateFloat64, opts: &STVOptions) -> bool {
	return stv::count_one_stage(&mut state.0, &opts.0);
}

// Reporting

fn print_candidates<'a, N: 'a + Number, I: Iterator<Item=(&'a Candidate, &'a CountCard<'a, N>)>>(candidates: I) {
	for (candidate, count_card) in candidates {
		if count_card.state == CandidateState::ELECTED {
			cprintln!("- {}: {:.dps$} ({:.dps$}) - ELECTED {}", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, dps=2);
		} else if count_card.state == CandidateState::EXCLUDED {
			cprintln!("- {}: {:.dps$} ({:.dps$}) - Excluded {}", candidate.name, count_card.votes, count_card.transfers, -count_card.order_elected, dps=2);
		} else {
			cprintln!("- {}: {:.dps$} ({:.dps$})", candidate.name, count_card.votes, count_card.transfers, dps=2);
		}
	}
}

fn print_stage<N: Number>(stage_num: usize, result: &StageResult<N>) {
	// Print stage details
	match result.kind {
		None => { cprintln!("{}. {}", stage_num, result.title); }
		Some(kind) => { cprintln!("{}. {} {}", stage_num, kind, result.title); }
	};
	cprintln!("{}", result.logs.join(" "));
	
	let state = result.state.as_ref();
	
	// Print candidates
	let candidates = state.election.candidates.iter()
		.map(|c| (c, state.candidates.get(c).unwrap()));
	print_candidates(candidates);
	
	// Print summary rows
	cprintln!("Exhausted: {:.dps$} ({:.dps$})", state.exhausted.votes, state.exhausted.transfers, dps=2);
	cprintln!("Loss by fraction: {:.dps$} ({:.dps$})", state.loss_fraction.votes, state.loss_fraction.transfers, dps=2);
	
	let mut total_vote = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes });
	total_vote += &state.exhausted.votes;
	total_vote += &state.loss_fraction.votes;
	cprintln!("Total votes: {:.dps$}", total_vote, dps=2);
	
	cprintln!("Quota: {:.dps$}", state.quota, dps=2);
	
	cprintln!("");
}

#[wasm_bindgen]
pub fn make_and_print_result_float64(stage_num: usize, state: &CountStateFloat64) {
	let result = StageResult {
		kind: state.0.kind,
		title: &state.0.title,
		logs: state.0.logger.render(),
		state: CountStateOrRef::from(&state.0),
	};
	print_stage(stage_num, &result);
}

// Wrapper structs
// Required as we cannot specify &'static in wasm-bindgen: issue #1187

#[wasm_bindgen]
pub struct CountStateFloat64(CountState<'static, NativeFloat64>);
#[wasm_bindgen]
impl CountStateFloat64 {
	pub fn new(election: &ElectionFloat64) -> Self {
		return CountStateFloat64(CountState::new(election.as_static()));
	}
}

#[wasm_bindgen]
pub struct ElectionFloat64(Election<NativeFloat64>);
#[wasm_bindgen]
impl ElectionFloat64 {
	pub fn seats(&self) -> usize { self.0.seats }
	
	fn as_static(&self) -> &'static Election<NativeFloat64> {
		// Need to have this as we cannot specify &'static in wasm-bindgen: issue #1187
		unsafe {
			let ptr = &self.0 as *const Election<NativeFloat64>;
			&*ptr
		}
	}
}

#[wasm_bindgen]
pub struct STVOptions(stv::STVOptions<'static>);
#[wasm_bindgen]
impl STVOptions {
	pub fn new(round_votes: Option<usize>, exclusion: String) -> Self {
		if exclusion == "one_round" {
			return STVOptions(stv::STVOptions {
				round_votes: round_votes,
				exclusion: &"one_round",
			});
		} else {
			panic!("Unknown --exclusion");
		}
	}
}
