use crate::fixed::Fixed;
use crate::types::{F64_32,F128_64,F192_96,F512_256};

#[test]
fn you_can_add_numbers(){
	let a=F512_256::from((3i128*2).pow(4));
	assert_eq!(a+a,F512_256::from((3i128*2).pow(4)*2));
}

macro_rules! test_bit_by_bit{
	($n:expr,$float:ty,$mantissa_bits:expr)=>{{
		const MANT:u64=(1<<$mantissa_bits)-1;
		// all bits in range
		for i in 0..$n-$mantissa_bits{
			let a=Fixed::<{$n/8},{$n>>1}>::from_bits(bnum::cast::As::as_::<bnum::Int::<{$n/8}>>(MANT).shl(i));
			let b=(MANT as $float)*(2.0 as $float).powi(i as i32-{$n>>1});
			let f:$float=a.into();
			assert_eq!(f,b,"F{}_{} Into float {i}",$n,$n>>1);
			assert_eq!(a,b.try_into().unwrap(),"F{}_{} From float {i}",$n,$n>>1);
		}
		// underflow
		for i in 0u32..$mantissa_bits{
			let a=Fixed::<{$n/8},{$n>>1}>::from_bits(bnum::cast::As::as_::<bnum::Int::<{$n/8}>>(MANT>>i));
			let b=((MANT>>i) as $float)*(2.0 as $float).powi(-{$n>>1});
			let f:$float=a.into();
			assert_eq!(f,b,"Underflow F{}_{} Into float {i}",$n,$n>>1);
			assert_eq!(a,b.try_into().unwrap(),"Underflow F{}_{} From float {i}",$n,$n>>1);
		}
	}};
}

#[test]
fn test_many(){
	test_bit_by_bit!(64,f32,24);
	test_bit_by_bit!(128,f32,24);
	// f32 is reaching its limits here
	// test_bit_by_bit!(256,f32,24);
	// test_bit_by_bit!(512,f32,24);
	test_bit_by_bit!(64,f64,53);
	test_bit_by_bit!(128,f64,53);
	test_bit_by_bit!(256,f64,53);
	test_bit_by_bit!(512,f64,53);
}

#[test]
fn to_f32(){
	let a=F64_32::ZERO;
	let f:f32=a.into();
	assert_eq!(f,0.0f32);
	let a=F64_32::from(1)>>2;
	let f:f32=a.into();
	assert_eq!(f,0.25f32);
	let f:f32=(-a).into();
	assert_eq!(f,-0.25f32);
	let a=F64_32::MIN;
	let f:f32=a.into();
	assert_eq!(f,i32::MIN as f32);
	let a=F512_256::from(1)>>2;
	let f:f32=a.into();
	assert_eq!(f,0.25f32);
	let f:f32=(-a).into();
	assert_eq!(f,-0.25f32);
	let a=F512_256::from(0);
	let f:f32=(-a).into();
	assert_eq!(f,0f32);
	let a=F512_256::from(237946589723468975i64)<<16;
	let f:f32=a.into();
	assert_eq!(f,237946589723468975f32*2.0f32.powi(16));
}

#[test]
fn to_f64(){
	let a=F64_32::ZERO;
	let f:f64=a.into();
	assert_eq!(f,0.0f64);
	let a=F64_32::from(1)>>2;
	let f:f64=a.into();
	assert_eq!(f,0.25f64);
	let f:f64=(-a).into();
	assert_eq!(f,-0.25f64);
	let a=F64_32::MIN;
	let f:f64=a.into();
	assert_eq!(f,i32::MIN as f64);
	let a=F512_256::from(1)>>2;
	let f:f64=a.into();
	assert_eq!(f,0.25f64);
	let f:f64=(-a).into();
	assert_eq!(f,-0.25f64);
	let a=F512_256::from(0);
	let f:f64=(-a).into();
	assert_eq!(f,0f64);
	let a=F512_256::from(237946589723468975i64)<<16;
	let f:f64=a.into();
	assert_eq!(f,237946589723468975f64*2.0f64.powi(16));
}

