// SPDX-License-Identifier: CC0-1.0

//! Block height and interval types.
//!
//! These types are thin wrappers around `u32`, no invariants implemented or implied.
//!
//! These are general types for abstracting over block heights, they are not designed to use with
//! lock times. If you are creating lock times you should be using the
//! [`locktime::absolute::Height`] and [`locktime::relative::Height`] types.
//!
//! The difference between these types and the locktime types is that these types are thin wrappers
//! whereas the locktime types contain more complex locktime specific abstractions.

use core::{fmt, ops};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

#[cfg(doc)]
use crate::locktime;
use crate::locktime::{absolute, relative};

/// The block height, zero denotes the genesis block.
///
/// This type is not meant for constructing height based timelocks, this is a general purpose block
/// height abstraction. For locktimes please see [`locktime::absolute::Height`].
///
/// This is a thin wrapper around a `u32` that may take on all values of a `u32`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
// Public to try and make it really clear that there are no invariants.
pub struct BlockHeight(pub u32);

impl BlockHeight {
    /// Block height 0, the genesis block.
    pub const ZERO: Self = BlockHeight(0);

    /// The minimum block height (0), the genesis block.
    pub const MIN: Self = Self::ZERO;

    /// The maximum block height.
    pub const MAX: Self = BlockHeight(u32::MAX);

    /// Creates a block height from a `u32`.
    // Because From<u32> is not const.
    pub const fn from_u32(inner: u32) -> Self { Self(inner) }

    /// Returns block height as a `u32`.
    // Because type inference doesn't always work using `Into`.
    pub const fn to_u32(&self) -> u32 { self.0 }
}

impl fmt::Display for BlockHeight {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}

crate::impl_parse_str_from_int_infallible!(BlockHeight, u32, from);

impl From<u32> for BlockHeight {
    fn from(inner: u32) -> Self { Self::from_u32(inner) }
}

impl From<BlockHeight> for u32 {
    fn from(height: BlockHeight) -> Self { height.to_u32() }
}

impl From<absolute::Height> for BlockHeight {
    /// Converts [`locktime::absolute::Height`] to a [`BlockHeight`].
    ///
    /// An absolute locktime block height has a maximum value of `absolute::LOCK_TIME_THRESHOLD`
    /// (500,000,000) where as a `BlockHeight` is a thin wrapper around a `u32`, the two types are
    /// not interchangeable.
    fn from(h: absolute::Height) -> Self { Self::from_u32(h.to_consensus_u32()) }
}

impl TryFrom<BlockHeight> for absolute::Height {
    type Error = absolute::ConversionError;

    /// Converts [`BlockHeight`] to a [`locktime::absolute::Height`].
    ///
    /// An absolute locktime block height has a maximum value of `absolute::LOCK_TIME_THRESHOLD`
    /// (500,000,000) where as a `BlockHeight` is a thin wrapper around a `u32`, the two types are
    /// not interchangeable.
    fn try_from(h: BlockHeight) -> Result<Self, Self::Error> {
        absolute::Height::from_consensus(h.to_u32())
    }
}

/// The block interval.
///
/// Block interval is an integer type denoting the number of blocks that has passed since some point
/// i.e., this type is meant for usage as a relative block measure.
///
/// This type is not meant for constructing relative height based timelocks, this is a general
/// purpose block interval abstraction. For locktimes please see [`locktime::relative::Height`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
// Public to try and make it really clear that there are no invariants.
pub struct BlockInterval(pub u32);

impl BlockInterval {
    /// Block interval 0 i.e., the current block.
    pub const ZERO: Self = BlockInterval(0);

    /// The minimum block interval (0).
    pub const MIN: Self = Self::ZERO;

    /// The maximum block interval.
    pub const MAX: Self = BlockInterval(u32::MAX);

    /// Creates a block interval from a `u32`.
    // Because From<u32> is not const.
    pub const fn from_u32(inner: u32) -> Self { Self(inner) }

    /// Returns block interval as a `u32`.
    // Because type inference doesn't always work using `Into`.
    pub const fn to_u32(&self) -> u32 { self.0 }
}

impl fmt::Display for BlockInterval {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}

crate::impl_parse_str_from_int_infallible!(BlockInterval, u32, from);

