use std::fmt::Debug;
use thiserror::Error;
use super::region_validation_error::RegionValidationError;
use crate::memory::Region;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum CommunicationError {
    #[error(
        "The Wasm memory address {} provided by the contract could not be dereferenced: {}",
        offset,
        msg
    )]
    DerefErr {
        offset: u32,
        msg: String,
    },
    #[error("Got an invalid value for iteration order: {}", value)]
    InvalidOrder { value: i32 },
    #[error("Got an invalid region: {}", source)]
    InvalidRegion {
        #[from]
        source: RegionValidationError,
    },
    #[error("Cannot decode UTF8 bytes into string: {}", msg)]
    InvalidUtf8 { msg: String },
    #[error("Region length too big. Got {}, limit {}", length, max_length)]
    RegionLengthTooBig { length: usize, max_length: usize },
    #[error("Region too small. Got {}, required {}", size, required)]
    RegionTooSmall { size: usize, required: usize },
    #[error("Tried to access memory of region {:?} in Wasm memory of size {} bytes. This typically happens when the given Region pointer does not point to a proper Region struct.", region, memory_size)]
    RegionAccessErr {
        region: Region,
        memory_size: usize,
    },
    #[error("Got a zero Wasm address")]
    ZeroAddress {},
}
impl CommunicationError {
    pub(crate) fn deref_err(offset: u32, msg: impl Into<String>) -> Self {
        CommunicationError::DerefErr {
            offset,
            msg: msg.into(),
        }
    }
    #[allow(dead_code)]
    pub(crate) fn invalid_order(value: i32) -> Self {
        CommunicationError::InvalidOrder { value }
    }
    #[allow(dead_code)]
    pub(crate) fn invalid_utf8(msg: impl ToString) -> Self {
        CommunicationError::InvalidUtf8 {
            msg: msg.to_string(),
        }
    }
    pub(crate) fn region_length_too_big(length: usize, max_length: usize) -> Self {
        CommunicationError::RegionLengthTooBig { length, max_length }
    }
    pub(crate) fn region_too_small(size: usize, required: usize) -> Self {
        CommunicationError::RegionTooSmall { size, required }
    }
    pub(crate) fn region_access_err(region: Region, memory_size: usize) -> Self {
        CommunicationError::RegionAccessErr {
            region,
            memory_size,
        }
    }
    pub(crate) fn zero_address() -> Self {
        CommunicationError::ZeroAddress {}
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn deref_err() {
        let error = CommunicationError::deref_err(345, "broken stuff");
        match error {
            CommunicationError::DerefErr { offset, msg, .. } => {
                assert_eq!(offset, 345);
                assert_eq!(msg, "broken stuff");
            }
            e => panic!("Unexpected error: {e:?}"),
        }
    }
    #[test]
    fn invalid_order() {
        let error = CommunicationError::invalid_order(-745);
        match error {
            CommunicationError::InvalidOrder { value, .. } => assert_eq!(value, -745),
            e => panic!("Unexpected error: {e:?}"),
        }
    }
    #[test]
    fn invalid_utf8() {
        let error = CommunicationError::invalid_utf8("broken");
        match error {
            CommunicationError::InvalidUtf8 { msg, .. } => assert_eq!(msg, "broken"),
            e => panic!("Unexpected error: {e:?}"),
        }
    }
    #[test]
    fn region_length_too_big_works() {
        let error = CommunicationError::region_length_too_big(50, 20);
        match error {
            CommunicationError::RegionLengthTooBig {
                length, max_length, ..
            } => {
                assert_eq!(length, 50);
                assert_eq!(max_length, 20);
            }
            e => panic!("Unexpected error: {e:?}"),
        }
    }
    #[test]
    fn region_too_small_works() {
        let error = CommunicationError::region_too_small(12, 33);
        match error {
            CommunicationError::RegionTooSmall { size, required, .. } => {
                assert_eq!(size, 12);
                assert_eq!(required, 33);
            }
            e => panic!("Unexpected error: {e:?}"),
        }
    }
    #[test]
    fn zero_address() {
        let error = CommunicationError::zero_address();
        match error {
            CommunicationError::ZeroAddress { .. } => {}
            e => panic!("Unexpected error: {e:?}"),
        }
    }
}