/*  OpenTally: Open-source election vote counting
 *  Copyright © 2021–2023 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::STVError;

use crate::numbers::Number;
use crate::ties::TieStrategy;

use derive_builder::Builder;
use derive_more::Constructor;
use itertools::Itertools;
#[allow(unused_imports)]
use wasm_bindgen::prelude::wasm_bindgen;

/// Options for conducting an STV count
#[derive(Builder, Constructor)]
pub struct STVOptions {
	/// Round surplus fractions to specified decimal places
	#[builder(default="None")]
	pub round_surplus_fractions: Option<usize>,
	
	/// Round ballot values to specified decimal places
	#[builder(default="None")]
	pub round_values: Option<usize>,
	
	/// Round votes to specified decimal places
	#[builder(default="None")]
	pub round_votes: Option<usize>,
	
	/// Round quota to specified decimal places
	#[builder(default="None")]
	pub round_quota: Option<usize>,
	
	/// How to round votes in transfer table
	#[builder(default="RoundSubtransfersMode::SingleStep")]
	pub round_subtransfers: RoundSubtransfersMode,
	
	/// (Meek STV) Limit for stopping iteration of surplus distribution
	#[builder(default=r#"String::from("0.001%")"#)]
	pub meek_surplus_tolerance: String,
	
	/// Quota type
	#[builder(default="QuotaType::Droop")]
	pub quota: QuotaType,
	
	/// Whether to elect candidates on meeting (geq) or strictly exceeding (gt) the quota
	#[builder(default="QuotaCriterion::Greater")]
	pub quota_criterion: QuotaCriterion,
	
	/// Whether to apply a form of progressive quota
	#[builder(default="QuotaMode::Static")]
	pub quota_mode: QuotaMode,
	
	/// Tie-breaking method
	#[builder(default="vec![TieStrategy::Prompt]")]
	pub ties: Vec<TieStrategy>,
	
	/// Method of surplus distributions
	#[builder(default="SurplusMethod::WIG")]
	pub surplus: SurplusMethod,
	
	/// (Gregory STV) Order to distribute surpluses
	#[builder(default="SurplusOrder::BySize")]
	pub surplus_order: SurplusOrder,
	
	/// (Gregory STV) Examine only transferable papers during surplus distributions
	#[builder(default="false")]
	pub transferable_only: bool,
	
	/// (Gregory STV) If --transferable-only, calculate value of transferable papers by subtracting value of non-transferable papers
	#[builder(default="false")]
	pub subtract_nontransferable: bool,
	
	/// (Gregory STV) Method of exclusions
	#[builder(default="ExclusionMethod::SingleStage")]
	pub exclusion: ExclusionMethod,
	
	/// (Meek STV) NZ Meek STV behaviour: Iterate keep values one round before candidate exclusion
	#[builder(default="false")]
	pub meek_nz_exclusion: bool,
	
	/// (Hare) Method of drawing a sample
	#[builder(default="SampleMethod::StratifyLR")]
	pub sample: SampleMethod,
	
	/// (Hare) Sample-based methods: Check for candidate election after each individual ballot paper transfer
	#[builder(default="false")]
	pub sample_per_ballot: bool,
	
	/// Bulk elect as soon as continuing candidates fill all remaining vacancies
	#[builder(default="true")]
	pub early_bulk_elect: bool,
	
	/// Use bulk exclusion
	#[builder(default="false")]
	pub bulk_exclude: bool,
	
	/// Defer surplus distributions if possible
	#[builder(default="false")]
	pub defer_surpluses: bool,
	
	/// Elect candidates on meeting the quota, rather than on surpluses being distributed; (Meek STV) Immediately elect candidates even if keep values have not converged
	#[builder(default="true")]
	pub immediate_elect: bool,
	
	/// On exclusion, exclude any candidate with this many votes or fewer
	#[builder(default="\"0\".to_string()")]
	pub min_threshold: String,
	
	/// Path to constraints file (used only for [STVOptions::describe])
	#[builder(default="None")]
	pub constraints_path: Option<String>,
	
	/// Mode of handling constraints
	#[builder(default="ConstraintMode::GuardDoom")]
	pub constraint_mode: ConstraintMode,
	
	/// (CLI) Hide excluded candidates from results report
	#[builder(default="false")]
	pub hide_excluded: bool,
	
	/// (CLI) Sort candidates by votes in results report
	#[builder(default="false")]
	pub sort_votes: bool,
	
	/// (CLI) Show details of transfers to candidates during surplus distributions/candidate exclusions
	#[builder(default="false")]
	pub transfers_detail: bool,
	
	/// Print votes to specified decimal places in results report
	#[builder(default="2")]
	pub pp_decimals: usize,
}

impl STVOptions {
	/// Converts the [STVOptions] into CLI argument representation
	pub fn describe<N: Number>(&self) -> String {
		let mut flags = Vec::new();
		let n_str = N::describe_opt(); if !n_str.is_empty() { flags.push(N::describe_opt()) };
		if self.surplus != SurplusMethod::IHare && self.surplus != SurplusMethod::Hare {
			if let Some(dps) = self.round_surplus_fractions { flags.push(format!("--round-surplus-fractions {}", dps)); }
			if let Some(dps) = self.round_values { flags.push(format!("--round-values {}", 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.surplus != SurplusMethod::Meek && self.round_subtransfers != RoundSubtransfersMode::SingleStep { flags.push(self.round_subtransfers.describe()); }
		if self.surplus == SurplusMethod::Meek && self.meek_surplus_tolerance != "0.001%" { flags.push(format!("--meek-surplus-tolerance {}", self.meek_surplus_tolerance)); }
		if self.quota != QuotaType::Droop { flags.push(self.quota.describe()); }
		if self.quota_criterion != QuotaCriterion::Greater { flags.push(self.quota_criterion.describe()); }
		if self.quota_mode != QuotaMode::Static { flags.push(self.quota_mode.describe()); }
		let ties_str = self.ties.iter().map(|t| t.describe()).join(" ");
		if ties_str != "prompt" { flags.push(format!("--ties {}", ties_str)); }
		for t in self.ties.iter() { if let TieStrategy::Random(seed) = t { flags.push(format!("--random-seed {}", seed)); } }
		if self.surplus != SurplusMethod::WIG { flags.push(self.surplus.describe()); }
		if self.surplus != SurplusMethod::Meek {
			if self.surplus_order != SurplusOrder::BySize { flags.push(self.surplus_order.describe()); }
			if self.transferable_only { flags.push("--transferable-only".to_string()); }
			if self.subtract_nontransferable { flags.push("--subtract-nontransferable".to_string()); }
			if self.exclusion != ExclusionMethod::SingleStage { flags.push(self.exclusion.describe()); }
		}
		if self.surplus == SurplusMethod::Meek && self.meek_nz_exclusion { flags.push("--meek-nz-exclusion".to_string()); }
		if (self.surplus == SurplusMethod::IHare || self.surplus == SurplusMethod::Hare) && self.sample != SampleMethod::StratifyLR { flags.push(self.sample.describe()); }
		if (self.surplus == SurplusMethod::IHare || self.surplus == SurplusMethod::Hare) && self.sample_per_ballot { flags.push("--sample-per-ballot".to_string()); }
		if !self.early_bulk_elect { flags.push("--no-early-bulk-elect".to_string()); }
		if self.bulk_exclude { flags.push("--bulk-exclude".to_string()); }
		if self.defer_surpluses { flags.push("--defer-surpluses".to_string()); }
		if !self.immediate_elect { flags.push("--no-immediate-elect".to_string()); }
		if self.min_threshold != "0" { flags.push(format!("--min-threshold {}", self.min_threshold)); }
		if let Some(path) = &self.constraints_path {
			flags.push(format!("--constraints {}", path));
			if self.constraint_mode != ConstraintMode::GuardDoom { flags.push(self.constraint_mode.describe()); }
		}
		if self.hide_excluded { flags.push("--hide-excluded".to_string()); }
		if self.sort_votes { flags.push("--sort-votes".to_string()); }
		if self.transfers_detail { flags.push("--transfers-detail".to_string()); }
		if self.pp_decimals != 2 { flags.push(format!("--pp-decimals {}", self.pp_decimals)); }
		return flags.join(" ");
	}
	
	/// Validate the combination of [STVOptions] and error if invalid
	pub fn validate(&self) -> Result<(), STVError> {
		if self.surplus == SurplusMethod::Meek {
			if self.quota_mode == QuotaMode::ERS97 {
				// Invalid because keep values cannot be calculated for a candidate elected with less than a surplus
				return Err(STVError::InvalidOptions("--surplus meek is incompatible with --quota-mode ers97"));
			}
			if self.quota_mode == QuotaMode::ERS76 {
				// Invalid because keep values cannot be calculated for a candidate elected with less than a surplus
				return Err(STVError::InvalidOptions("--surplus meek is incompatible with --quota-mode ers76"));
			}
			if self.quota_mode == QuotaMode::DynamicByActive {
				// Invalid because all votes are "active" in Meek STV
				return Err(STVError::InvalidOptions("--surplus meek is incompatible with --quota-mode dynamic_by_active"));
			}
			if self.transferable_only {
				// Invalid because this would imply a different keep value applies to nontransferable ballots (?)
				// TODO: NYI?
				return Err(STVError::InvalidOptions("--surplus meek is incompatible with --transferable-only"));
			}
			if self.exclusion != ExclusionMethod::SingleStage {
				// Invalid because Meek STV is independent of order of exclusion, so segmented exclusion has no impact
				return Err(STVError::InvalidOptions("--surplus meek requires --exclusion single_stage"));
			}
			if self.constraints_path.is_some() && self.constraint_mode == ConstraintMode::RepeatCount {
				// TODO: NYI?
				return Err(STVError::InvalidOptions("--constraint-mode repeat_count requires a Gregory method for --surplus"));
			}
		}
		if self.surplus == SurplusMethod::IHare || self.surplus == SurplusMethod::Hare {
			if self.round_quota != Some(0) {
				// Invalid because votes are counted only in whole numbers
				return Err(STVError::InvalidOptions("--surplus ihare and --surplus hare require --round-quota 0"));
			}
			if self.sample == SampleMethod::StratifyLR && self.sample_per_ballot {
				// Invalid because a stratification cannot be made until all relevant ballots are transferred
				return Err(STVError::InvalidOptions("--sample stratify is incompatible with --sample-per-ballot"));
			}
			if self.sample_per_ballot && !self.immediate_elect {
				// Invalid because otherwise --sample-per-ballot would be ineffectual
				return Err(STVError::InvalidOptions("--sample-per-ballot is incompatible with --no-immediate-elect"));
			}
			if self.constraints_path.is_some() && self.constraint_mode == ConstraintMode::RepeatCount {
				// TODO: NYI?
				return Err(STVError::InvalidOptions("--constraint-mode repeat_count requires a Gregory method for --surplus"));
			}
		}
		if self.subtract_nontransferable {
			if self.surplus != SurplusMethod::WIG {
				// Invalid because other methods do not distinguish between ballots of different value during surplus transfer
				return Err(STVError::InvalidOptions("--subtract-nontransferable requires --surplus wig"));
			}
			if !self.transferable_only {
				// Invalid because nontransferables are only subtracted with --transferable-only
				return Err(STVError::InvalidOptions("--subtract-nontransferable requires --transferable-only"));
			}
		}
		if !self.immediate_elect {
			if self.surplus_order != SurplusOrder::BySize {
				// Invalid because there is no other metric to determine which surplus to distribute
				return Err(STVError::InvalidOptions("--no-immediate-elect requires --surplus-order by_size"));
			}
			if self.quota_mode == QuotaMode::ERS97 {
				// Invalid because candidates meeting the VRE never have surpluses distributed
				return Err(STVError::InvalidOptions("--no-immediate-elect is incompatible with --quota-mode ers97"));
			}
			if self.quota_mode == QuotaMode::ERS76 {
				// Invalid because candidates meeting the VRE never have surpluses distributed
				return Err(STVError::InvalidOptions("--no-immediate-elect is incompatible with --quota-mode ers76"));
			}
		}
		if self.min_threshold != "0" && self.defer_surpluses {
			// TODO: NYI
			return Err(STVError::InvalidOptions("--min-threshold is incompatible with --defer-surpluses (not yet implemented)"));
		}
		if self.round_subtransfers == RoundSubtransfersMode::ByValueAndSource && self.bulk_exclude {
			// TODO: NYI
			return Err(STVError::InvalidOptions("--round-subtransfers by_value_and_source is incompatible with --bulk-exclude (not yet implemented)"));
		}
		return Ok(());
	}
}

/// Enum of options for [STVOptions::round_subtransfers]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum RoundSubtransfersMode {
	/// Do not round subtransfers (only round final number of votes credited)
	SingleStep,
	/// Round in subtransfers according to the value when received
	ByValue,
	/// Round in subtransfers according to the candidate from who each vote was received, and the value when received
	ByValueAndSource,
	/// Round in subtransfers according to parcel
	ByParcel,
	/// Sum and round transfers individually for each ballot paper
	PerBallot,
}

impl RoundSubtransfersMode {
	/// Convert to CLI argument representation
	fn describe(self) -> String {
		match self {
			RoundSubtransfersMode::SingleStep => "--round-subtransfers single_step",
			RoundSubtransfersMode::ByValue => "--round-subtransfers by_value",
			RoundSubtransfersMode::ByValueAndSource => "--round-subtransfers by_value_and_source",
			RoundSubtransfersMode::ByParcel => "--round-subtransfers by_parcel",
			RoundSubtransfersMode::PerBallot => "--round-subtransfers per_ballot",
		}.to_string()
	}
}

impl<S: AsRef<str>> From<S> for RoundSubtransfersMode {
	fn from(s: S) -> Self {
		match s.as_ref() {
			"single_step" => RoundSubtransfersMode::SingleStep,
			"by_value" => RoundSubtransfersMode::ByValue,
			"by_value_and_source" => RoundSubtransfersMode::ByValueAndSource,
			"by_parcel" => RoundSubtransfersMode::ByParcel,
			"per_ballot" => RoundSubtransfersMode::PerBallot,
			_ => panic!("Invalid --round-subtransfers"),
		}
	}
}

/// Enum of options for [STVOptions::quota]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum QuotaType {
	/// Droop quota
	Droop,
	/// Hare quota
	Hare,
	/// Exact Droop quota (Newland–Britton/Hagenbach-Bischoff quota)
	DroopExact,
	/// Exact Hare quota
	HareExact,
}

impl QuotaType {
	/// Convert to CLI argument representation
	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()
	}
}

impl<S: AsRef<str>> From<S> for QuotaType {
	fn from(s: S) -> Self {
		match s.as_ref() {
			"droop" => QuotaType::Droop,
			"hare" => QuotaType::Hare,
			"droop_exact" => QuotaType::DroopExact,
			"hare_exact" => QuotaType::HareExact,
			_ => panic!("Invalid --quota"),
		}
	}
}

/// Enum of options for [STVOptions::quota_criterion]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum QuotaCriterion {
	/// Elect candidates on equalling or exceeding the quota
	GreaterOrEqual,
	/// Elect candidates on strictly exceeding the quota
	Greater,
}

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

impl<S: AsRef<str>> From<S> for QuotaCriterion {
	fn from(s: S) -> Self {
		match s.as_ref() {
			"geq" => QuotaCriterion::GreaterOrEqual,
			"gt" => QuotaCriterion::Greater,
			_ => panic!("Invalid --quota-criterion"),
		}
	}
}

/// Enum of options for [STVOptions::quota_mode]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum QuotaMode {
	/// Static quota
	Static,
	/// Static quota with ERS97 rules
	ERS97,
	/// Static quota with ERS76 rules
	ERS76,
	/// Dynamic quota by total vote
	DynamicByTotal,
	/// Dynamic quota by active vote
	DynamicByActive,
}

impl QuotaMode {
	/// Convert to CLI argument representation
	fn describe(self) -> String {
		match self {
			QuotaMode::Static => "--quota-mode static",
			QuotaMode::ERS97 => "--quota-mode ers97",
			QuotaMode::ERS76 => "--quota-mode ers76",
			QuotaMode::DynamicByTotal => "--quota-mode dynamic_by_total",
			QuotaMode::DynamicByActive => "--quota-mode dynamic_by_active",
		}.to_string()
	}
}

impl<S: AsRef<str>> From<S> for QuotaMode {
	fn from(s: S) -> Self {
		match s.as_ref() {
			"static" => QuotaMode::Static,
			"ers97" => QuotaMode::ERS97,
			"ers76" => QuotaMode::ERS76,
			"dynamic_by_total" => QuotaMode::DynamicByTotal,
			"dynamic_by_active" => QuotaMode::DynamicByActive,
			_ => panic!("Invalid --quota-mode"),
		}
	}
}

/// Enum of options for [STVOptions::surplus]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum SurplusMethod {
	/// Weighted inclusive Gregory method
	WIG,
	/// Unweighted inclusive Gregory method
	UIG,
	/// Exclusive Gregory method (last bundle)
	EG,
	/// Meek method
	Meek,
	/// Inclusive Hare method (random subset)
	IHare,
	/// (Exclusive) Hare method (random subset)
	Hare,
}

impl SurplusMethod {
	/// Convert to CLI argument representation
	fn describe(self) -> String {
		match self {
			SurplusMethod::WIG => "--surplus wig",
			SurplusMethod::UIG => "--surplus uig",
			SurplusMethod::EG => "--surplus eg",
			SurplusMethod::Meek => "--surplus meek",
			SurplusMethod::IHare => "--surplus ihare",
			SurplusMethod::Hare => "--surplus hare",
		}.to_string()
	}
	
	/// Returns `true` if this is a weighted method
	pub fn is_weighted(&self) -> bool {
		return match self {
			SurplusMethod::WIG => { true }
			SurplusMethod::UIG | SurplusMethod::EG => { false }
			_ => unreachable!()
		};
	}
}

impl<S: AsRef<str>> From<S> for SurplusMethod {
	fn from(s: S) -> Self {
		match s.as_ref() {
			"wig" => SurplusMethod::WIG,
			"uig" => SurplusMethod::UIG,
			"eg" => SurplusMethod::EG,
			"meek" => SurplusMethod::Meek,
			"ihare" | "ih" | "cincinnati" => SurplusMethod::IHare, // Inclusive Hare method used to be erroneously referred to as "Cincinnati" method - accept for backwards compatibility
			"hare" | "eh" => SurplusMethod::Hare,
			_ => panic!("Invalid --surplus"),
		}
	}
}

/// Enum of options for [STVOptions::surplus_order]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum SurplusOrder {
	/// Transfer the largest surplus first, even if it arose at a later stage of the count
	BySize,
	/// Transfer the surplus of the candidate elected first, even if it is smaller than another
	ByOrder,
}

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

impl<S: AsRef<str>> From<S> for SurplusOrder {
	fn from(s: S) -> Self {
		match s.as_ref() {
			"by_size" => SurplusOrder::BySize,
			"by_order" => SurplusOrder::ByOrder,
			_ => panic!("Invalid --surplus-order"),
		}
	}
}

/// Enum of options for [STVOptions::exclusion]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum ExclusionMethod {
	/// Transfer all ballot papers of an excluded candidate in one stage
	SingleStage,
	/// Transfer the ballot papers of an excluded candidate in descending order of accumulated transfer value
	ByValue,
	/// Transfer the first preferences for an excluded candidate, then ballot papers received on transfers in descending order of accumulated transfer value
	FirstPreferencesThenByValue,
	/// Transfer the ballot papers of an excluded candidate according to the candidate who transferred the papers to the excluded candidate, in the order the transferring candidates were elected or excluded
	BySource,
	/// Transfer the ballot papers of an excluded candidate parcel by parcel in the order received
	ParcelsByOrder,
	/// Reset count and re-iterate from count of first preferences
	ResetAndReiterate,
}

impl ExclusionMethod {
	/// Convert to CLI argument representation
	fn describe(self) -> String {
		match self {
			ExclusionMethod::SingleStage => "--exclusion single_stage",
			ExclusionMethod::ByValue => "--exclusion by_value",
			ExclusionMethod::FirstPreferencesThenByValue => "--exclusion first_prefs_then_by_value",
			ExclusionMethod::BySource => "--exclusion by_source",
			ExclusionMethod::ParcelsByOrder => "--exclusion parcels_by_order",
			ExclusionMethod::ResetAndReiterate => "--exclusion reset_and_reiterate",
		}.to_string()
	}
}

impl<S: AsRef<str>> From<S> for ExclusionMethod {
	fn from(s: S) -> Self {
		match s.as_ref() {
			"single_stage" => ExclusionMethod::SingleStage,
			"by_value" => ExclusionMethod::ByValue,
			"first_prefs_then_by_value" => ExclusionMethod::FirstPreferencesThenByValue,
			"by_source" => ExclusionMethod::BySource,
			"parcels_by_order" => ExclusionMethod::ParcelsByOrder,
			"reset_and_reiterate" => ExclusionMethod::ResetAndReiterate,
			"wright" => ExclusionMethod::ResetAndReiterate,
			_ => panic!("Invalid --exclusion"),
		}
	}
}

/// Enum of options for [STVOptions::sample]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum SampleMethod {
	/// Stratify the ballots into parcels according to next available preference and transfer the last ballots from each parcel; round fractions according to largest remainders
	StratifyLR,
	// Stratify the ballots into parcels according to next available preference and transfer the last ballots from each parcel; disregard fractions
	//StratifyFloor,
	/// Transfer the last ballots
	ByOrder,
	/// Transfer every n-th ballot, Cincinnati style
	Cincinnati,
}

impl SampleMethod {
	/// Convert to CLI argument representation
	fn describe(self) -> String {
		match self {
			SampleMethod::StratifyLR => "--sample stratify",
			//SampleMethod::StratifyFloor => "--sample stratify_floor",
			SampleMethod::ByOrder => "--sample by_order",
			SampleMethod::Cincinnati => "--sample cincinnati",
		}.to_string()
	}
}

impl<S: AsRef<str>> From<S> for SampleMethod {
	fn from(s: S) -> Self {
		match s.as_ref() {
			"stratify" | "stratify_lr" => SampleMethod::StratifyLR,
			//"stratify_floor" => SampleMethod::StratifyFloor,
			"by_order" => SampleMethod::ByOrder,
			"cincinnati" | "nth_ballot" => SampleMethod::Cincinnati,
			_ => panic!("Invalid --sample-method"),
		}
	}
}

/// Enum of options for [STVOptions::constraint_mode]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum ConstraintMode {
	/// Guard or doom candidates as soon as required to secure a conformant result
	GuardDoom,
	/// If constraints violated, exclude/reintroduce candidates as required and redistribute ballot papers
	RepeatCount,
}

impl ConstraintMode {
	/// Convert to CLI argument representation
	fn describe(self) -> String {
		match self {
			ConstraintMode::GuardDoom => "--constraint-mode guard_doom",
			ConstraintMode::RepeatCount => "--constraint-mode repeat_count",
		}.to_string()
	}
}

impl<S: AsRef<str>> From<S> for ConstraintMode {
	fn from(s: S) -> Self {
		match s.as_ref() {
			"guard_doom" => ConstraintMode::GuardDoom,
			"repeat_count" => ConstraintMode::RepeatCount,
			_ => panic!("Invalid --constraint-mode"),
		}
	}
}
