use rust_decimal::Decimal;
use crate::alu::RegisterError;

/// Inner representation of the split register.
/// Hides implementation details and provides a safe
/// representation of the register status.
#[derive(Copy, Clone, Debug, PartialEq)]
enum Value {
    /// A 22 bits value
    Full(Decimal),

    /// Two 11 bits values. Left one is higher bits.
    Split(Decimal, Decimal),
}

impl Value {
    pub fn new() -> Value {
        Value::Full(0.into())
    }
}

/// P101 storage register (B, C) that can either contain:
/// - a single number up to 22 digits and comma and sign
/// - a pair of numbers up to 11 digits.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SplitRegister {
    value: Value,
}

impl SplitRegister {
    /// Creates a new register with the default value
    /// - Full (not split)
    /// - Zero value
    pub fn new() -> SplitRegister {
        SplitRegister {
            value: Value::new(),
        }
    }

    /// Read the whole value of the register or the right part
    /// of a split register. Supposing to work on register B,
    /// this maps to B♦
    pub fn read(&self) -> Decimal {
    	match self.value {
            Value::Full(v) => v,
            Value::Split(_, r) => r,
        }
    }

    /// Read the left part of the split register. If the register 
    /// is not split, always returns Decimal::new(0, 0). 
    /// Supposing to be work on register b, this maps to b♦
    pub fn read_left(&self) -> Decimal {
    	match self.value {
            Value::Full(_) => 0.into(),
            Value::Split(l, _) => l,
        }
    }

    /// Writes the value to the register. Different behavior occurs
    /// depending on register status.
    /// - Writing to a full register is always ok
    /// - Writing to a split register is ok the value does not overflow
    /// exceeds capacity.
    pub fn write(&mut self, value: Decimal) -> Result<(), RegisterError> {
    	match self.value {
            Value::Full(ref mut v) => {
                *v = value;
                Ok(())
            },
            Value::Split(l, ref mut r) => {
                if Self::overflows(value) {
                    if l == 0.into() {
                        self.value = Value::Full(value);
                        Ok(())
                    } else {
                        Err(RegisterError::Overflow)
                    }
                } else {
                    *r = value;
                    Ok(())
                }
            },
        }
    }

    /// Writes the value to the left part of a split register.
    /// Different behavior occurs depending on register status.
    /// - Writing to a split register succeed if  the value does not overflow
    /// - Writing to a full register automatically splits it provided
    ///   its value does not overflow.
    pub fn write_left(&mut self, value: Decimal) -> Result<(), RegisterError> {
        match self.value {
            Value::Full(v) => {
                if v == 0.into() {
                    if Self::overflows(value) {
                        Err(RegisterError::Overflow)
                    } else {
                        self.value = Value::Split(value, v);
                        Ok(())
                    }
                } else {
                    Err(RegisterError::CannotSplit)
                }
            },
            Value::Split(ref mut l, _) => {
                if Self::overflows(value) {
                    Err(RegisterError::Overflow)
                } else {
                    *l = value;
                    Ok(())
                }
            }
        }
    }

    /// Tells if the register is split or "full"
    pub fn is_split(&self) -> bool {
        match self.value {
            Value::Split(_, _) => true,
            _ => false,
        }
    }

    // Check if data can fit into a split register.
    fn overflows(v: Decimal) -> bool {
        let s = format!("{}", v);
        s.len() > 11
    }
}

impl From<Decimal> for SplitRegister {
    fn from(n: Decimal) -> Self {
        SplitRegister {
            value: Value::Full(n),
        }
    }
}

impl From<(Decimal, Decimal)> for SplitRegister {
    fn from(n: (Decimal, Decimal)) -> Self {
        SplitRegister {
            value: Value::Split(n.0, n.1),
        }
    }
}

impl Default for SplitRegister {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn register_default_value_is_zero() {
        let r = SplitRegister::new();

        assert_eq!(Decimal::new(0, 0), r.read());
    }

    #[test]
    pub fn register_value_matches_the_exposed_one() {
        let r = SplitRegister {
            value: Value::Full(1.into()),
        };

        assert_eq!(Decimal::new(1, 0), r.read());

        let r = SplitRegister {
            value: Value::Full(Decimal::new(-1, 0)),
        };

        assert_eq!(-Decimal::new(1, 0), r.read());
    }

