use cryptonum::unsigned::{UCN,divmod};
use std::fmt;
use std::cmp::Ordering;
use std::fmt::Write;
use std::ops::*;

/// In case you were wondering, it stands for "Signed Crypto Num".
#[derive(Clone,Debug,PartialEq,Eq)]
pub struct SCN {
    negative: bool,
    value: UCN
}

impl SCN {
    pub fn zero() -> SCN {
        SCN{ negative: false, value: UCN::zero() }
    }

    pub fn is_zero(&self) -> bool {
        self.value.is_zero()
    }

    pub fn from_str(x: &str) -> SCN {
        if x.get(0..1) == Some("-") {
            SCN{ negative: true, value: UCN::from_str(&x[1..]) }
        } else {
            SCN{ negative: false, value: UCN::from_str(x) }
        }
    }

    fn cleanup(&mut self) {
        if self.value.is_zero() {
            self.negative = false;
        }
    }
}

impl fmt::UpperHex for SCN {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(),fmt::Error> {
        if self.negative {
            fmt.write_char('-')?;
        }
        self.value.fmt(fmt)
    }
}

//------------------------------------------------------------------------------
//
//  Conversions to/from crypto nums.
//
//------------------------------------------------------------------------------

define_signed_from!(SCN, i8,  u8);
define_signed_from!(SCN, i16, u16);
define_signed_from!(SCN, i32, u32);
define_signed_from!(SCN, i64, u64);
define_signed_into!(SCN, i8,  u8);
define_signed_into!(SCN, i16, u16);
define_signed_into!(SCN, i32, u32);
define_signed_into!(SCN, i64, u64);

//------------------------------------------------------------------------------
//
//  Comparisons
//
//------------------------------------------------------------------------------

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

impl Ord for SCN {
    fn cmp(&self, other: &SCN) -> Ordering {
        match (self.negative, other.negative) {
            (false, false) => self.value.cmp(&other.value),
            (false, true)  => Ordering::Greater,
            (true,  false) => Ordering::Less,
            (true,  true)  => self.value.cmp(&other.value).reverse()
        }
    }
}

//------------------------------------------------------------------------------
//
//  Arithmetic
//
//------------------------------------------------------------------------------

impl Neg for SCN {
    type Output = SCN;

    fn neg(self) -> SCN {
        if self.is_zero() {
            self
        } else {
            SCN{ negative: !self.negative, value: self.value }
        }
    }
}

impl<'a> Neg for &'a SCN {
    type Output = SCN;

    fn neg(self) -> SCN {
        if self.is_zero() {
            self.clone()
        } else {
            SCN{ negative: !self.negative, value: self.value.clone() }
        }
    }
}

impl<'a> AddAssign<&'a SCN> for SCN {
    fn add_assign(&mut self, rhs: &SCN) {
        if self.negative == rhs.negative {
            self.value.add_assign(&rhs.value);
        } else {
            if self.value >= rhs.value {
                self.value.sub_assign(&rhs.value);
            } else {
                self.negative = !self.negative;
                self.value = &rhs.value - &self.value;
            }
        }
        self.cleanup();
    }
}

impl<'a> SubAssign<&'a SCN> for SCN {
    fn sub_assign(&mut self, rhs: &SCN) {
        let flipped = SCN{ negative: !rhs.negative, value: rhs.value.clone() };
        self.add_assign(&flipped);
        self.cleanup();
    }
}

impl<'a> MulAssign<&'a SCN> for SCN {
    fn mul_assign(&mut self, rhs: &SCN) {
        self.negative ^= rhs.negative;
        self.value.mul_assign(&rhs.value);
        self.cleanup();
    }
}

impl<'a> DivAssign<&'a SCN> for SCN {
    fn div_assign(&mut self, rhs: &SCN) {
        self.negative ^= rhs.negative;
        // rounding makes me grumpy
        let mut remainder = Vec::new();
        let copy = self.value.contents.clone();
        divmod(&mut self.value.contents, &mut remainder,
               &copy, &rhs.value.contents);
        if self.negative && !remainder.is_empty() {
            let one = UCN{ contents: vec![1] };
            self.sub_assign(SCN{ negative: false, value: one});
        }
        self.cleanup();
    }
}

impl<'a> RemAssign<&'a SCN> for SCN {
    fn rem_assign(&mut self, rhs: &SCN) {
        let base = &self.value % &rhs.value;

        if self.negative == rhs.negative {
            self.value = base;
        } else {
            self.negative = rhs.negative;
            self.value = &rhs.value - &base;
        }
        self.cleanup();
    }
}

