#pragma once
// Copyright (c) 2011, 2012 Alf P. Steinbach
 
 
//--------------------------------------------------------- Dependencies:
 
#include <progrock/cpp/u/natural_encoding.h>                // CPP_NATURAL_ENCODING, RawEncodingUnitFor
#include <progrock/cpp/c++11_emulation.h>                   // CPP_STATIC_ASSERT, CPP_NOEXCEPT
#include <progrock/cpp/data/Size.h>                  // cpp::Size, cpp::bitsPerByte
 
#include <locale>       // std::char_traits
#include <utility>      // comparison operators
 
 
//--------------------------------------------------------- Interface:
 
// You might '#define U CPP_U' within a separately compiled file, for convenience.
// There is some risk that a macro U might conflict with e.g. template parameter.
 
#if     CPP_NATURAL_ENCODING == CPP_ENCODING_UTF16
    CPP_STATIC_ASSERT( sizeof( wchar_t ) == 2 );        // E.g. Windows.
#   define CPP_U_ENCODING ::progrock::cpp::u::NaturalEncoding::utf16
#   define CPP_U( aLiteral ) ::progrock::cpp::u::typed( L##aLiteral )
#elif   CPP_NATURAL_ENCODING == CPP_ENCODING_UTF8
#   define CPP_U_ENCODING ::progrock::cpp::u::NaturalEncoding::utf8
#   define CPP_U( aLiteral ) ::progrock::cpp::u::typed( aLiteral )
#else
#   error "The natural encoding for this OS is not supported, sorry."
#endif
 
namespace progrock { namespace cpp { namespace u {
    using namespace std::rel_ops;       // operator!= etc.
 
    NaturalEncoding::Enum const    encoding    = CPP_U_ENCODING;
 
    //typedef detail::EncodingTraits< encoding >  Traits;
    //
    // Using the traits here brings in too much indirection for Visual C++ to handle:
    //
    //typedef Traits::Raw::Unit              RawEncodingUnit;
    //typedef Traits::Raw::ExtendedUnit      RawExtendedEncodingUnit;
    //typedef Traits::Unit                   EncodingUnit;
    //typedef Traits::ExtendedUnit           ExtendedEncodingUnit;
 
    typedef RawEncodingUnitFor< encoding >::Type            RawEncodingUnit;
    typedef std::char_traits<RawEncodingUnit>::int_type     RawExtendedEncodingUnit;
    typedef enum: RawEncodingUnit {}                        EncodingUnit;
    typedef enum: RawExtendedEncodingUnit {}                ExtendedEncodingUnit;
 
    typedef RawEncodingUnit RawCh;  // For associative mnemonic value, although misleading!
    typedef EncodingUnit    Ch;     // For associative mnemonic value, although misleading!
 
 
    CPP_STATIC_ASSERT( sizeof( EncodingUnit ) == sizeof( RawEncodingUnit ) );
    CPP_STATIC_ASSERT( sizeof( ExtendedEncodingUnit ) == sizeof( RawExtendedEncodingUnit ) );
 
    namespace detail {
        inline RawEncodingUnit raw( EncodingUnit const v ) CPP_NOEXCEPT
        {
            return v;
        }
 
        inline RawExtendedEncodingUnit raw( ExtendedEncodingUnit const v ) CPP_NOEXCEPT
        {
            return v;
        }
 
        template< class TpEncodingUnit >    // Templating to de-emphasize re overload resolution.
        inline RawEncodingUnit* raw( TpEncodingUnit* p ) CPP_NOEXCEPT;
 
        template<>
        inline RawEncodingUnit* raw<EncodingUnit>( EncodingUnit* p ) CPP_NOEXCEPT
        {
            return reinterpret_cast< RawEncodingUnit* >( p );
        }
 
        template< class TpEncodingUnit >    // Templating to de-emphasize re overload resolution.
        inline RawEncodingUnit const* raw( TpEncodingUnit const* p ) CPP_NOEXCEPT;
 
        template<>
        inline RawEncodingUnit const* raw<EncodingUnit>( EncodingUnit const* p ) CPP_NOEXCEPT
        {
            return reinterpret_cast< RawEncodingUnit const* >( p );
        }
 
        inline EncodingUnit typed( RawEncodingUnit const v ) CPP_NOEXCEPT
        {
            return EncodingUnit( v );
        }
 
        inline EncodingUnit* typed( RawEncodingUnit* const p ) CPP_NOEXCEPT
        {
            return reinterpret_cast< EncodingUnit* >( p );
        }
 
        inline EncodingUnit const* typed( RawEncodingUnit const* const p ) CPP_NOEXCEPT
        {
            return reinterpret_cast< EncodingUnit const* >( p );
        }
    }  // namespace detail
 
    template< Size size >
    inline RawEncodingUnit (&raw( EncodingUnit (&s)[size] )) [size]
    {
        return reinterpret_cast< RawEncodingUnit (&)[size] >( s );
    }
 