#[test]
fn from_f32(){
	let a=F64_32::ZERO;
	let b:Result<F64_32,_>=0.0f32.try_into();
	assert_eq!(b,Ok(a));
	let a=F512_256::from(1)>>2;
	let b:Result<F512_256,_>=0.25f32.try_into();
	assert_eq!(b,Ok(a));
	let a=F512_256::from(-1)>>2;
	let b:Result<F512_256,_>=(-0.25f32).try_into();
	assert_eq!(b,Ok(a));
	let a=F512_256::from(0);
	let b:Result<F512_256,_>=0.try_into();
	assert_eq!(b,Ok(a));
	let a=F512_256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
	let b:Result<F512_256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f32*2.0f32.powi(16)).try_into();
	assert_eq!(b,Ok(a));
	//I32F32::MAX into f32 is truncated into this value
	let a=F64_32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64);
	let b:Result<F64_32,_>=Into::<f32>::into(F64_32::MAX).try_into();
	assert_eq!(b,Ok(a));
	//I32F32::MIN hits a special case since it's not representable as a positive signed integer
	//TODO: don't return an overflow because this is technically possible
	let a=F64_32::MIN;
	let f:f32=a.into();
	let b:Result<F64_32,_>=f.try_into();
	assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
	//16 is within the 24 bits of float precision
	let a=-F64_32::MIN.widen_128();
	let f:f32=a.into();
	let b:Result<F64_32,_>=f.try_into();
	assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
	let b:Result<F64_32,_>=f32::MIN_POSITIVE.try_into();
	assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Underflow));
	//test many cases
	for i in 0..64{
		let a=F128_64::from_u64(0b111111111111111111111111000000000000000000000000000000000000000u64)<<i;
		let f:f32=a.into();
		let b:Result<F128_64,_>=f.try_into();
		assert_eq!(b,Ok(a));
	}
}

#[test]
fn from_f64(){
	let a=F64_32::ZERO;
	let b:Result<F64_32,_>=0.0f64.try_into();
	assert_eq!(b,Ok(a));
	let a=F512_256::from(1)>>2;
	let b:Result<F512_256,_>=0.25f64.try_into();
	assert_eq!(b,Ok(a));
	let a=F512_256::from(-1)>>2;
	let b:Result<F512_256,_>=(-0.25f64).try_into();
	assert_eq!(b,Ok(a));
	let a=F512_256::from(0);
	let b:Result<F512_256,_>=0.try_into();
	assert_eq!(b,Ok(a));
	let a=F512_256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
	let b:Result<F512_256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f64*2.0f64.powi(16)).try_into();
	assert_eq!(b,Ok(a));
}

#[test]
fn you_can_shr_numbers(){
	let a=F64_32::from(4);
	assert_eq!(a>>1,F64_32::from(2));
}

#[test]
fn test_wide_mul(){
	let a=F64_32::ONE;
	let aa=a.wide_mul_64_64(a);
	assert_eq!(aa,F128_64::ONE);
}

#[test]
fn test_wide_div(){
	let a=F64_32::ONE*4;
	let b=F64_32::ONE*2;
	let wide_a=a.wide_mul_64_64(F64_32::ONE);
	let wide_b=b.wide_mul_64_64(F64_32::ONE);
	let ab=a.wide_div_64_64(b);
	assert_eq!(ab,F128_64::ONE*2);
	let wab=wide_a.wide_div_128_64(b);
	assert_eq!(wab,F192_96::ONE*2);
	let awb=a.wide_div_64_128(wide_b);
	assert_eq!(awb,F192_96::ONE*2);
}

#[test]
fn test_wide_mul_repeated() {
	let a=F64_32::from(2);
	let b=F64_32::from(3);

	let w1=a.wide_mul_64_64(b);
	let w2=w1.wide_mul_128_128(w1);
	let w3=w2.wide_mul_256_256(w2);

	assert_eq!(w3,F512_256::from((3i128*2).pow(4)));
}

#[test]
fn test_bint(){
	let a=F64_32::ONE;
	assert_eq!(a*2,F64_32::from(2));
}