impl From<u32> for BlockInterval {
    fn from(inner: u32) -> Self { Self::from_u32(inner) }
}

impl From<BlockInterval> for u32 {
    fn from(height: BlockInterval) -> Self { height.to_u32() }
}

impl From<relative::Height> for BlockInterval {
    /// Converts [`locktime::relative::Height`] to a [`BlockInterval`].
    ///
    /// A relative locktime block height has a maximum value of `u16::MAX` where as a
    /// `BlockInterval` is a thin wrapper around a `u32`, the two types are not interchangeable.
    fn from(h: relative::Height) -> Self { Self::from_u32(h.value().into()) }
}

impl TryFrom<BlockInterval> for relative::Height {
    type Error = TooBigForRelativeBlockHeightError;

    /// Converts [`BlockInterval`] to a [`locktime::relative::Height`].
    ///
    /// A relative locktime block height has a maximum value of `u16::MAX` where as a
    /// `BlockInterval` is a thin wrapper around a `u32`, the two types are not interchangeable.
    fn try_from(h: BlockInterval) -> Result<Self, Self::Error> {
        let h = h.to_u32();
        if h > u16::MAX as u32 {
            return Err(TooBigForRelativeBlockHeightError(h));
        }
        Ok(relative::Height::from(h as u16)) // Cast ok, value checked above
    }
}

/// The block interval is too big to be used as a relative lock time.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TooBigForRelativeBlockHeightError(u32);

impl fmt::Display for TooBigForRelativeBlockHeightError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "block interval is too big to be used as a relative lock time: {} (max: {})",
            self.0,
            relative::Height::MAX
        )
    }
}

#[cfg(feature = "std")]
impl std::error::Error for TooBigForRelativeBlockHeightError {}

// height - height = interval
impl ops::Sub<BlockHeight> for BlockHeight {
    type Output = BlockInterval;

    fn sub(self, rhs: BlockHeight) -> Self::Output {
        let interval = self.to_u32() - rhs.to_u32();
        BlockInterval::from_u32(interval)
    }
}

// height + interval = height
impl ops::Add<BlockInterval> for BlockHeight {
    type Output = BlockHeight;

    fn add(self, rhs: BlockInterval) -> Self::Output {
        let height = self.to_u32() + rhs.to_u32();
        BlockHeight::from_u32(height)
    }
}

// height - interval = height
impl ops::Sub<BlockInterval> for BlockHeight {
    type Output = BlockHeight;

    fn sub(self, rhs: BlockInterval) -> Self::Output {
        let height = self.to_u32() - rhs.to_u32();
        BlockHeight::from_u32(height)
    }
}

// interval + interval = interval
impl ops::Add<BlockInterval> for BlockInterval {
    type Output = BlockInterval;

    fn add(self, rhs: BlockInterval) -> Self::Output {
        let height = self.to_u32() + rhs.to_u32();
        BlockInterval::from_u32(height)
    }
}

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

// interval - interval = interval
impl ops::Sub<BlockInterval> for BlockInterval {
    type Output = BlockInterval;

    fn sub(self, rhs: BlockInterval) -> Self::Output {
        let height = self.to_u32() - rhs.to_u32();
        BlockInterval::from_u32(height)
    }
}

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

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

    // These tests are supposed to comprise an exhaustive list of available operations.
    #[test]
    fn all_available_ops() {
        // height - height = interval
        assert!(BlockHeight(100) - BlockHeight(99) == BlockInterval(1));

        // height + interval = height
        assert!(BlockHeight(100) + BlockInterval(1) == BlockHeight(101));

        // height - interval == height
        assert!(BlockHeight(100) - BlockInterval(1) == BlockHeight(99));

        // interval + interval = interval
        assert!(BlockInterval(1) + BlockInterval(2) == BlockInterval(3));

        // interval - interval = interval
        assert!(BlockInterval(3) - BlockInterval(2) == BlockInterval(1));

        // interval += interval
        let mut int = BlockInterval(1);
        int += BlockInterval(2);
        assert_eq!(int, BlockInterval(3));

        // interval -= interval
        let mut int = BlockInterval(3);
        int -= BlockInterval(2);
        assert_eq!(int, BlockInterval(1));
    }
}