derive_arithmetic_operators!(SCN, Add, add, AddAssign, add_assign);
derive_arithmetic_operators!(SCN, Sub, sub, SubAssign, sub_assign);
derive_arithmetic_operators!(SCN, Mul, mul, MulAssign, mul_assign);
derive_arithmetic_operators!(SCN, Div, div, DivAssign, div_assign);
derive_arithmetic_operators!(SCN, Rem, rem, RemAssign, rem_assign);

//------------------------------------------------------------------------------
//
//  Tests!
//
//------------------------------------------------------------------------------

#[cfg(test)]
mod test {
    use quickcheck::{Arbitrary,Gen};
    use std::fs::File;
    use std::io::Read;
    use super::*;

    fn gold_test<F>(name: &str, f: F)
     where
      F: Fn(SCN,SCN) -> SCN
    {
        let mut file = File::open(name).unwrap();
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();
        let mut iter = contents.lines();

        while let Some(xstr) = iter.next() {
            let ystr = iter.next().unwrap();
            let zstr = iter.next().unwrap();

            assert!(xstr.starts_with("x: "));
            assert!(ystr.starts_with("y: "));
            assert!(zstr.starts_with("z: "));
            let x = SCN::from_str(&xstr[3..]);
            let y = SCN::from_str(&ystr[3..]);
            let z = SCN::from_str(&zstr[3..]);
            assert_eq!(f(x,y), z);
        }
    }

    #[test]
    fn add_tests() {
        gold_test("tests/add_tests_signed.txt", |x,y| x + y);
    }

    #[test]
    fn sub_tests() {
        gold_test("tests/sub_tests_signed.txt", |x,y| x - y);
    }

    #[test]
    fn mul_tests() {
        gold_test("tests/mul_tests_signed.txt", |x,y| x * y);
    }

    #[test]
    fn div_tests() {
        gold_test("tests/div_tests_signed.txt", |x,y| x / y);
    }

    #[test]
    fn mod_tests() {
        gold_test("tests/mod_tests_signed.txt", |x,y| x % y);
    }

    impl Arbitrary for SCN {
        fn arbitrary<G: Gen>(g: &mut G) -> SCN {
            let neg = (g.next_u32() & 1) == 1;
            SCN{ negative: neg, value: UCN::arbitrary(g) }
        }
    }

    fn one() -> SCN {
        SCN{ negative: false, value: UCN::from(1 as u8) }
    }

    quickcheck! {
        fn additive_identity(x: SCN) -> bool {
            (&x + &SCN::zero()) == x
        }
        fn subtractive_identity(x: SCN) -> bool {
            (&x - &SCN::zero()) == x
        }
        fn multiplicative_identity(x: SCN) -> bool {
            (&x * &one()) == x
        }
        fn division_identity(x: SCN) -> bool {
            let result = &x / &one();
            println!("\nx: {:?}", x);
            println!("result: {:?}", result);
            result == x
        }

        fn additive_destructor(x: SCN) -> bool {
            (&x + (- &x)) == SCN::zero()
        }
        fn subtractive_destructor(x: SCN) -> bool {
            (&x - &x) == SCN::zero()
        }
        fn multiplicative_destructor(x: SCN) -> bool {
            (x * SCN::zero()) == SCN::zero()
        }
        fn division_deastructor(x: SCN) -> bool {
            (&x / &x) == one()
        }
        fn remainder_destructor(x: SCN) -> bool {
            (&x % &x) == SCN::zero()
        }

        fn addition_commutes(a: SCN, b: SCN) -> bool {
            (&a + &b) == (&b + &a)
        }
        fn multiplication_commutes(a: SCN, b: SCN) -> bool {
            (&a * &b) == (&b * &a)
        }
        fn addition_associates(a: SCN, b: SCN, c: SCN) -> bool {
            ((&a + &b) + &c) == (&a + (&b + &c))
        }
        fn multiplication_associates(a: SCN, b: SCN, c: SCN) -> bool {
            ((&a * &b) * &c) == (&a * (&b * &c))
        }
        fn distribution_works(a: SCN, b: SCN, c: SCN) -> bool {
            (&a * (&b + &c)) == ((&a * &b) + (&a * &c))
        }

        fn negation_works(a: SCN) -> bool {
            (- &a) == (&a * &SCN{ negative: true, value: UCN::from(1 as u8) })
        }
        fn double_negation_works(a: SCN) -> bool {
            (- (- &a)) == a
        }
        fn negation_commutes(a: SCN, b: SCN) -> bool {
            ((- &a) * &b) == (&a * (- &b))
        }
        fn negation_cancels(a: SCN, b: SCN) -> bool {
            ((- &a) * (- &b)) == (&a * &b)
        }
        fn negation_distributes(a: SCN, b: SCN) -> bool {
            (- (&a + &b)) == ((- &a) + (- &b))
        }
    }
}
