/*  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::{Assign, Number};

use ibig::{IBig, ops::Abs};
use num_traits::{Num, One, Zero};

use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use std::ops;
use std::fmt;

static mut DPS: Option<usize> = None;
static mut FACTOR: Option<IBig> = None;
static mut FACTOR_CMP: Option<IBig> = None;

#[inline]
pub fn get_dps() -> usize {
	unsafe { DPS.unwrap() }
}

#[inline]
fn get_factor() -> &'static IBig {
	unsafe { FACTOR.as_ref().unwrap() }
}

#[inline]
fn get_factor_cmp() -> &'static IBig {
	unsafe { FACTOR_CMP.as_ref().unwrap() }
}

/// Guarded fixed-point number
#[derive(Clone, Debug, Eq)]
pub struct GuardedFixed(IBig);

impl GuardedFixed {
	/// Set the number of decimal places to compute results to
	pub fn set_dps(dps: usize) {
		unsafe {
			DPS = Some(dps);
			FACTOR = Some(IBig::from(10).pow(dps * 2));
			FACTOR_CMP = Some(IBig::from(10).pow(dps) / IBig::from(2));
		}
	}
}

impl Number for GuardedFixed {
	fn new() -> Self { Self(IBig::zero()) }
	
	fn describe() -> String { format!("--numbers gfixed --decimals {}", get_dps()) }
	
	fn pow_assign(&mut self, exponent: i32) {
		self.0 = self.0.pow(exponent as usize) * get_factor() / get_factor().pow(exponent as usize);
	}
	
	fn floor_mut(&mut self, dps: usize) {
		// Only do something if truncating
		if dps < get_dps() * 2 {
			let factor = IBig::from(10).pow(get_dps() * 2 - dps);
			self.0 /= &factor;
			self.0 *= factor;
		}
	}
	
	fn ceil_mut(&mut self, dps: usize) {
		// Only do something if truncating
		if dps < get_dps() * 2 {
			self.0 -= IBig::one();
			let factor = IBig::from(10).pow(get_dps() * 2 - dps);
			self.0 /= &factor;
			self.0 += IBig::one();
			self.0 *= factor;
		}
	}
	
	fn parse(s: &str) -> Self {
		// Parse decimal
		if s.contains('.') {
			let (whole, decimal) = s.split_once('.').unwrap();
			let whole = match IBig::from_str_radix(whole, 10) {
				Ok(value) => value,
				Err(_) => panic!("Syntax Error"),
			} * get_factor();
			let decimal = match IBig::from_str_radix(decimal, 10) {
				Ok(value) => value,
				Err(_) => panic!("Syntax Error"),
			} * get_factor() / IBig::from(10).pow(decimal.len());
			return Self(whole + decimal);
		}
		
		// Parse integer
		if let Ok(value) = Self::from_str_radix(s, 10) {
			return value;
		} else {
			panic!("Syntax Error");
		}
	}
}

impl Num for GuardedFixed {
	type FromStrRadixErr = ibig::error::ParseError;
	fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
		match IBig::from_str_radix(str, radix) {
			Ok(value) => Ok(Self(value * get_factor())),
			Err(err) => Err(err)
		}
	}
}

impl PartialEq for GuardedFixed {
	fn eq(&self, other: &GuardedFixed) -> bool {
		if &(&self.0 - &other.0).abs() < get_factor_cmp() {
			return true;
		} else {
			return false;
		}
	}
}

impl PartialOrd for GuardedFixed {
	fn partial_cmp(&self, other: &GuardedFixed) -> Option<Ordering> {
		return Some(self.cmp(other));
	}
}

impl Ord for GuardedFixed {
	fn cmp(&self, other: &GuardedFixed) -> Ordering {
		if self.eq(other) {
			return Ordering::Equal;
		} else {
			return self.0.cmp(&other.0);
		}
	}
}

impl Assign for GuardedFixed {
	fn assign(&mut self, src: Self) { self.0 = src.0 }
}

impl Assign<&Self> for GuardedFixed {
	fn assign(&mut self, src: &Self) { self.0 = src.0.clone() }
}

impl From<usize> for GuardedFixed {
	fn from(n: usize) -> Self { Self(IBig::from(n) * get_factor()) }
}

impl fmt::Display for GuardedFixed {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		let dps = match f.precision() {
			Some(precision) => if precision < get_dps() * 2 { precision } else { get_dps() * 2 },
			None => get_dps(),
		};
		
		let mut result;
		if dps == get_dps() * 2 {
			result = self.0.clone().abs().to_string();
		} else {
			// Rounding required
			let factor = IBig::from(10).pow(get_dps() * 2 - dps - 1);
			let mut rounded = (&self.0 / factor).abs() + IBig::from(5); // Add 5 to force round-to-nearest
			rounded /= IBig::from(10); // Remove rounding digit
			result = rounded.to_string();
		}
		
		//let should_add_minus = (self.0 < IBig::zero()) && result != "0";
		let should_add_minus = self.0 < IBig::zero();
		
		// Add leading 0s
		result = format!("{0:0>1$}", result, dps + 1);
		
		// Add the decimal point
		if dps > 0 {
			result.insert(result.len() - dps, '.');
		}
		
		// Add the sign
		if should_add_minus {
			result.insert(0, '-');
		}
		
		return f.write_str(&result);
	}
}

impl One for GuardedFixed {
	fn one() -> Self { Self(get_factor().clone()) }
}

impl Zero for GuardedFixed {
	fn zero() -> Self { Self::new() }
	fn is_zero(&self) -> bool { self.0.is_zero() }
}

impl ops::Neg for GuardedFixed {
	type Output = Self;
	fn neg(self) -> Self::Output { Self(-self.0) }
}

impl ops::Add for GuardedFixed {
	type Output = Self;
	fn add(self, rhs: Self) -> Self::Output { Self(self.0 + rhs.0) }
}

impl ops::Sub for GuardedFixed {
	type Output = Self;
	fn sub(self, _rhs: Self) -> Self::Output {
		todo!()
	}
}

impl ops::Mul for GuardedFixed {
	type Output = Self;
	fn mul(self, _rhs: Self) -> Self::Output {
		todo!()
	}
}

impl ops::Div for GuardedFixed {
	type Output = Self;
	fn div(self, rhs: Self) -> Self::Output { Self(self.0 * get_factor() / rhs.0) }
}

impl ops::Rem for GuardedFixed {
	type Output = Self;
	fn rem(self, _rhs: Self) -> Self::Output {
		todo!()
	}
}

impl ops::Add<&Self> for GuardedFixed {
	type Output = Self;
	fn add(self, rhs: &Self) -> Self::Output { Self(self.0 + &rhs.0) }
}

impl ops::Sub<&Self> for GuardedFixed {
	type Output = Self;
	fn sub(self, rhs: &Self) -> Self::Output { Self(self.0 - &rhs.0) }
}

impl ops::Mul<&Self> for GuardedFixed {
	type Output = Self;
	fn mul(self, rhs: &Self) -> Self::Output { Self(self.0 * &rhs.0 / get_factor()) }
}

impl ops::Div<&Self> for GuardedFixed {
	type Output = Self;
	fn div(self, rhs: &Self) -> Self::Output { Self(self.0 * get_factor() / &rhs.0) }
}

impl ops::Rem<&Self> for GuardedFixed {
	type Output = Self;
	fn rem(self, _rhs: &Self) -> Self::Output {
		todo!()
	}
}

impl ops::AddAssign for GuardedFixed {
	fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0; }
}

impl ops::SubAssign for GuardedFixed {
	fn sub_assign(&mut self, rhs: Self) { self.0 -= rhs.0; }
}

impl ops::MulAssign for GuardedFixed {
	fn mul_assign(&mut self, rhs: Self) {
		self.0 *= rhs.0;
		self.0 /= get_factor();
	}
}

impl ops::DivAssign for GuardedFixed {
	fn div_assign(&mut self, rhs: Self) {
		self.0 *= get_factor();
		self.0 /= rhs.0;
	}
}

impl ops::RemAssign for GuardedFixed {
	fn rem_assign(&mut self, _rhs: Self) {
		todo!()
	}
}

impl ops::AddAssign<&Self> for GuardedFixed {
	fn add_assign(&mut self, rhs: &Self) { self.0 += &rhs.0; }
}

impl ops::SubAssign<&Self> for GuardedFixed {
	fn sub_assign(&mut self, rhs: &Self) { self.0 -= &rhs.0; }
}

impl ops::MulAssign<&Self> for GuardedFixed {
	fn mul_assign(&mut self, rhs: &Self) {
		self.0 *= &rhs.0;
		self.0 /= get_factor();
	}
}

impl ops::DivAssign<&Self> for GuardedFixed {
	fn div_assign(&mut self, _rhs: &Self) {
		todo!()
	}
}

impl ops::RemAssign<&Self> for GuardedFixed {
	fn rem_assign(&mut self, _rhs: &Self) {
		todo!()
	}
}

impl ops::Neg for &GuardedFixed {
	type Output = GuardedFixed;
	fn neg(self) -> Self::Output { GuardedFixed(-&self.0) }
}

impl ops::Add<Self> for &GuardedFixed {
	type Output = GuardedFixed;
	fn add(self, rhs: Self) -> Self::Output { GuardedFixed(&self.0 + &rhs.0) }
}

impl ops::Sub<Self> for &GuardedFixed {
	type Output = GuardedFixed;
	fn sub(self, rhs: Self) -> Self::Output { GuardedFixed(&self.0 - &rhs.0) }
}

impl ops::Mul<Self> for &GuardedFixed {
	type Output = GuardedFixed;
	fn mul(self, rhs: Self) -> Self::Output { GuardedFixed(&self.0 * &rhs.0 / get_factor()) }
}

impl ops::Div<Self> for &GuardedFixed {
	type Output = GuardedFixed;
	fn div(self, rhs: Self) -> Self::Output { GuardedFixed(&self.0 * get_factor() / &rhs.0) }
}

impl ops::Rem<Self> for &GuardedFixed {
	type Output = GuardedFixed;
	fn rem(self, _rhs: Self) -> Self::Output {
		todo!()
	}
}

/*
impl ops::Add<&&Rational> for &Rational {
	
}

impl ops::Sub<&&Rational> for &Rational {
	
}

impl ops::Mul<&&Rational> for &Rational {
	
}

impl ops::Div<&&Rational> for &Rational {
	
}

impl ops::Rem<&&Rational> for &Rational {
	
}
*/
