use crate::serialization::SerializationError;

use std::{
    convert::TryFrom,
    ops::{Add, Sub},
};

/// The height of a block is the length of the chain back to the genesis block.
///
/// Block heights can't be added, but they can be *subtracted*,
/// to get a difference of block heights, represented as an `i32`,
/// and height differences can be added to block heights to get new heights.
///
/// # Invariants
///
/// Users should not construct block heights greater than `Height::MAX`.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Height(pub u32);

impl std::str::FromStr for Height {
    type Err = SerializationError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.parse() {
            Ok(h) if (Height(h) <= Height::MAX) => Ok(Height(h)),
            Ok(_) => Err(SerializationError::Parse("Height exceeds maximum height")),
            Err(_) => Err(SerializationError::Parse("Height(u32) integer parse error")),
        }
    }
}

impl Height {
    /// The minimum Height.
    ///
    /// Due to the underlying type, it is impossible to construct block heights
    /// less than `Height::MIN`.
    ///
    /// Style note: Sometimes, `Height::MIN` is less readable than
    /// `Height(0)`. Use whichever makes sense in context.
    pub const MIN: Height = Height(0);

    /// The maximum Height.
    ///
    /// Users should not construct block heights greater than `Height::MAX`.
    pub const MAX: Height = Height(499_999_999);

    /// The maximum Height as a u32, for range patterns.
    ///
    /// `Height::MAX.0` can't be used in match range patterns, use this
    /// alias instead.
    pub const MAX_AS_U32: u32 = Self::MAX.0;

    /// The maximum expiration Height that is allowed in all transactions
    /// previous to Nu5 and in non-coinbase transactions from Nu5 activation height
    /// and above.
    ///
    /// TODO: This is currently the same as `Height::MAX` but that change in #1113.
    /// Remove this TODO when that happens.
    pub const MAX_EXPIRY_HEIGHT: Height = Height(499_999_999);
}

impl Add<Height> for Height {
    type Output = Option<Height>;

    fn add(self, rhs: Height) -> Option<Height> {
        // We know that both values are positive integers. Therefore, the result is
        // positive, and we can skip the conversions. The checked_add is required,
        // because the result may overflow.
        let height = self.0.checked_add(rhs.0)?;
        let height = Height(height);

        if height <= Height::MAX && height >= Height::MIN {
            Some(height)
        } else {
            None
        }
    }
}

impl Sub<Height> for Height {
    type Output = i32;

    /// Panics if the inputs or result are outside the valid i32 range.
    fn sub(self, rhs: Height) -> i32 {
        // We construct heights from integers without any checks,
        // so the inputs or result could be out of range.
        let lhs = i32::try_from(self.0)
            .expect("out of range input `self`: inputs should be valid Heights");
        let rhs =
            i32::try_from(rhs.0).expect("out of range input `rhs`: inputs should be valid Heights");
        lhs.checked_sub(rhs)
            .expect("out of range result: valid input heights should yield a valid result")
    }
}

// We don't implement Add<u32> or Sub<u32>, because they cause type inference issues for integer constants.

impl Add<i32> for Height {
    type Output = Option<Height>;

    fn add(self, rhs: i32) -> Option<Height> {
        // Because we construct heights from integers without any checks,
        // the input values could be outside the valid range for i32.
        let lhs = i32::try_from(self.0).ok()?;
        let result = lhs.checked_add(rhs)?;
        let result = u32::try_from(result).ok()?;
        match result {
            h if (Height(h) <= Height::MAX && Height(h) >= Height::MIN) => Some(Height(h)),
            _ => None,
        }
    }
}

impl Sub<i32> for Height {
    type Output = Option<Height>;

    fn sub(self, rhs: i32) -> Option<Height> {
        // These checks are required, see above for details.
        let lhs = i32::try_from(self.0).ok()?;
        let result = lhs.checked_sub(rhs)?;
        let result = u32::try_from(result).ok()?;
        match result {
            h if (Height(h) <= Height::MAX && Height(h) >= Height::MIN) => Some(Height(h)),
            _ => None,
        }
    }
}

#[cfg(any(test, feature = "proptest-impl"))]
use proptest::prelude::*;
#[cfg(any(test, feature = "proptest-impl"))]
impl Arbitrary for Height {
    type Parameters = ();

    fn arbitrary_with(_args: ()) -> Self::Strategy {
        (Height::MIN.0..=Height::MAX.0).prop_map(Height).boxed()
    }

    type Strategy = BoxedStrategy<Self>;
}

#[test]
fn operator_tests() {
    zebra_test::init();

    assert_eq!(Some(Height(2)), Height(1) + Height(1));
    assert_eq!(None, Height::MAX + Height(1));
    // Bad heights aren't caught at compile-time or runtime, until we add or subtract
    assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + Height(0));
    assert_eq!(None, Height(i32::MAX as u32) + Height(0));
    assert_eq!(None, Height(u32::MAX) + Height(0));

    assert_eq!(Some(Height(2)), Height(1) + 1);
    assert_eq!(None, Height::MAX + 1);
    // Adding negative numbers
    assert_eq!(Some(Height(1)), Height(2) + -1);
    assert_eq!(Some(Height(0)), Height(1) + -1);
    assert_eq!(None, Height(0) + -1);
    assert_eq!(Some(Height(Height::MAX_AS_U32 - 1)), Height::MAX + -1);
    // Bad heights aren't caught at compile-time or runtime, until we add or subtract
    // `+ 0` would also cause an error here, but it triggers a spurious clippy lint
    assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + 1);
    assert_eq!(None, Height(i32::MAX as u32) + 1);
    assert_eq!(None, Height(u32::MAX) + 1);
    // Adding negative numbers
    assert_eq!(None, Height(i32::MAX as u32) + -1);
    assert_eq!(None, Height(u32::MAX) + -1);

    assert_eq!(Some(Height(1)), Height(2) - 1);
    assert_eq!(Some(Height(0)), Height(1) - 1);
    assert_eq!(None, Height(0) - 1);
    assert_eq!(Some(Height(Height::MAX_AS_U32 - 1)), Height::MAX - 1);
    // Subtracting negative numbers
    assert_eq!(Some(Height(2)), Height(1) - -1);
    assert_eq!(Some(Height::MAX), Height(Height::MAX_AS_U32 - 1) - -1);
    assert_eq!(None, Height::MAX - -1);
    // Bad heights aren't caught at compile-time or runtime, until we add or subtract
    assert_eq!(None, Height(i32::MAX as u32) - 1);
    assert_eq!(None, Height(u32::MAX) - 1);
    // Subtracting negative numbers
    assert_eq!(None, Height(Height::MAX_AS_U32 + 1) - -1);
    assert_eq!(None, Height(i32::MAX as u32) - -1);
    assert_eq!(None, Height(u32::MAX) - -1);

    // Sub<Height> panics on out of range errors
    assert_eq!(1, Height(2) - Height(1));
    assert_eq!(0, Height(1) - Height(1));
    assert_eq!(-1, Height(0) - Height(1));
    assert_eq!(-5, Height(2) - Height(7));
    assert_eq!(Height::MAX_AS_U32 as i32, Height::MAX - Height(0));
    assert_eq!(1, Height::MAX - Height(Height::MAX_AS_U32 - 1));
    assert_eq!(-1, Height(Height::MAX_AS_U32 - 1) - Height::MAX);
    assert_eq!(-(Height::MAX_AS_U32 as i32), Height(0) - Height::MAX);
}
