use alloc::vec::Vec;
use core::fmt;
use crate::{
    bytesrepr::{Error, FromBytes, ToBytes},
    SemVer,
};
#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ProtocolVersion(SemVer);
#[derive(Debug, PartialEq, Eq)]
pub enum VersionCheckResult {
    
    CodeIsRequired,
    
    CodeIsOptional,
    
    Invalid,
}
impl VersionCheckResult {
    
    
    
    pub fn is_invalid(&self) -> bool {
        match self {
            VersionCheckResult::Invalid => true,
            VersionCheckResult::CodeIsRequired | VersionCheckResult::CodeIsOptional => false,
        }
    }
    
    
    
    pub fn is_code_required(&self) -> bool {
        match self {
            VersionCheckResult::CodeIsRequired => true,
            _ => false,
        }
    }
}
impl ProtocolVersion {
    
    pub const V1_0_0: ProtocolVersion = ProtocolVersion(SemVer {
        major: 1,
        minor: 0,
        patch: 0,
    });
    
    pub fn new(version: SemVer) -> ProtocolVersion {
        ProtocolVersion(version)
    }
    
    pub fn from_parts(major: u32, minor: u32, patch: u32) -> ProtocolVersion {
        let sem_ver = SemVer::new(major, minor, patch);
        Self::new(sem_ver)
    }
    
    pub fn value(&self) -> SemVer {
        self.0
    }
    
    pub fn check_next_version(&self, next: &ProtocolVersion) -> VersionCheckResult {
        if next.0.major < self.0.major || next.0.major > self.0.major + 1 {
            
            
            return VersionCheckResult::Invalid;
        }
        if next.0.major == self.0.major.saturating_add(1) {
            
            if next.0.minor != 0 || next.0.patch != 0 {
                return VersionCheckResult::Invalid;
            }
            return VersionCheckResult::CodeIsRequired;
        }
        
        debug_assert_eq!(next.0.major, self.0.major);
        if next.0.minor < self.0.minor || next.0.minor > self.0.minor + 1 {
            
            
            return VersionCheckResult::Invalid;
        }
        if next.0.minor == self.0.minor + 1 {
            
            if next.0.patch != 0 {
                return VersionCheckResult::Invalid;
            }
            return VersionCheckResult::CodeIsOptional;
        }
        
        debug_assert_eq!(next.0.minor, self.0.minor);
        
        if next.0.patch <= self.0.patch {
            return VersionCheckResult::Invalid;
        }
        VersionCheckResult::CodeIsOptional
    }
    
    
    
