Compare.h

// Compare.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2022

@import "Reflection.h"
#include "Ceda/cxUtils/Detect.h"
#include "Ceda/cxUtils/BasicTypes.h"
#include "Ceda/cxUtils/xostream.h"
#include "Ceda/cxUtils/xstring.h"

/*
We need both < and == comparisons
---------------------------------

If < is defined then all 6 comparison operations can be deduced:

    a < b         <-->        a < b
    a <= b        <-->        not (b < a)
    a > b         <-->        b < a
    a >= b        <-->        not (a < b)
    a == b        <-->        not (a < b) and not (b < a)
    a != b        <-->        (a < b) or (b < a)

Note however that == is not redundant because some types support == but not <

For this reason in the functions below we provide both Lt and Eq versions.  This also means that 
regardless of which of the 6 comparison operations we need to perform, it can be achieved with
just a single call (either to a Lt or an Eq function).

Equality testing
----------------

In python a bool is treated as an integer, and so can be compared with an integer.
Do we do likewise in C++?

Proposal:

    bool is its own type and cannot be implicit compared to any other type

    The numeric types are:
        int8, int16, int32, int64, uint8, uint16, uint32, uint64, char8, char16, float32, float64
    These can be compared to each other

    string8, string16 can be compared - they are different encodings of the same thing (unicode text strings)

    class :  It is assumed each class is its own independent type
    (or maybe we support coercions between classes)

TypeOps function to allow comparison with any other type
--------------------------------------------------------

    ECompareResult compare(T& lhs, const ReflectedType& rhs_type, const void* rhs);

*/

namespace ceda
{
struct ReflectedType;
struct TypeOps;

enum class ECompareResult
{
    None,
    Less,
    Equal,
    Greater,
    NotEqual
};

inline ECompareResult Reverse(ECompareResult e)
{
    if (e == ECompareResult::Less) 
        return ECompareResult::Greater;
    if (e == ECompareResult::Greater) 
        return ECompareResult::Less;
    return e;
}

@api ConstStringZ ToString(ECompareResult r);

inline xostream& operator<<(xostream& os, ECompareResult r)
{
    os << ToString(r);
    return os;
}

@api ECompareResult Compare(const TypeOps& ops, const void* data1, const void* data2);

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4804 4805 4244)
#endif
template<typename T1, typename T2>
inline ECompareResult Compare(const T1& x, const T2& y)
{
    if constexpr (has_compare_equal<T1,T2>::value)
    {
        // Otherwise if == exists then we can return equal or not-equal
        if (x == y)
        {
            return ECompareResult::Equal;
        }
        if constexpr (has_compare_less<T1,T2>::value)
        {
            return (x < y) ? ECompareResult::Less : ECompareResult::Greater;
        }
        else
        {
            return ECompareResult::NotEqual;
        }
    }
    else
    {
        // No comparisons at all
        return ECompareResult::None;
    }
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif

/*
The flag checkReverse is used to avoid a infinite recursion given that to compare(T1,T2) we'd like to try compare(T2,T1)
*/
template<typename T>
ECompareResult RVCompare(const T& v, const ReflectedType& rtype, const void* data, bool checkReverse = true)
{
    if (rtype.tag == EReflectedTypeTag::Leaf)
    {
        switch(rtype.Leaf->tag)
        {
            case ELeafType::Bool :    return Compare(v, *reinterpret_cast<const bool*>(data));
            case ELeafType::Int8 :    return Compare(v, *reinterpret_cast<const int8*>(data));
            case ELeafType::Int16 :   return Compare(v, *reinterpret_cast<const int16*>(data));
            case ELeafType::Int32 :   return Compare(v, *reinterpret_cast<const int32*>(data));
            case ELeafType::Int64 :   return Compare(v, *reinterpret_cast<const int64*>(data));
            case ELeafType::Uint8 :   return Compare(v, *reinterpret_cast<const uint8*>(data));
            case ELeafType::Uint16 :  return Compare(v, *reinterpret_cast<const uint16*>(data));
            case ELeafType::Uint32 :  return Compare(v, *reinterpret_cast<const uint32*>(data));
            case ELeafType::Uint64 :  return Compare(v, *reinterpret_cast<const uint64*>(data));
            case ELeafType::Float32 : return Compare(v, *reinterpret_cast<const float32*>(data));
            case ELeafType::Float64 : return Compare(v, *reinterpret_cast<const float64*>(data));
            case ELeafType::Char8 :   return Compare(v, *reinterpret_cast<const char8*>(data));
            case ELeafType::Char16 :  return Compare(v, *reinterpret_cast<const char16*>(data));
            case ELeafType::String8 : return Compare(v, *reinterpret_cast<const string8*>(data));
            case ELeafType::String16 :return Compare(v, *reinterpret_cast<const string16*>(data));
            default:
                return ECompareResult::None;
        }
    }

    if (const TypeOps* ops = GetTypeOps(rtype))
    {
        if (&GetTypeOps<T>() == ops)
        {
            return Compare(*ops, &v, data);
        }
        if constexpr( has_GetReflectedType<T>::value )
        {
            if (checkReverse && ops->rvCompareFn)
            {
                return Reverse(ops->rvCompareFn(data, GetReflectedType<T>(), &v, false));
            }
        }
    }
    return ECompareResult::None;
}

@api ECompareResult Compare(const ReflectedType& lhs_type, const void* lhs, const ReflectedType& rhs_type, const void* rhs);
} // namespace ceda