/*  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 num_traits::{Num, One, Zero};
use rug::{self, ops::Pow, ops::PowAssign, rational::ParseRationalError};
use rug::Assign as RugAssign;

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

/// Rational number
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Rational(rug::Rational);

impl Number for Rational {
	fn new() -> Self { Self(rug::Rational::new()) }
	
	fn describe() -> String { "--numbers rational".to_string() }
	fn describe_opt() -> String { String::new() }
	
	fn pow_assign(&mut self, exponent: i32) {
		self.0.pow_assign(exponent);
	}
	
	fn floor_mut(&mut self, dps: usize) {
		if dps == 0 {
			self.0.floor_mut();
		} else {
			let factor = rug::Rational::from(10).pow(dps as u32);
			self.0 *= &factor;
			self.0.floor_mut();
			self.0 /= factor;
		}
	}
	
	fn ceil_mut(&mut self, dps: usize) {
		if dps == 0 {
			self.0.ceil_mut();
		} else {
			let factor = rug::Rational::from(10).pow(dps as u32);
			self.0 *= &factor;
			self.0.ceil_mut();
			self.0 /= factor;
		}
	}
	
	fn round_mut(&mut self, dps: usize) {
		if dps == 0 {
			self.0.round_mut();
		} else {
			// TODO: Streamline
			let mut factor = Self::from(10);
			factor.pow_assign(-(dps as i32));
			factor /= Self::from(2);
			
			*self = self.clone() + factor;
			self.floor_mut(dps);
		}
	}
	
	fn parse(s: &str) -> Self {
		// Parse decimal
		if s.contains('.') {
			let (whole, decimal) = s.split_once('.').unwrap();
			let whole = match rug::Rational::parse_radix(whole, 10) {
				Ok(value) => rug::Rational::from(value),
				Err(_) => panic!("Syntax Error"),
			};
			let decimal = match rug::Rational::parse_radix(decimal, 10) {
				Ok(value) => rug::Rational::from(value),
				Err(_) => panic!("Syntax Error"),
			} / rug::Rational::from(10).pow(decimal.len() as u32);
			
			if whole < rug::Rational::new() {
				return Self(whole - decimal);
			} else {
				return Self(whole + decimal);
			}
		}
		
		// Parse integer
		if let Ok(value) = Self::from_str_radix(s, 10) {
			return value;
		} else {
			panic!("Syntax Error");
		}
	}
}

#[test]
fn describe() {
	// Never executed - just for the sake of code coverage
	assert_eq!(Rational::describe(), "--numbers rational");
}

#[test]
fn rounding() {
	let mut x = Rational::parse("55.550"); x.floor_mut(2); assert_eq!(x, Rational::parse("55.55"));
	let mut x = Rational::parse("55.552"); x.floor_mut(2); assert_eq!(x, Rational::parse("55.55"));
	let mut x = Rational::parse("55.555"); x.floor_mut(2); assert_eq!(x, Rational::parse("55.55"));
	let mut x = Rational::parse("55.557"); x.floor_mut(2); assert_eq!(x, Rational::parse("55.55"));
	
	let mut x = Rational::parse("55.550"); x.ceil_mut(2); assert_eq!(x, Rational::parse("55.55"));
	let mut x = Rational::parse("55.552"); x.ceil_mut(2); assert_eq!(x, Rational::parse("55.56"));
	let mut x = Rational::parse("55.555"); x.ceil_mut(2); assert_eq!(x, Rational::parse("55.56"));
	let mut x = Rational::parse("55.557"); x.ceil_mut(2); assert_eq!(x, Rational::parse("55.56"));
	
	let mut x = Rational::parse("55.550"); x.round_mut(2); assert_eq!(x, Rational::parse("55.55"));
	let mut x = Rational::parse("55.552"); x.round_mut(2); assert_eq!(x, Rational::parse("55.55"));
	let mut x = Rational::parse("55.555"); x.round_mut(2); assert_eq!(x, Rational::parse("55.56"));
	let mut x = Rational::parse("55.557"); x.round_mut(2); assert_eq!(x, Rational::parse("55.56"));
}

impl Num for Rational {
	type FromStrRadixErr = ParseRationalError;
	fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
		match rug::Rational::parse_radix(str, radix as i32) {
			Ok(value) => Ok(Self(rug::Rational::from(value))),
			Err(err) => Err(err)
		}
	}
}

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

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

#[test]
fn assign() {
	let a = Rational::parse("123.45");
	let b = Rational::parse("678.90");
	
	let mut x = a.clone(); x.assign(b.clone()); assert_eq!(x, b);
	let mut x = a.clone(); x.assign(&b); assert_eq!(x, b);
}

impl From<usize> for Rational {
	fn from(n: usize) -> Self { Self(rug::Rational::from(n)) }
}

impl fmt::Display for Rational {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		if let Some(precision) = f.precision() {
			if precision == 0 {
				let result = rug::Integer::from((&self.0).round_ref()).to_string();
				return f.write_str(&result);
			} else {
				let base = rug::Rational::from(10).pow(precision as u32);
				let mut result = rug::Integer::from((&self.0 * base).abs().round_ref()).to_string();
				
				let should_add_minus = (self.0 < 0) && result != "0";
				
				// Add leading 0s
				result = format!("{0:0>1$}", result, precision + 1);
				
				// Add the decimal point
				result.insert(result.len() - precision, '.');
				
				// Add the sign
				if should_add_minus {
					result.insert(0, '-');
				}
				
				return f.write_str(&result);
			}
		} else {
			return self.0.fmt(f);
		}
	}
}

#[test]
fn display_debug() {
	let x = Rational::parse("123.4"); assert_eq!(format!("{}", x), "617/5");
	let x = Rational::parse("123.4"); assert_eq!(format!("{:.2}", x), "123.40");
	let x = Rational::parse("123.4"); assert_eq!(format!("{:?}", x), "Rational(617/5)");
}

impl One for Rational {
	fn one() -> Self { Self(rug::Rational::from(1)) }
}

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

impl Eq for Rational {}
impl Ord for Rational {
	fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) }
}

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

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

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

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

impl ops::Div for Rational {
	type Output = Self;
	fn div(self, rhs: Self) -> Self::Output {
		if rhs.0.cmp0() == Ordering::Equal {
			panic!("Divide by zero");
		}
		return Self(self.0 / rhs.0);
	}
}

impl ops::Rem for Rational {
	type Output = Self;
	fn rem(self, rhs: Self) -> Self::Output {
		if rhs.0.cmp0() == Ordering::Equal {
			panic!("Divide by zero");
		}
		
		// TODO: Is there a cleaner way of implementing this?
		let mut quotient = self.0 / &rhs.0;
		quotient.rem_trunc_mut();
		quotient *= rhs.0;
		return Self(quotient);
	}
}

#[test]
fn arith_owned_owned() {
	let a = Rational::parse("123.45");
	let b = Rational::parse("678.90");
	
	assert_eq!(a.clone() + b.clone(), Rational::parse("802.35"));
	assert_eq!(a.clone() - b.clone(), Rational::parse("-555.45"));
	assert_eq!(a.clone() * b.clone(), Rational::parse("83810.205"));
	assert_eq!((a.clone() / b.clone()) * b.clone(), a);
	assert_eq!(b.clone() % a.clone(), Rational::parse("61.65"));
}

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

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

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

impl ops::Div<&Self> for Rational {
	type Output = Self;
	fn div(self, rhs: &Self) -> Self::Output {
		if rhs.0.cmp0() == Ordering::Equal {
			panic!("Divide by zero");
		}
		return Self(self.0 / &rhs.0);
	}
}

impl ops::Rem<&Self> for Rational {
	type Output = Self;
	fn rem(self, rhs: &Self) -> Self::Output {
		if rhs.0.cmp0() == Ordering::Equal {
			panic!("Divide by zero");
		}
		let mut quotient = self.0 / &rhs.0;
		quotient.rem_trunc_mut();
		quotient *= &rhs.0;
		return Self(quotient);
	}
}

#[test]
fn arith_owned_ref() {
	let a = Rational::parse("123.45");
	let b = Rational::parse("678.90");
	
	assert_eq!(a.clone() + &b, Rational::parse("802.35"));
	assert_eq!(a.clone() - &b, Rational::parse("-555.45"));
	assert_eq!(a.clone() * &b, Rational::parse("83810.205"));
	assert_eq!((a.clone() / &b) * &b, a);
	assert_eq!(b.clone() % &a, Rational::parse("61.65"));
}

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

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

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

impl ops::DivAssign for Rational {
	fn div_assign(&mut self, rhs: Self) {
		if rhs.0.cmp0() == Ordering::Equal {
			panic!("Divide by zero");
		}
		self.0 /= rhs.0;
	}
}

impl ops::RemAssign for Rational {
	fn rem_assign(&mut self, rhs: Self) {
		if rhs.0.cmp0() == Ordering::Equal {
			panic!("Divide by zero");
		}
		self.0 /= &rhs.0;
		self.0.rem_trunc_mut();
		self.0 *= rhs.0;
	}
}

#[test]
fn arithassign_owned() {
	let a = Rational::parse("123.45");
	let b = Rational::parse("678.90");
	
	let mut x = a.clone(); x += b.clone(); assert_eq!(x, Rational::parse("802.35"));
	let mut x = a.clone(); x -= b.clone(); assert_eq!(x, Rational::parse("-555.45"));
	let mut x = a.clone(); x *= b.clone(); assert_eq!(x, Rational::parse("83810.205"));
	let mut x = a.clone(); x /= b.clone(); x *= &b; assert_eq!(x, a);
	let mut x = b.clone(); x %= a.clone(); assert_eq!(x, Rational::parse("61.65"));
}

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

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

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

impl ops::DivAssign<&Self> for Rational {
	fn div_assign(&mut self, rhs: &Self) {
		if rhs.0.cmp0() == Ordering::Equal {
			panic!("Divide by zero");
		}
		self.0 /= &rhs.0;
	}
}

impl ops::RemAssign<&Self> for Rational {
	fn rem_assign(&mut self, rhs: &Self) {
		if rhs.0.cmp0() == Ordering::Equal {
			panic!("Divide by zero");
		}
		self.0 /= &rhs.0;
		self.0.rem_trunc_mut();
		self.0 *= &rhs.0;
	}
}

#[test]
fn arithassign_ref() {
	let a = Rational::parse("123.45");
	let b = Rational::parse("678.90");
	
	let mut x = a.clone(); x += &b; assert_eq!(x, Rational::parse("802.35"));
	let mut x = a.clone(); x -= &b; assert_eq!(x, Rational::parse("-555.45"));
	let mut x = a.clone(); x *= &b; assert_eq!(x, Rational::parse("83810.205"));
	let mut x = a.clone(); x /= &b; x *= &b; assert_eq!(x, a);
	let mut x = b.clone(); x %= &a; assert_eq!(x, Rational::parse("61.65"));
}

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

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

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

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

impl ops::Div<Self> for &Rational {
	type Output = Rational;
	fn div(self, rhs: Self) -> Self::Output {
		if rhs.0.cmp0() == Ordering::Equal {
			panic!("Divide by zero");
		}
		return Rational(rug::Rational::from(&self.0 / &rhs.0));
	}
}

impl ops::Rem<Self> for &Rational {
	type Output = Rational;
	fn rem(self, rhs: Self) -> Self::Output {
		if rhs.0.cmp0() == Ordering::Equal {
			panic!("Divide by zero");
		}
		let mut quotient = rug::Rational::from(&self.0 / &rhs.0);
		quotient.rem_trunc_mut();
		quotient *= &rhs.0;
		return Rational(quotient);
	}
}

#[test]
fn arith_ref_ref() {
	let a = Rational::parse("123.45");
	let b = Rational::parse("678.90");
	
	assert_eq!(&a + &b, Rational::parse("802.35"));
	assert_eq!(&a - &b, Rational::parse("-555.45"));
	assert_eq!(&a * &b, Rational::parse("83810.205"));
	assert_eq!((&a / &b) * &b, a);
	assert_eq!(&b % &a, Rational::parse("61.65"));
}

/*
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 {
	
}
*/