    pub fn is_compatible_with(&self, version: &ProtocolVersion) -> bool {
        self.0.major == version.0.major
    }
}
impl ToBytes for ProtocolVersion {
    fn to_bytes(&self) -> Result<Vec<u8>, Error> {
        self.value().to_bytes()
    }
    fn serialized_length(&self) -> usize {
        self.value().serialized_length()
    }
}
impl FromBytes for ProtocolVersion {
    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
        let (version, rem) = SemVer::from_bytes(bytes)?;
        let protocol_version = ProtocolVersion::new(version);
        Ok((protocol_version, rem))
    }
}
impl fmt::Display for ProtocolVersion {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::SemVer;
    #[test]
    fn should_follow_version_with_optional_code() {
        let value = VersionCheckResult::CodeIsOptional;
        assert!(!value.is_invalid());
        assert!(!value.is_code_required());
    }
    #[test]
    fn should_follow_version_with_required_code() {
        let value = VersionCheckResult::CodeIsRequired;
        assert!(!value.is_invalid());
        assert!(value.is_code_required());
    }
    #[test]
    fn should_not_follow_version_with_invalid_code() {
        let value = VersionCheckResult::Invalid;
        assert!(value.is_invalid());
        assert!(!value.is_code_required());
    }
    #[test]
    fn should_be_able_to_get_instance() {
        let initial_value = SemVer::new(1, 0, 0);
        let item = ProtocolVersion::new(initial_value);
        assert_eq!(initial_value, item.value(), "should have equal value")
    }
    #[test]
    fn should_be_able_to_compare_two_instances() {
        let lhs = ProtocolVersion::new(SemVer::new(1, 0, 0));
        let rhs = ProtocolVersion::new(SemVer::new(1, 0, 0));
        assert_eq!(lhs, rhs, "should be equal");
        let rhs = ProtocolVersion::new(SemVer::new(2, 0, 0));
        assert_ne!(lhs, rhs, "should not be equal")
    }
    #[test]
    fn should_be_able_to_default() {
        let defaulted = ProtocolVersion::default();
        let expected = ProtocolVersion::new(SemVer::new(0, 0, 0));
        assert_eq!(defaulted, expected, "should be equal")
    }
    #[test]
    fn should_be_able_to_compare_relative_value() {
        let lhs = ProtocolVersion::new(SemVer::new(2, 0, 0));
        let rhs = ProtocolVersion::new(SemVer::new(1, 0, 0));
        assert!(lhs > rhs, "should be gt");
        let rhs = ProtocolVersion::new(SemVer::new(2, 0, 0));
        assert!(lhs >= rhs, "should be gte");
        assert!(lhs <= rhs, "should be lte");
        let lhs = ProtocolVersion::new(SemVer::new(1, 0, 0));
        assert!(lhs < rhs, "should be lt");
    }
    #[test]
    fn should_follow_major_version_upgrade() {
        
        
        
        let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
        let next = ProtocolVersion::new(SemVer::new(2, 0, 0));
        assert_eq!(
            prev.check_next_version(&next),
            VersionCheckResult::CodeIsRequired
        );
    }
    #[test]
    fn should_reject_if_major_version_decreases() {
        let prev = ProtocolVersion::new(SemVer::new(10, 0, 0));
        let next = ProtocolVersion::new(SemVer::new(9, 0, 0));
        
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
    }
    #[test]
    fn should_check_follows_minor_version_upgrade() {
        
        
        let prev = ProtocolVersion::new(SemVer::new(1, 1, 0));
        let next = ProtocolVersion::new(SemVer::new(1, 2, 0));
        assert_eq!(
            prev.check_next_version(&next),
            VersionCheckResult::CodeIsOptional
        );
    }
    #[test]
    fn should_check_if_minor_bump_resets_patch() {
        
        let prev = ProtocolVersion::new(SemVer::new(1, 2, 0));
        let next = ProtocolVersion::new(SemVer::new(1, 3, 1));
        
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
        let prev = ProtocolVersion::new(SemVer::new(1, 20, 42));
        let next = ProtocolVersion::new(SemVer::new(1, 30, 43));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
    }
    #[test]
    fn should_check_if_major_resets_minor_and_patch() {
        
        let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
        let next = ProtocolVersion::new(SemVer::new(2, 1, 0));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); 
        let next = ProtocolVersion::new(SemVer::new(2, 0, 1));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); 
        let next = ProtocolVersion::new(SemVer::new(2, 1, 1));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); 
                                                                                 
                                                                                 
                                                                                 
    }
    #[test]
    fn should_reject_patch_version_rollback() {
        
        
        let prev = ProtocolVersion::new(SemVer::new(1, 0, 42));
        let next = ProtocolVersion::new(SemVer::new(1, 0, 41));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
        let next = ProtocolVersion::new(SemVer::new(1, 0, 13));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
    }
    #[test]
    fn should_accept_patch_version_update_with_optional_code() {
        let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
        let next = ProtocolVersion::new(SemVer::new(1, 0, 1));
        assert_eq!(
            prev.check_next_version(&next),
            VersionCheckResult::CodeIsOptional
        );
        let prev = ProtocolVersion::new(SemVer::new(1, 0, 8));
        let next = ProtocolVersion::new(SemVer::new(1, 0, 42));
        assert_eq!(
            prev.check_next_version(&next),
            VersionCheckResult::CodeIsOptional
        );
    }
    #[test]
    fn should_accept_minor_version_update_with_optional_code() {
        
        let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
        let next = ProtocolVersion::new(SemVer::new(1, 1, 0));
        assert_eq!(
            prev.check_next_version(&next),
            VersionCheckResult::CodeIsOptional
        );
        let prev = ProtocolVersion::new(SemVer::new(3, 98, 0));
        let next = ProtocolVersion::new(SemVer::new(3, 99, 0));
        assert_eq!(
            prev.check_next_version(&next),
            VersionCheckResult::CodeIsOptional
        );
    }
    #[test]
    fn should_not_skip_minor_version_within_major_version() {
        
        let prev = ProtocolVersion::new(SemVer::new(1, 1, 0));
        let next = ProtocolVersion::new(SemVer::new(1, 3, 0));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
        let next = ProtocolVersion::new(SemVer::new(1, 7, 0));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
    }
    #[test]
    fn should_reset_minor_and_patch_on_major_bump() {
        
        let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
        let next = ProtocolVersion::new(SemVer::new(2, 1, 1));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
        let prev = ProtocolVersion::new(SemVer::new(1, 1, 1));
        let next = ProtocolVersion::new(SemVer::new(2, 2, 3));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
    }
    #[test]
    fn should_allow_code_on_major_update() {
        
        let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
        let next = ProtocolVersion::new(SemVer::new(2, 0, 0));
        assert_eq!(
            prev.check_next_version(&next),
            VersionCheckResult::CodeIsRequired
        );
        let prev = ProtocolVersion::new(SemVer::new(2, 99, 99));
        let next = ProtocolVersion::new(SemVer::new(3, 0, 0));
        assert_eq!(
            prev.check_next_version(&next),
            VersionCheckResult::CodeIsRequired
        );
    }
    #[test]
    fn should_not_skip_major_version() {
        
        let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
        let next = ProtocolVersion::new(SemVer::new(3, 0, 0));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
    }
    #[test]
    fn should_reject_major_version_rollback() {
        
        let prev = ProtocolVersion::new(SemVer::new(2, 0, 0));
        let next = ProtocolVersion::new(SemVer::new(0, 0, 0));
        assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
    }
    #[test]
    fn should_check_same_version_is_invalid() {
        for ver in &[
            ProtocolVersion::from_parts(1, 0, 0),
            ProtocolVersion::from_parts(1, 2, 0),
            ProtocolVersion::from_parts(1, 2, 3),
        ] {
            assert_eq!(ver.check_next_version(&ver), VersionCheckResult::Invalid);
        }
    }
    #[test]
    fn should_not_be_compatible_with_different_major_version() {
        let current = ProtocolVersion::from_parts(1, 2, 3);
        let other = ProtocolVersion::from_parts(2, 5, 6);
        assert!(!current.is_compatible_with(&other));
        let current = ProtocolVersion::from_parts(1, 0, 0);
        let other = ProtocolVersion::from_parts(2, 0, 0);
        assert!(!current.is_compatible_with(&other));
    }
    #[test]
    fn should_be_compatible_with_equal_major_version_backwards() {
        let current = ProtocolVersion::from_parts(1, 99, 99);
        let other = ProtocolVersion::from_parts(1, 0, 0);
        assert!(current.is_compatible_with(&other));
    }
    #[test]
    fn should_be_compatible_with_equal_major_version_forwards() {
        let current = ProtocolVersion::from_parts(1, 0, 0);
        let other = ProtocolVersion::from_parts(1, 99, 99);
        assert!(current.is_compatible_with(&other));
    }
}