    #[test]
    pub fn split_register_default_values_are_zero() {
        let r = SplitRegister::new();

        assert_eq!(Decimal::new(0, 0), r.read_left());
        assert_eq!(Decimal::new(0, 0), r.read());
    }

    #[test]
    pub fn split_register_values_match_the_exposed_ones() {
        let r = SplitRegister {
            value: Value::Split(1.into(), 2.into()),
        };

        assert_eq!(Decimal::new(1,0), r.read_left());
        assert_eq!(Decimal::new(2, 0), r.read());

        let r = SplitRegister {
            value: Value::Split((-1).into(), (-2).into()),
        };

        assert_eq!(-Decimal::new(1, 0), r.read_left());
        assert_eq!(-Decimal::new(2, 0), r.read());
    }

    #[test]
    pub fn write_register() {
        let mut r = SplitRegister::new();

        let result = r.write(1.into());

        assert!(result.is_ok());
        assert_eq!(Decimal::new(1, 0), r.read());
    }

    #[test]
    pub fn write_an_overflowing_value_to_a_zeroed_split_register_coalesce_the_register() {
        let mut r = SplitRegister {
            value: Value::Split(0.into(), 0.into())
        };

        let result = r.write(1_000_000_000_000_i64.into());

        assert!(result.is_ok());
        assert_eq!(Decimal::new(1_000_000_000_000, 0), r.read());

        match r.value {
            Value::Full(_) => (),
            _ => assert!(false),
        };
    }

    #[test]
    pub fn overflowing_a_non_zero_split_register_produces_an_error() {
        let mut r = SplitRegister {
            value: Value::Split(1.into(), 2.into())
        };

        let result = r.write(1_000_000_000_000_i64.into());

        assert!(result.is_err());

        match result {
            Err(RegisterError::Overflow) => (),
            _ => assert!(false),
        };
    }

    #[test]
    pub fn write_split_register_values() {
        let mut r = SplitRegister::new();

        match r.write_left(1.into()) {
            Ok(_) => assert_eq!(Decimal::new(1, 0), r.read_left()),
            _ => assert!(false),
        };

        match r.write(2.into()) {
            Ok(_) => assert_eq!(Decimal::new(2, 0), r.read()),
            _ => assert!(false),
        };
    }

    #[test]
    pub fn catch_split_register_overflow() {
        let mut r = SplitRegister::new();
        
        let result = r.write_left(1_000_000_000_000_i64.into());

        assert!(result.is_err());

        match result {
            Err(RegisterError::Overflow) => (),
            _ => assert!(false),
        };
    }

    #[test]
    pub fn catch_cannot_split_error() {
        let mut r = SplitRegister {
            value: Value::Full(1_000_000_000_000_i64.into())
        };

        let result = r.write_left(1_000_000_000_000_i64.into());

        assert!(result.is_err());

        match result {
            Err(RegisterError::CannotSplit) => (),
            _ => assert!(false),
        };
    }

    #[test]
    pub fn updating_left_register_value() {
        let mut r = SplitRegister {
            value: Value::Split(1.into(), 2.into())
        };

        let result = r.write_left(2.into());

        assert!(result.is_ok());
        assert_eq!(Decimal::new(2, 0), r.read_left());
    }

    #[test]
    pub fn overflowing_left_register_produces_an_error() {
        let mut r = SplitRegister {
            value: Value::Split(1.into(), 2.into())
        };
        
        let result = r.write_left(1_000_000_000_000_i64.into());

        assert!(result.is_err());
        assert_eq!(Decimal::new(1, 0), r.read_left());
        assert_eq!(Decimal::new(2, 0), r.read());
    }

    #[test]
    pub fn updating_left_register_on_a_zeroed_full_register_splits_it() {
        let mut r = SplitRegister {
            value: Value::Full(0.into())
        };

        let result = r.write_left(1.into());

        assert!(result.is_ok());
        assert_eq!(Decimal::new(1, 0), r.read_left());
        assert_eq!(Decimal::new(0, 0), r.read());
    }
}

