// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under    *
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0          *
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************

#ifndef STRING_TRAITS_H_813274321443234
#define STRING_TRAITS_H_813274321443234

#include <cstring> //strlen
#include <string_view>
#include "type_traits.h"


//uniform access to string-like types, both classes and character arrays
namespace zen
{
/*  isStringLike<>:
        isStringLike<const wchar_t*> //equals "true"
        isStringLike<const int*>     //equals "false"

    GetCharTypeT<>:
        GetCharTypeT<std::wstring> //equals wchar_t
        GetCharTypeT<wchar_t[5]>   //equals wchar_t

    strLength():
        strLength(str);   //equals str.length()
        strLength(array); //equals cStringLength(array)

    strBegin():         -> not null-terminated! -> may be nullptr if length is 0!
        std::wstring str(L"dummy");
        char array[] = "dummy";
        strBegin(str);   //returns str.c_str()
        strBegin(array); //returns array                                           */



//---------------------- implementation ----------------------
namespace impl
{
template <class S, class Char> //test if result of S::c_str() can convert to const Char*
class HasConversion
{
    using Yes = char[1];
    using No  = char[2];

    static Yes& hasConversion(const Char*);
    static  No& hasConversion(...);

public:
    static constexpr bool value = sizeof(hasConversion(std::declval<S>().c_str())) == sizeof(Yes);
};


template <class S, bool isStringClass> struct GetCharTypeImpl { using Type = void; };

template <class S>
struct GetCharTypeImpl<S, true>
{
    using Type = std::conditional_t<HasConversion<S, wchar_t>::value, wchar_t,
          /**/   std::conditional_t<HasConversion<S, char   >::value, char, void>>;

    //using Type = typename S::value_type;
    /*DON'T use S::value_type:
        1. support Glib::ustring: value_type is "unsigned int" but c_str() returns "const char*"
        2. wxString, wxWidgets v2.9, has some questionable string design: wxString::c_str() returns a proxy (wxCStrData) which
           is implicitly convertible to *both* "const char*" and "const wchar_t*" while wxString::value_type is a wrapper around an unsigned int
    */
};

template <> struct GetCharTypeImpl<char,    false> { using Type = char; };
template <> struct GetCharTypeImpl<wchar_t, false> { using Type = wchar_t; };

template <> struct GetCharTypeImpl<std::basic_string_view<char         >, false> { using Type = char; };
template <> struct GetCharTypeImpl<std::basic_string_view<wchar_t      >, false> { using Type = wchar_t; };
template <> struct GetCharTypeImpl<std::basic_string_view<const char   >, false> { using Type = char; };
template <> struct GetCharTypeImpl<std::basic_string_view<const wchar_t>, false> { using Type = wchar_t; };


ZEN_INIT_DETECT_MEMBER_TYPE(value_type)
ZEN_INIT_DETECT_MEMBER(c_str)  //we don't know the exact declaration of the member attribute and it may be in a base class!
ZEN_INIT_DETECT_MEMBER(length) //

template <class S>
class StringTraits
{
    using CleanType       = std::remove_cvref_t<S>;
    using NonArrayType    = std::remove_extent_t <CleanType>;
    using NonPtrType      = std::remove_pointer_t<NonArrayType>;
    using UndecoratedType = std::remove_cv_t     <NonPtrType>; //handle "const char* const"

public:
    static constexpr bool isStringClass = hasMemberType_value_type<CleanType> &&
                                          hasMember_c_str         <CleanType> &&
                                          hasMember_length        <CleanType>;

    using CharType = typename GetCharTypeImpl<UndecoratedType, isStringClass>::Type;

    static constexpr bool isStringLike = std::is_same_v<CharType, char> ||
                                         std::is_same_v<CharType, wchar_t>;
};
}


template <class T>
constexpr bool isStringLike = impl::StringTraits<T>::isStringLike;

template <class T>
using GetCharTypeT = typename impl::StringTraits<T>::CharType;


namespace impl
{
//strlen/wcslen are vectorized since VS14 CTP3
inline size_t cStringLength(const char*    str) { return std::strlen(str); }
inline size_t cStringLength(const wchar_t* str) { return std::wcslen(str); }

#if 0 //no significant perf difference for "comparison" test case between cStringLength/wcslen:
template <class C> inline
size_t cStringLength(const C* str)
{
    static_assert(std::is_same_v<C, char> || std::is_same_v<C, wchar_t>);
    size_t len = 0;
    while (*str++ != 0)
        ++len;
    return len;
}
#endif

template <class S, typename = std::enable_if_t<StringTraits<S>::isStringClass>> inline
const GetCharTypeT<S>* strBegin(const S& str) //SFINAE: T must be a "string"
{
    return str.c_str();
}

inline const char*    strBegin(const char*    str) { return str; }
inline const wchar_t* strBegin(const wchar_t* str) { return str; }
inline const char*    strBegin(const char&    ch)  { return &ch; }
inline const wchar_t* strBegin(const wchar_t& ch)  { return &ch; }

inline const char*    strBegin(const std::basic_string_view<char         >& ref) { return ref.data(); }
inline const wchar_t* strBegin(const std::basic_string_view<wchar_t      >& ref) { return ref.data(); }
inline const char*    strBegin(const std::basic_string_view<const char   >& ref) { return ref.data(); }
inline const wchar_t* strBegin(const std::basic_string_view<const wchar_t>& ref) { return ref.data(); }

template <class S, typename = std::enable_if_t<StringTraits<S>::isStringClass>> inline
size_t strLength(const S& str) //SFINAE: T must be a "string"
{
    return str.length();
}

inline size_t strLength(const char*    str) { return cStringLength(str); }
inline size_t strLength(const wchar_t* str) { return cStringLength(str); }
inline size_t strLength(char)               { return 1; }
inline size_t strLength(wchar_t)            { return 1; }

inline size_t strLength(const std::basic_string_view<char         >& ref) { return ref.length(); }
inline size_t strLength(const std::basic_string_view<wchar_t      >& ref) { return ref.length(); }
inline size_t strLength(const std::basic_string_view<const char   >& ref) { return ref.length(); }
inline size_t strLength(const std::basic_string_view<const wchar_t>& ref) { return ref.length(); }
}


template <class S> inline
auto strBegin(S&& str)
{
    static_assert(isStringLike<S>);
    return impl::strBegin(str);
}


template <class S> inline
size_t strLength(S&& str)
{
    static_assert(isStringLike<S>);
    return impl::strLength(str);
}
}

#endif //STRING_TRAITS_H_813274321443234
