#[cfg(feature = "std")]
use std::rc::Rc;
use crate::{
    val::ValConvert,
    xdr::{ScErrorCode, ScErrorType},
    Env, Error, Tag, Val,
};
use core::cmp::Ordering;
pub trait Compare<T> {
    type Error;
    fn compare(&self, a: &T, b: &T) -> Result<Ordering, Self::Error>;
}
impl<T, C: Compare<T>> Compare<&T> for C {
    type Error = C::Error;
    fn compare(&self, a: &&T, b: &&T) -> Result<Ordering, Self::Error> {
        <C as Compare<T>>::compare(self, *a, *b)
    }
}
impl<T, C: Compare<T>> Compare<Option<T>> for C {
    type Error = C::Error;
    fn compare(&self, a: &Option<T>, b: &Option<T>) -> Result<Ordering, Self::Error> {
        match (a, b) {
            (Some(a), Some(b)) => <C as Compare<T>>::compare(self, a, b),
            (None, None) => Ok(Ordering::Equal),
            (None, Some(_)) => Ok(Ordering::Less),
            (Some(_), None) => Ok(Ordering::Greater),
        }
    }
}
macro_rules! impl_compare_for_tuple {
    ( $($idx:tt $T:ident),+) => {
        impl<$($T,)+ E, C> Compare<($($T,)+)> for C
        where
            $(C: Compare<$T, Error = E>,)+
        {
            type Error = E;
            fn compare(&self, a: &($($T,)+), b: &($($T,)+)) -> Result<Ordering, Self::Error> {
                $(
                    match <C as Compare<$T>>::compare(self, &a.$idx, &b.$idx)? {
                        unequal @ (Ordering::Less | Ordering::Greater) => return Ok(unequal),
                        _ => ()
                    }
                )*
                Ok(Ordering::Equal)
            }
        }
    };
}
impl_compare_for_tuple!(0 T, 1 U);
impl_compare_for_tuple!(0 T, 1 U, 2 V);
impl_compare_for_tuple!(0 T, 1 U, 2 V, 3 W);
impl_compare_for_tuple!(0 T, 1 U, 2 V, 3 W, 4 X);
#[cfg(feature = "std")]
impl<T, C: Compare<T>> Compare<Vec<T>> for C {
    type Error = C::Error;
    fn compare(&self, a: &Vec<T>, b: &Vec<T>) -> Result<Ordering, Self::Error> {
        let mut i = 0;
        loop {
            match (a.get(i), b.get(i)) {
                (None, None) => return Ok(Ordering::Equal),
                (None, Some(_)) => return Ok(Ordering::Less),
                (Some(_), None) => return Ok(Ordering::Greater),
                (Some(a), Some(b)) => match <C as Compare<T>>::compare(self, a, b)? {
                    Ordering::Equal => i += 1,
                    unequal => return Ok(unequal),
                },
            }
        }
    }
}
#[cfg(feature = "std")]
impl<T, C: Compare<T>> Compare<Box<T>> for C {
    type Error = C::Error;
    fn compare(&self, a: &Box<T>, b: &Box<T>) -> Result<Ordering, Self::Error> {
        <Self as Compare<T>>::compare(self, a, b)
    }
}
#[cfg(feature = "std")]
impl<T, C: Compare<T>> Compare<Rc<T>> for C {
    type Error = C::Error;
    fn compare(&self, a: &Rc<T>, b: &Rc<T>) -> Result<Ordering, Self::Error> {
        <Self as Compare<T>>::compare(self, a, b)
    }
}
macro_rules! delegate_compare_to_wrapper {
    ($T:ident,$A:ident,$B:ident,$SELF:ident) => {{
        let a = unsafe { crate::$T::unchecked_from_val(*$A) };
        let b = unsafe { crate::$T::unchecked_from_val(*$B) };
        $SELF.compare(&a, &b)
    }};
}
#[allow(clippy::comparison_chain)]
impl<E: Env> Compare<Val> for E {
    type Error = E::Error;
    fn compare(&self, a: &Val, b: &Val) -> Result<Ordering, Self::Error> {
        if a.get_payload() == b.get_payload() {
            return Ok(Ordering::Equal);
        }
        if a.is_object() || b.is_object() {
            let v = self.obj_cmp(*a, *b)?;
            return if v == 0 {
                Ok(Ordering::Equal)
            } else if v < 0 {
                Ok(Ordering::Less)
            } else {
                Ok(Ordering::Greater)
            };
        }
        let a_tag = a.get_tag();
        let b_tag = b.get_tag();
        if a_tag < b_tag {
            Ok(Ordering::Less)
        } else if a_tag > b_tag {
            Ok(Ordering::Greater)
        } else {
            match a_tag {
                Tag::False => Ok(Ordering::Equal),
                Tag::True => Ok(Ordering::Equal),
                Tag::Void => Ok(Ordering::Equal),
                Tag::Error => delegate_compare_to_wrapper!(Error, a, b, self),
                Tag::U32Val => delegate_compare_to_wrapper!(U32Val, a, b, self),
                Tag::I32Val => delegate_compare_to_wrapper!(I32Val, a, b, self),
                Tag::U64Small => delegate_compare_to_wrapper!(U64Small, a, b, self),
                Tag::I64Small => delegate_compare_to_wrapper!(I64Small, a, b, self),
                Tag::TimepointSmall => delegate_compare_to_wrapper!(TimepointSmall, a, b, self),
                Tag::DurationSmall => delegate_compare_to_wrapper!(DurationSmall, a, b, self),
                Tag::U128Small => delegate_compare_to_wrapper!(U128Small, a, b, self),
                Tag::I128Small => delegate_compare_to_wrapper!(I128Small, a, b, self),
                Tag::U256Small => delegate_compare_to_wrapper!(U256Small, a, b, self),
                Tag::I256Small => delegate_compare_to_wrapper!(I256Small, a, b, self),
                Tag::SymbolSmall => delegate_compare_to_wrapper!(SymbolSmall, a, b, self),
                Tag::LedgerKeyContractInstance => Ok(Ordering::Equal),
                Tag::SmallCodeUpperBound => Ok(Ordering::Equal),
                Tag::ObjectCodeLowerBound => Ok(Ordering::Equal),
                Tag::U64Object
                | Tag::I64Object
                | Tag::TimepointObject
                | Tag::DurationObject
                | Tag::U128Object
                | Tag::I128Object
                | Tag::U256Object
                | Tag::I256Object
                | Tag::BytesObject
                | Tag::StringObject
                | Tag::SymbolObject
                | Tag::VecObject
                | Tag::MapObject
                | Tag::AddressObject => Err(Error::from_type_and_code(
                    ScErrorType::Context,
                    ScErrorCode::InternalError,
                )
                .into()),
                Tag::ObjectCodeUpperBound => Ok(Ordering::Equal),
                Tag::Bad => Ok(Ordering::Equal),
            }
        }
    }
}