#[test]
fn test_wrap(){
	assert_eq!(F64_32::ONE,F512_256::ONE.wrap_64());
	assert_eq!(F64_32::NEG_ONE,F512_256::NEG_ONE.wrap_64());
}
#[test]
fn test_narrow(){
	assert_eq!(Ok(F64_32::ONE),F512_256::ONE.narrow_64());
	assert_eq!(Ok(F64_32::NEG_ONE),F512_256::NEG_ONE.narrow_64());
}
#[test]
fn test_widen(){
	assert_eq!(F64_32::ONE.widen_512(),F512_256::ONE);
	assert_eq!(F64_32::NEG_ONE.widen_512(),F512_256::NEG_ONE);
}
#[test]
fn test_clamp(){
	assert_eq!(F64_32::ONE,F512_256::ONE.clamp_64());
	assert_eq!(F64_32::NEG_ONE,F512_256::NEG_ONE.clamp_64());
}
#[test]
fn test_sqrt(){
	let a=F64_32::ONE*4;
	assert_eq!(a.sqrt(),F64_32::from(2));
}
#[test]
fn test_sqrt_zero(){
	let a=F64_32::ZERO;
	assert_eq!(a.sqrt(),F64_32::ZERO);
}
#[test]
fn test_sqrt_low(){
	let a=F64_32::HALF;
	let b=a.fixed_mul(a);
	assert_eq!(b.sqrt(),a);
}
fn find_equiv_sqrt_via_f64(n:F64_32)->F64_32{
	//GIMME THEM BITS BOY
	let ibits=i64::from_le_bytes(n.to_bits().to_bytes());
	let f=(ibits as f64)/((1u64<<32) as f64);
	let f_ans=f.sqrt();
	let i=(f_ans*((1u64<<32) as f64)) as u64;
	let r=F64_32::from_u64(i);
	//mimic the behaviour of the algorithm,
	//return the result if it truncates to the exact answer
	if (r+F64_32::EPSILON).wide_mul_64_64(r+F64_32::EPSILON)==n.wide_mul_64_64(F64_32::ONE){
		return r+F64_32::EPSILON;
	}
	if (r-F64_32::EPSILON).wide_mul_64_64(r-F64_32::EPSILON)==n.wide_mul_64_64(F64_32::ONE){
		return r-F64_32::EPSILON;
	}
	return r;
}
fn test_exact(n:F64_32){
	assert_eq!(n.sqrt(),find_equiv_sqrt_via_f64(n));
}
#[test]
fn test_sqrt_exact(){
	//43
	for i in 0..((i64::MAX as f32).ln() as u32){
		let n=F64_32::from_u64((i as f32).exp() as u64);
		test_exact(n);
	}
}
#[test]
fn test_sqrt_max(){
	let a=F64_32::MAX;
	test_exact(a);
}
#[test]
#[cfg(all(feature="zeroes",not(feature="deferred-division")))]
fn test_zeroes_normal(){
	// (x-1)*(x+1)
	// x^2-1
	let zeroes=F64_32::zeroes2(F64_32::NEG_ONE,F64_32::ZERO,F64_32::ONE);
	assert_eq!(zeroes,arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE,I32F32::ONE]));
	let zeroes=F64_32::zeroes2(F64_32::NEG_ONE*3,F64_32::ONE*2,F64_32::ONE);
	assert_eq!(zeroes,arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE*3,I32F32::ONE]));
}
#[test]
#[cfg(all(feature="zeroes",feature="deferred-division"))]
fn test_zeroes_deferred_division(){
	// (x-1)*(x+1)
	// x^2-1
	let zeroes=F64_32::zeroes2(F64_32::NEG_ONE,F64_32::ZERO,F64_32::ONE);
	assert_eq!(
		zeroes,
		arrayvec::ArrayVec::from_iter([
			ratio_ops::ratio::Ratio::new(F64_32::ONE*2,F64_32::NEG_ONE*2),
			ratio_ops::ratio::Ratio::new(F64_32::ONE*2,F64_32::ONE*2),
		])
	);
}

#[test]
fn test_debug(){
	assert_eq!(format!("{:?}",F64_32::EPSILON),"0.00000001");
	assert_eq!(format!("{:?}",F64_32::ONE),"1.00000000");
	assert_eq!(format!("{:?}",F64_32::TWO),"2.00000000");
	assert_eq!(format!("{:?}",F64_32::MAX),"7fffffff.ffffffff");
	assert_eq!(format!("{:?}",F64_32::try_from(core::f64::consts::PI).unwrap()),"3.243f6a88");
	assert_eq!(format!("{:?}",F64_32::NEG_EPSILON),"-0.00000001");
	assert_eq!(format!("{:?}",F64_32::NEG_ONE),"-1.00000000");
	assert_eq!(format!("{:?}",F64_32::NEG_TWO),"-2.00000000");
	assert_eq!(format!("{:?}",F64_32::MIN),"-80000000.00000000");
}