    template< Size size >
    inline RawEncodingUnit const (&raw( EncodingUnit const (&s)[size] ) CPP_NOEXCEPT)[size]
    {
        return reinterpret_cast< RawEncodingUnit const (&)[size] >( s );
    }
 
    template< size_t size >
    inline EncodingUnit (&typed( RawEncodingUnit (&s)[size] ) CPP_NOEXCEPT)[size]
    {
        return reinterpret_cast< EncodingUnit (&)[size] >( s );
    }
 
    template< size_t size >
    inline EncodingUnit const (&typed( RawEncodingUnit const (&s)[size] ) CPP_NOEXCEPT)[size]
    {
        return reinterpret_cast< EncodingUnit const (&)[size] >( s );
    }
 
    template< class Arg >
    inline auto raw( Arg&& arg ) -> decltype( detail::raw( arg ) )
    {
        return detail::raw( arg );
    }
 
    template< class Arg >
    inline auto typed( Arg&& arg ) -> decltype( detail::typed( arg ) )
    {
        return detail::typed( arg );
    }
 
    enum Adl {};
 
    inline EncodingUnit typed( RawEncodingUnit const v, Adl ) CPP_NOEXCEPT
    {
        return EncodingUnit( v );
    }
 
    inline EncodingUnit* typed( RawEncodingUnit* const p, Adl ) CPP_NOEXCEPT
    {
        return reinterpret_cast< EncodingUnit* >( p );
    }
 
    inline EncodingUnit const* typed( RawEncodingUnit const* const p, Adl ) CPP_NOEXCEPT
    {
        return reinterpret_cast< EncodingUnit const* >( p );
    }
 
    template< size_t size >
    inline EncodingUnit (&typed( RawEncodingUnit (&s)[size], Adl ) CPP_NOEXCEPT)[size]
    {
        return reinterpret_cast< EncodingUnit (&)[size] >( s );
    }
 
    template< size_t size >
    inline EncodingUnit const (&typed( RawEncodingUnit const (&s)[size], Adl ) CPP_NOEXCEPT)[size]
    {
        return reinterpret_cast< EncodingUnit const (&)[size] >( s );
    }
 
} } }  // namespace progrock::cpp::u
 
namespace std {
 
    // Requirements specified by C++11 §21.2.1/1 table 62.
    template<>
    struct char_traits< ::progrock::cpp::u::EncodingUnit >
    {
    private:
        typedef ::progrock::cpp::u::Adl                 AdlCppU;
        typedef ::progrock::cpp::u::RawEncodingUnit     RawEncodingUnit;
 
        static AdlCppU const    cppU    = AdlCppU();
 
    public:
        typedef ::progrock::cpp::u::EncodingUnit            char_type;
        typedef ::progrock::cpp::u::ExtendedEncodingUnit    int_type;
 
        typedef std::char_traits< RawEncodingUnit >  Std;
 
        typedef Std::off_type                  off_type;
        typedef Std::pos_type                  pos_type;
        typedef Std::state_type                state_type;
 
        static bool eq( char_type a, char_type b ) CPP_NOEXCEPT
        { return (a == b); }
 
        static bool lt( char_type a, char_type b ) CPP_NOEXCEPT
        { return (a < b); }
 
        static int compare( char_type const* s1, char_type const* s2, size_t n )
        { return Std::compare( raw( s1 ), raw( s2 ), n ); }
 
        static size_t length( char_type const* s )
        { return Std::length( raw( s ) ); }
 
        static char_type const* find( char_type const* s, size_t n, char_type const a )
        { return typed( Std::find( raw( s ), n, raw( a ) ), cppU ); }
 
        static char_type* move( char_type* s1, char_type const* s2, size_t n )
        { return typed( Std::move( raw( s1 ), raw( s2 ), n ), cppU ); }
 
        static char_type* copy( char_type* s1, char_type const* s2, size_t n )
        { return typed( Std::copy( raw( s1 ), raw( s2 ), n ), cppU ); }
 
        static void assign( char_type& c1, char_type const c2 ) CPP_NOEXCEPT
        { c1 = c2; }
 
        static char_type* assign( char_type* s, size_t n, char_type const a )
        { return typed( Std::assign( raw( s ), n, raw( a ) ), cppU ); }
 
        static int_type not_eof( int_type const c ) CPP_NOEXCEPT
        { return int_type( Std::not_eof( Std::int_type( c ) ) ); }
 
        static char_type to_char_type( int_type const c ) CPP_NOEXCEPT
        { return typed( raw( c ), cppU ); }
 
        static int_type to_int_type( char_type const c ) CPP_NOEXCEPT
        { return int_type( c ); }
 
        static bool eq_int_type( int_type const c1, int_type const c2 ) CPP_NOEXCEPT
        { return (c1 == c2); }
 
        static int_type eof() CPP_NOEXCEPT
        { return int_type( Std::eof() ); }
    };
 
}  // namespace std
 
namespace progrock { namespace cpp { namespace u {
 
    typedef std::char_traits< Ch > ChTraits;
 
} } }  // namespace progrock::cpp::u