// Copyright (c) 2015-2016 Dr. Colin Hirsch and Daniel Frey
// Please see LICENSE for license or visit https://github.com/taocpp/json/

#ifndef TAOCPP_JSON_INCLUDE_VALUE_HH
#define TAOCPP_JSON_INCLUDE_VALUE_HH

#include <cstdint>
#include <cmath>
#include <cassert>
#include <utility>
#include <stdexcept>

#include "external/operators.hpp"
#include "external/optional.hpp"

#include "internal/totally_ordered.hh"
#include "internal/value_union.hh"
#include "internal/get_by_enum.hh"
#include "internal/throw.hh"

#include "type.hh"
#include "traits.hh"
#include "pair.hh"
#include "single.hh"
#include "pointer.hh"

namespace tao
{
   namespace json
   {
      namespace internal
      {
         template< typename, typename, typename = void > struct has_as : std::false_type {};
         template< typename T, typename V > struct has_as< T, V, decltype( T::as( std::declval< const V & >() ), void() ) > : std::true_type {};

         // required work-around for GCC 6
         inline void rethrow()
         {
            throw;
         }

      } // internal

      template< template< typename ... > class Traits >
      class basic_value
           : operators::totally_ordered< basic_value< Traits > >,
             internal::totally_ordered< basic_value< Traits >, null_t, type::NULL_ >,
             internal::totally_ordered< basic_value< Traits >, bool, type::BOOLEAN >,
             internal::totally_ordered< basic_value< Traits >, signed char, type::SIGNED >,
             internal::totally_ordered< basic_value< Traits >, unsigned char, type::UNSIGNED >,
             internal::totally_ordered< basic_value< Traits >, signed short, type::SIGNED >,
             internal::totally_ordered< basic_value< Traits >, unsigned short, type::UNSIGNED >,
             internal::totally_ordered< basic_value< Traits >, signed int, type::SIGNED >,
             internal::totally_ordered< basic_value< Traits >, unsigned int, type::UNSIGNED >,
             internal::totally_ordered< basic_value< Traits >, signed long, type::SIGNED >,
             internal::totally_ordered< basic_value< Traits >, unsigned long, type::UNSIGNED >,
             internal::totally_ordered< basic_value< Traits >, signed long long, type::SIGNED >,
             internal::totally_ordered< basic_value< Traits >, unsigned long long, type::UNSIGNED >,
             internal::totally_ordered< basic_value< Traits >, float, type::DOUBLE >,
             internal::totally_ordered< basic_value< Traits >, double, type::DOUBLE >,
             internal::totally_ordered< basic_value< Traits >, std::string, type::STRING >,
             internal::totally_ordered< basic_value< Traits >, const char *, type::STRING >,
             internal::totally_ordered< basic_value< Traits >, std::vector< basic_value< Traits > >, type::ARRAY >,
             internal::totally_ordered< basic_value< Traits >, empty_array_t, type::ARRAY >,
             internal::totally_ordered< basic_value< Traits >, std::map< std::string, basic_value< Traits > >, type::OBJECT >,
             internal::totally_ordered< basic_value< Traits >, empty_object_t, type::OBJECT >,
             internal::totally_ordered< basic_value< Traits >, const basic_value< Traits > *, type::RAW_PTR >,
             internal::totally_ordered< basic_value< Traits >, basic_value< Traits > *, type::RAW_PTR >,
             internal::totally_ordered< basic_value< Traits >, std::nullptr_t, type::RAW_PTR >
      {
      public:
         using array_t = std::vector< basic_value >;
         using object_t = std::map< std::string, basic_value >;

         basic_value() noexcept
         { }

         basic_value( basic_value && r ) noexcept
              : m_type( r.m_type )
         {
            seize( std::move( r ) );
         }

         // required work-around for a bug in older GCCs (<4.9)
         basic_value( const basic_value && r )
              : m_type( r.m_type )
         {
            embed( r );
         }

         basic_value( const basic_value & r )
              : m_type( r.m_type )
         {
            embed( r );
         }

         basic_value( basic_value & r )
              : basic_value( static_cast< const basic_value & >( r ) )
         { }

         template< typename T >
         basic_value( T && v ) noexcept( noexcept( Traits< typename std::decay< T >::type >::assign( std::declval< basic_value & >(), std::forward< T >( v ) ) ) )
         {
            try {
               using D = typename std::decay< T >::type;
               Traits< D >::assign( *this, std::forward< T >( v ) );
            }
            catch( ... ) {
               unsafe_discard();
               const_cast< volatile json::type & >( m_type ) = json::type::DISCARDED;
               internal::rethrow();
            }
         }

         basic_value( std::initializer_list< pair< Traits > > && l )
         {
            try {
               unsafe_assign( std::move( l ) );
            }
            catch( ... ) {
               unsafe_discard();
               const_cast< volatile json::type & >( m_type ) = json::type::DISCARDED;
               throw;
            }
         }

         basic_value( const std::initializer_list< pair< Traits > > & l )
         {
            try {
               unsafe_assign( l );
            }
            catch( ... ) {
               unsafe_discard();
               const_cast< volatile json::type & >( m_type ) = json::type::DISCARDED;
               throw;
            }
         }

         basic_value( std::initializer_list< pair< Traits > > & l )
              : basic_value( static_cast< const std::initializer_list< pair< Traits > > & >( l ) )
         { }

         ~basic_value() noexcept
         {
            unsafe_discard();
            assert( ( const_cast< volatile json::type & >( m_type ) = json::type::DISCARDED, true ) );
         }

         static basic_value array( std::initializer_list< single< Traits > > && l )
         {
            basic_value v;
            v.append( std::move( l ) );
            return v;
         }

         static basic_value array( const std::initializer_list< single< Traits > > & l )
         {
            basic_value v;
            v.append( l );
            return v;
         }

         basic_value & operator= ( basic_value v ) noexcept
         {
            unsafe_discard();
            m_type = v.m_type;
            seize( std::move( v ) );
            return * this;
         }

         void swap( basic_value & r ) noexcept
         {
            basic_value t( std::move( r ) );
            r = std::move( * this );
            ( * this ) = ( std::move( t ) );
         }

         json::type type() const noexcept
         {
            return m_type;
         }

         explicit operator bool() const noexcept
         {
            assert( m_type != json::type::DISCARDED );
            return m_type != json::type::UNINITIALIZED;
         }

         bool is_null() const noexcept
         {
            return m_type == json::type::NULL_;
         }

         bool is_boolean() const noexcept
         {
            return m_type == json::type::BOOLEAN;
         }

         bool is_signed() const noexcept
         {
            return m_type == json::type::SIGNED;
         }

         bool is_unsigned() const noexcept
         {
            return m_type == json::type::UNSIGNED;
         }

         bool is_integer() const noexcept
         {
            return is_signed() || is_unsigned();
         }

         bool is_double() const noexcept
         {
            return m_type == json::type::DOUBLE;
         }

         bool is_number() const noexcept
         {
            return is_integer() || is_double();
         }

         bool is_string() const noexcept
         {
            return m_type == json::type::STRING;
         }

         bool is_array() const noexcept
         {
            return m_type == json::type::ARRAY;
         }

         bool is_object() const noexcept
         {
            return m_type == json::type::OBJECT;
         }

         bool is_raw_ptr() const noexcept
         {
            return m_type == json::type::RAW_PTR;
         }

         bool get_boolean() const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::BOOLEAN );
            return unsafe_get_boolean();
         }

         std::int64_t get_signed() const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::SIGNED );
            return unsafe_get_signed();
         }

         std::uint64_t get_unsigned() const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::UNSIGNED );
            return unsafe_get_unsigned();
         }

         double get_double() const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::DOUBLE );
            return unsafe_get_double();
         }

         std::string & get_string()
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::STRING );
            return unsafe_get_string();
         }

         const std::string & get_string() const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::STRING );
            return unsafe_get_string();
         }

         array_t & get_array()
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::ARRAY );
            return unsafe_get_array();
         }

         const array_t & get_array() const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::ARRAY );
            return unsafe_get_array();
         }

         object_t & get_object()
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::OBJECT );
            return unsafe_get_object();
         }

         const object_t & get_object() const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::OBJECT );
            return unsafe_get_object();
         }

         const basic_value * get_raw_ptr() const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::RAW_PTR );
            return unsafe_get_raw_ptr();
         }

         template< json::type E >
         decltype( internal::get_by_enum< E >::get( std::declval< internal::value_union< basic_value > & >() ) ) get()
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, E );
            return internal::get_by_enum< E >::get( m_union );
         }

         template< json::type E >
         decltype( internal::get_by_enum< E >::get( std::declval< const internal::value_union< basic_value > & >() ) ) get() const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, E );
            return internal::get_by_enum< E >::get( m_union );
         }

         template< typename T >
         void extract( T& v ) const
         {
            Traits< typename std::decay< T >::type >::extract( * this, v );
         }

         template< typename T >
         typename std::enable_if< internal::has_as< Traits< T >, basic_value >::value, T >::type as() const
         {
            return Traits< T >::as( * this );
         }

         template< typename T >
         typename std::enable_if< ! internal::has_as< Traits< T >, basic_value >::value, T >::type as() const
         {
            T nrv;
            extract( nrv );
            return nrv;
         }

         template< typename T >
         tao::optional< T > optional() const
         {
            if ( this->is_null() ) {
               return tao::nullopt;
            }
            else {
               return this->as< T >();
            }
         }

         // The unsafe_get_*() accessor functions MUST NOT be
         // called when the type of the value is not the one
         // corresponding to the type of the accessor!

         bool unsafe_get_boolean() const noexcept
         {
            return m_union.b;
         }

         std::int64_t unsafe_get_signed() const noexcept
         {
            return m_union.i;
         }

         std::uint64_t unsafe_get_unsigned() const noexcept
         {
            return m_union.u;
         }

         double unsafe_get_double() const noexcept
         {
            return m_union.d;
         }

         std::string & unsafe_get_string() noexcept
         {
            return m_union.s;
         }

         const std::string & unsafe_get_string() const noexcept
         {
            return m_union.s;
         }

         array_t & unsafe_get_array() noexcept
         {
            return m_union.a;
         }

         const array_t & unsafe_get_array() const noexcept
         {
            return m_union.a;
         }

         object_t & unsafe_get_object() noexcept
         {
            return m_union.o;
         }

         const object_t & unsafe_get_object() const noexcept
         {
            return m_union.o;
         }

         const basic_value * unsafe_get_raw_ptr() const noexcept
         {
            return m_union.p;
         }

         template< json::type E >
         decltype( internal::get_by_enum< E >::get( std::declval< internal::value_union< basic_value > & >() ) ) unsafe_get()
         {
            return internal::get_by_enum< E >::get( m_union );
         }

         template< json::type E >
         decltype( internal::get_by_enum< E >::get( std::declval< const internal::value_union< basic_value > & >() ) ) unsafe_get() const
         {
            return internal::get_by_enum< E >::get( m_union );
         }

         const basic_value * skip_raw_ptr() const noexcept
         {
            const basic_value * p = this;
            while ( p && p->is_raw_ptr() ) {
               p = p->unsafe_get_raw_ptr();
            }
            return p;
         }

         basic_value * unsafe_find( const std::string & key ) noexcept
         {
            const auto it = m_union.o.find( key );
            return ( it != m_union.o.end() ) ? ( & it->second ) : nullptr;
         }

         const basic_value * unsafe_find( const std::string & key ) const noexcept
         {
            const auto it = m_union.o.find( key );
            return ( it != m_union.o.end() ) ? ( & it->second ) : nullptr;
         }

         basic_value * find( const std::string & key )
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::OBJECT );
            return unsafe_find( key );
         }

         const basic_value * find( const std::string & key ) const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::OBJECT );
            return unsafe_find( key );
         }

         basic_value & at( const std::size_t index )
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::ARRAY );
            return m_union.a.at( index );
         }

         const basic_value & at( const std::size_t index ) const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::ARRAY );
            return m_union.a.at( index );
         }

         basic_value & at( const std::string & key )
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::OBJECT );
            return m_union.o.at( key );
         }

         const basic_value & at( const std::string & key ) const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::OBJECT );
            return m_union.o.at( key );
         }

         basic_value & at( const pointer & k )
         {
            return internal::pointer_at( this, k.begin(), k.end() );
         }

         const basic_value & at( const pointer & k ) const
         {
            return internal::pointer_at( this, k.begin(), k.end() );
         }

         basic_value & unsafe_at( const std::size_t index ) noexcept
         {
            return m_union.a[ index ];
         }

         const basic_value & unsafe_at( const std::size_t index ) const noexcept
         {
            return m_union.a[ index ];
         }

         basic_value & unsafe_at( const std::string & key ) noexcept
         {
            return m_union.o.find( key )->second;
         }

         const basic_value & unsafe_at( const std::string & key ) const noexcept
         {
            return m_union.o.find( key )->second;
         }

         void erase( const std::size_t index )
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::ARRAY );
            auto & a = m_union.a;
            if ( index >= a.size() ) {
               throw std::out_of_range( "JSON array index out of bounds" );
            }
            a.erase( a.begin() + index );
         }

         void erase( const std::string & key )
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::OBJECT );
            if ( m_union.o.erase( key ) == 0 ) {
               throw std::out_of_range( "JSON object key not found: " + key );
            }
         }

         void erase( const pointer & k )
         {
            if ( ! k ) {
               throw std::runtime_error( "invalid root JSON Pointer for erase" );
            }
            const auto b = k.begin();
            const auto e = std::prev( k.end() );
            basic_value & v = internal::pointer_at( this, b, e );
            switch ( v.m_type ) {
               case json::type::ARRAY:
                  v.erase( e->index() );
                  break;
               case json::type::OBJECT:
                  v.erase( e->key() );
                  break;
               default:
                  throw internal::invalid_type( b, std::next( e ) );
            }
         }

         basic_value & insert( const pointer & k, basic_value value )
         {
            if ( ! k ) {
               * this = std::move( value );
               return * this;
            }
            const auto b = k.begin();
            const auto e = std::prev( k.end() );
            basic_value & v = internal::pointer_at( this, b, e );
            switch ( v.m_type ) {
               case json::type::ARRAY:
                  {
                     if ( e->key() == "-" ) {
                        v.unsafe_emplace_back( std::move( value ) );
                        return v.m_union.a.back();
                     }
                     const auto i = e->index();
                     if ( i >= v.m_union.a.size() ) {
                        throw std::out_of_range( "invalid JSON Pointer '" + internal::tokens_to_string( b, std::next( e ) ) + "' -- array index out of bounds" );
                     }
                     v.m_union.a.insert( v.m_union.a.begin() + i, std::move( value ) );
                     return v.m_union.a.at( i );
                  }
                  break;
               case json::type::OBJECT:
                  {
                     const auto & k = e->key();
                     const auto it = v.m_union.o.find( k );
                     if ( it == v.m_union.o.end() ) {
                        const auto r = v.unsafe_emplace( k, std::move( value ) );
                        assert( r.second );
                        return r.first->second;
                     }
                     it->second = std::move( value );
                     return it->second;
                  }
                  break;
               default:
                  throw internal::invalid_type( b, std::next( e ) );
            }
         }

         template< typename T >
         tao::optional< T > optional( const std::string & key ) const
         {
            TAOCPP_JSON_CHECK_TYPE_ERROR( m_type, json::type::OBJECT );
            const auto it = m_union.o.find( key );
            if ( it == m_union.o.end() ) {
               return tao::nullopt;
            }
            else {
               return it->second.template as< T >();
            }
         }

         // The unsafe_assign()-functions MUST NOT be called on a
         // value v when json::needs_discard( v.type() ) is true!

         template< typename T >
         void unsafe_assign( T && v ) noexcept( noexcept( Traits< typename std::decay< T >::type >::assign( std::declval< basic_value & >(), std::forward< T >( v ) ) ) )
         {
            using D = typename std::decay< T >::type;
            Traits< D >::assign( *this, std::forward< T >( v ) );
         }

         void unsafe_assign( std::initializer_list< pair< Traits > > && l )
         {
            unsafe_emplace_object();
            for ( auto & e : l ) {
               const auto r = unsafe_emplace( std::move( e.key ), std::move( e.value ) );
               if ( ! r.second ) {
                  throw std::runtime_error( "duplicate JSON object key: " + r.first->first );
               }
            }
         }

         void unsafe_assign( const std::initializer_list< pair< Traits > > & l )
         {
            unsafe_emplace_object();
            for ( const auto & e : l ) {
               const auto r = unsafe_emplace( e.key, e.value );
               if ( ! r.second ) {
                  throw std::runtime_error( "duplicate JSON object key: " + r.first->first );
               }
            }
         }

         void unsafe_assign( std::initializer_list< pair< Traits > > & l )
         {
            unsafe_assign( static_cast< const std::initializer_list< pair< Traits > > & >( l ) );
         }

         void unsafe_assign_null() noexcept
         {
            m_type = json::type::NULL_;
         }

         void unsafe_assign_bool( const bool b ) noexcept
         {
            m_union.b = b;
            m_type = json::type::BOOLEAN;
         }

         void unsafe_assign_signed( const std::int64_t i ) noexcept
         {
            m_union.i = i;
            m_type = json::type::SIGNED;
         }

         void unsafe_assign_unsigned( const std::uint64_t u ) noexcept
         {
            m_union.u = u;
            m_type = json::type::UNSIGNED;
         }

         void unsafe_assign_double_unchecked( const double d ) noexcept
         {
            // d must be a finite value!
            m_union.d = d;
            m_type = json::type::DOUBLE;
         }

         void unsafe_assign_double( const double d )
         {
            if ( ! std::isfinite( d ) ) {
               throw std::runtime_error( "non-finite double value invalid for JSON" );
            }
            unsafe_assign_double_unchecked( d );
         }

         template< typename ... Ts >
         void unsafe_emplace_string( Ts && ... ts )
         {
            new ( & m_union.s ) std::string( std::forward< Ts >( ts ) ... );
            m_type = json::type::STRING;
         }

         template< typename ... Ts >
         void emplace_string( Ts && ... ts )
         {
            discard();
            unsafe_emplace_string( std::forward< Ts >( ts ) ... );
         }

         template< typename ... Ts >
         void unsafe_emplace_array( Ts && ... ts )
         {
            new ( & m_union.a ) std::vector< basic_value >( std::forward< Ts >( ts ) ... );
            m_type = json::type::ARRAY;
         }

         template< typename ... Ts >
         void emplace_array( Ts && ... ts )
         {
            discard();
            unsafe_emplace_array( std::forward< Ts >( ts ) ... );
         }

         void prepare_array()
         {
            switch ( m_type ) {
               case json::type::UNINITIALIZED:
                  unsafe_emplace_array();
               case json::type::ARRAY:
                  break;
               default:
                  TAOCPP_JSON_THROW_TYPE_ERROR( m_type );
            }
         }

         template< typename V >
         void unsafe_emplace_back( V && v )
         {
            m_union.a.emplace_back( std::forward< V >( v ) );
         }

         template< typename V >
         void emplace_back( V && v )
         {
            prepare_array();
            unsafe_emplace_back( std::forward< V >( v ) );
         }

         template< typename ... Ts >
         void unsafe_emplace_object( Ts && ... ts )
         {
            new ( & m_union.o ) std::map< std::string, basic_value >( std::forward< Ts >( ts ) ... );
            m_type = json::type::OBJECT;
         }

         template< typename ... Ts >
         void emplace_object( Ts && ... ts )
         {
            discard();
            unsafe_emplace_object( std::forward< Ts >( ts ) ... );
         }

         void prepare_object()
         {
            switch ( m_type ) {
               case json::type::UNINITIALIZED:
                  unsafe_emplace_object();
               case json::type::OBJECT:
                  break;
               default:
                  TAOCPP_JSON_THROW_TYPE_ERROR( m_type );
            }
         }

         template< typename K, typename V >
         std::pair< typename object_t::iterator, bool > unsafe_emplace( K && k, V && v )
         {
            return m_union.o.emplace( std::forward< K >( k ), std::forward< V >( v ) );
         }

         template< typename K, typename V >
         std::pair< typename object_t::iterator, bool > emplace( K && k, V && v )
         {
            prepare_object();
            return unsafe_emplace( std::forward< K >( k ), std::forward< V >( v ) );
         }

         void unsafe_assign_pointer( const basic_value * p ) noexcept
         {
            m_union.p = p;
            m_type = json::type::RAW_PTR;
         }

         void append( std::initializer_list< single< Traits > > && l )
         {
            prepare_array();
            auto & v = unsafe_get_array();
            v.reserve( v.size() + l.size() );
            for ( auto & e : l ) {
               unsafe_emplace_back( std::move( e.value ) );
            }
         }

         void append( const std::initializer_list< single< Traits > > & l )
         {
            prepare_array();
            auto & v = unsafe_get_array();
            v.reserve( v.size() + l.size() );
            for ( const auto & e : l ) {
               unsafe_emplace_back( e.value );
            }
         }

         void insert( std::initializer_list< pair< Traits > > && l )
         {
            prepare_object();
            for ( auto & e : l ) {
               const auto r = unsafe_emplace( std::move( e.key ), std::move( e.value ) );
               if ( ! r.second ) {
                  throw std::runtime_error( "duplicate JSON object key: " + r.first->first );
               }
            }
         }

         void insert( const std::initializer_list< pair< Traits > > & l )
         {
            prepare_object();
            for ( const auto & e : l ) {
               const auto r = unsafe_emplace( e.key, e.value );
               if ( ! r.second ) {
                  throw std::runtime_error( "duplicate JSON object key: " + r.first->first );
               }
            }
         }


         basic_value & operator[] ( const std::size_t index ) noexcept
         {
            assert( m_type == json::type::ARRAY );
            return m_union.a[ index ];
         }

         const basic_value & operator[] ( const std::size_t index ) const noexcept
         {
            assert( m_type == json::type::ARRAY );
            return m_union.a[ index ];
         }

         basic_value & operator[] ( const std::string & key )
         {
            prepare_object();
            return m_union.o[ key ];
         }

         basic_value & operator[] ( std::string && key )
         {
            prepare_object();
            return m_union.o[ std::move( key ) ];
         }

         basic_value & operator[] ( const pointer & k )
         {
            if ( ! k ) {
               return * this;
            }
            const auto b = k.begin();
            const auto e = std::prev( k.end() );
            basic_value & v = internal::pointer_at( this, b, e );
            switch ( v.m_type ) {
               case json::type::ARRAY:
                  {
                     if ( e->key() == "-" ) {
                        v.unsafe_emplace_back( null );
                        return v.m_union.a.back();
                     }
                     return v.at( e->index() );
                  }
                  break;
               case json::type::OBJECT:
                  {
                     const auto & k = e->key();
                     const auto it = v.m_union.o.find( k );
                     if ( it == v.m_union.o.end() ) {
                        const auto r = v.unsafe_emplace( k, null );
                        assert( r.second );
                        return r.first->second;
                     }
                     return it->second;
                  }
                  break;
               default:
                  throw internal::invalid_type( b, std::next( e ) );
            }
         }

         bool empty() const noexcept
         {
            switch ( m_type ) {
               case json::type::UNINITIALIZED:
                  return true;
               case json::type::DISCARDED:
                  // LCOV_EXCL_START
                  assert( m_type != json::type::DISCARDED );
                  return true;
                  // LCOV_EXCL_STOP
               case json::type::NULL_:
               case json::type::BOOLEAN:
               case json::type::SIGNED:
               case json::type::UNSIGNED:
               case json::type::DOUBLE:
                  return false;
               case json::type::STRING:
                  return m_union.s.empty();
               case json::type::ARRAY:
                  return m_union.a.empty();
               case json::type::OBJECT:
                  return m_union.o.empty();
               case json::type::RAW_PTR:
                  return ! m_union.p;
            }
            // LCOV_EXCL_START
            assert( false );
            return false;
            // LCOV_EXCL_STOP
         }

         void unsafe_discard() noexcept
         {
            switch ( m_type ) {
               case json::type::UNINITIALIZED:
               case json::type::DISCARDED:
               case json::type::NULL_:
               case json::type::BOOLEAN:
               case json::type::SIGNED:
               case json::type::UNSIGNED:
               case json::type::DOUBLE:
               case json::type::RAW_PTR:
                  return;
               case json::type::STRING:
                  m_union.s.~basic_string();
                  return;
               case json::type::ARRAY:
                  m_union.a.~vector();
                  return;
               case json::type::OBJECT:
                  m_union.o.~map();
                  return;
            }
            assert( false );  // LCOV_EXCL_LINE
         }

         void discard() noexcept
         {
            unsafe_discard();
            m_type = json::type::DISCARDED;
         }

         void reset() noexcept
         {
            unsafe_discard();
            m_type = json::type::UNINITIALIZED;
         }

      private:
         void seize( basic_value && r ) noexcept
         {
            switch ( r.m_type ) {
               case json::type::UNINITIALIZED:
                  assert( ( r.m_type = json::type::DISCARDED, true ) );
                  return;
               case json::type::DISCARDED:
                  // LCOV_EXCL_START
                  assert( r.m_type != json::type::DISCARDED );
                  return;
                  // LCOV_EXCL_STOP
               case json::type::NULL_:
                  assert( ( r.m_type = json::type::DISCARDED, true ) );
                  return;
               case json::type::BOOLEAN:
                  m_union.b = r.m_union.b;
                  assert( ( r.m_type = json::type::DISCARDED, true ) );
                  return;
               case json::type::SIGNED:
                  m_union.i = r.m_union.i;
                  assert( ( r.m_type = json::type::DISCARDED, true ) );
                  return;
               case json::type::UNSIGNED:
                  m_union.u = r.m_union.u;
                  assert( ( r.m_type = json::type::DISCARDED, true ) );
                  return;
               case json::type::DOUBLE:
                  m_union.d = r.m_union.d;
                  assert( ( r.m_type = json::type::DISCARDED, true ) );
                  return;
               case json::type::STRING:
                  new ( & m_union.s ) std::string( std::move( r.m_union.s ) );
                  assert( ( r.discard(), true ) );
                  return;
               case json::type::ARRAY:
                  new ( & m_union.a ) std::vector< basic_value >( std::move( r.m_union.a ) );
                  assert( ( r.discard(), true ) );
                  return;
               case json::type::OBJECT:
                  new ( & m_union.o ) std::map< std::string, basic_value >( std::move( r.m_union.o ) );
                  assert( ( r.discard(), true ) );
                  return;
               case json::type::RAW_PTR:
                  m_union.p = r.m_union.p;
                  assert( ( r.m_type = json::type::DISCARDED, true ) );
                  return;
            }
            assert( false );  // LCOV_EXCL_LINE
         }

         void embed( const basic_value & r )
         {
            switch ( r.m_type ) {
               case json::type::UNINITIALIZED:
                  return;
               case json::type::DISCARDED:
                  throw std::logic_error( "attempt to use a discarded value" );
               case json::type::NULL_:
                  return;
               case json::type::BOOLEAN:
                  m_union.b = r.m_union.b;
                  return;
               case json::type::SIGNED:
                  m_union.i = r.m_union.i;
                  return;
               case json::type::UNSIGNED:
                  m_union.u = r.m_union.u;
                  return;
               case json::type::DOUBLE:
                  m_union.d = r.m_union.d;
                  return;
               case json::type::STRING:
                  new ( & m_union.s ) std::string( r.m_union.s );
                  return;
               case json::type::ARRAY:
                  new ( & m_union.a ) std::vector< basic_value >( r.m_union.a );
                  return;
               case json::type::OBJECT:
                  new ( & m_union.o ) std::map< std::string, basic_value >( r.m_union.o );
                  return;
               case json::type::RAW_PTR:
                  m_union.p = r.m_union.p;
                  return;
            }
            assert( false );  // LCOV_EXCL_LINE
         }

         json::type m_type = json::type::UNINITIALIZED;
         internal::value_union< basic_value > m_union;
      };

      template< template< typename ... > class Traits >
      bool operator== ( const basic_value< Traits > & lhs, const basic_value< Traits > & rhs ) noexcept
      {
         if ( lhs.type() == type::RAW_PTR ) {
            if ( const auto * p = lhs.unsafe_get_raw_ptr() ) {
               return * p == rhs;
            }
            else {
               return null == rhs;
            }
         }
         if ( lhs.type() != rhs.type() ) {
            if ( rhs.type() == type::RAW_PTR ) {
               if ( const auto * p = rhs.unsafe_get_raw_ptr() ) {
                  return lhs == * p;
               }
               else {
                  return lhs == null;
               }
            }
            if ( lhs.type() == type::SIGNED ) {
               if ( rhs.type() == type::UNSIGNED ) {
                  const auto v = lhs.unsafe_get_signed();
                  return ( v >= 0 ) && ( static_cast< std::uint64_t >( v ) == rhs.unsafe_get_unsigned() );
               }
               if ( rhs.type() == type::DOUBLE ) {
                  return lhs.unsafe_get_signed() == rhs.unsafe_get_double();
               }
            }
            else if ( lhs.type() == type::UNSIGNED ) {
               if ( rhs.type() == type::SIGNED ) {
                  const auto v = rhs.unsafe_get_signed();
                  return ( v >= 0 ) && ( lhs.unsafe_get_unsigned() == static_cast< std::uint64_t >( v ) );
               }
               if ( rhs.type() == type::DOUBLE ) {
                  return lhs.unsafe_get_unsigned() == rhs.unsafe_get_double();
               }
            }
            else if ( lhs.type() == type::DOUBLE ) {
               if ( rhs.type() == type::SIGNED ) {
                  return lhs.unsafe_get_double() == rhs.unsafe_get_signed();
               }
               if ( rhs.type() == type::UNSIGNED ) {
                  return lhs.unsafe_get_double() == rhs.unsafe_get_unsigned();
               }
            }
            assert( lhs.type() != type::DISCARDED );
            assert( rhs.type() != type::DISCARDED );
            return false;
         }
         switch ( lhs.type() ) {
            case type::UNINITIALIZED:
               return true;
            case type::DISCARDED:
               // LCOV_EXCL_START
               assert( lhs.type() != type::DISCARDED );
               break;
               // LCOV_EXCL_STOP
            case type::NULL_:
               return true;
            case type::BOOLEAN:
               return lhs.unsafe_get_boolean() == rhs.unsafe_get_boolean();
            case type::SIGNED:
               return lhs.unsafe_get_signed() == rhs.unsafe_get_signed();
            case type::UNSIGNED:
               return lhs.unsafe_get_unsigned() == rhs.unsafe_get_unsigned();
            case type::DOUBLE:
               return lhs.unsafe_get_double() == rhs.unsafe_get_double();
            case type::STRING:
               return lhs.unsafe_get_string() == rhs.unsafe_get_string();
            case type::ARRAY:
               return lhs.unsafe_get_array() == rhs.unsafe_get_array();
            case type::OBJECT:
               return lhs.unsafe_get_object() == rhs.unsafe_get_object();
            case type::RAW_PTR:
               break;  // LCOV_EXCL_LINE
         }
         // LCOV_EXCL_START
         assert( false );
         return false;
         // LCOV_EXCL_STOP
      }

      template< template< typename ... > class Traits >
      bool operator< ( const basic_value< Traits > & lhs, const basic_value< Traits > & rhs ) noexcept
      {
         if ( lhs.type() == type::RAW_PTR ) {
            if ( const auto * p = lhs.unsafe_get_raw_ptr() ) {
               return * p < rhs;
            }
            else {
               return null < rhs;
            }
         }
         if ( lhs.type() != rhs.type() ) {
            if ( rhs.type() == type::RAW_PTR ) {
               if ( const auto * p = rhs.unsafe_get_raw_ptr() ) {
                  return lhs < * p;
               }
               else {
                  return lhs < null;
               }
            }
            if ( lhs.type() == type::SIGNED ) {
               if ( rhs.type() == type::UNSIGNED ) {
                  const auto v = lhs.unsafe_get_signed();
                  return ( v < 0 ) || ( static_cast< std::uint64_t >( v ) < rhs.unsafe_get_unsigned() );
               }
               if ( rhs.type() == type::DOUBLE ) {
                  return lhs.unsafe_get_signed() < rhs.unsafe_get_double();
               }
            }
            else if ( lhs.type() == type::UNSIGNED ) {
               if ( rhs.type() == type::SIGNED ) {
                  const auto v = rhs.unsafe_get_signed();
                  return ( v >= 0 ) && ( lhs.unsafe_get_unsigned() < static_cast< std::uint64_t >( v ) );
               }
               if ( rhs.type() == type::DOUBLE ) {
                  return lhs.unsafe_get_unsigned() < rhs.unsafe_get_double();
               }
            }
            else if ( lhs.type() == type::DOUBLE ) {
               if ( rhs.type() == type::SIGNED ) {
                  return lhs.unsafe_get_double() < rhs.unsafe_get_signed();
               }
               if ( rhs.type() == type::UNSIGNED ) {
                  return lhs.unsafe_get_double() < rhs.unsafe_get_unsigned();
               }
            }
            assert( lhs.type() != type::DISCARDED );
            assert( rhs.type() != type::DISCARDED );
            return lhs.type() < rhs.type();
         }
         switch ( lhs.type() ) {
            case type::UNINITIALIZED:
               return false;
            case type::DISCARDED:
               // LCOV_EXCL_START
               assert( lhs.type() != type::DISCARDED );
               break;
               // LCOV_EXCL_STOP
            case type::NULL_:
               return false;
            case type::BOOLEAN:
               return lhs.unsafe_get_boolean() < rhs.unsafe_get_boolean();
            case type::SIGNED:
               return lhs.unsafe_get_signed() < rhs.unsafe_get_signed();
            case type::UNSIGNED:
               return lhs.unsafe_get_unsigned() < rhs.unsafe_get_unsigned();
            case type::DOUBLE:
               return lhs.unsafe_get_double() < rhs.unsafe_get_double();
            case type::STRING:
               return lhs.unsafe_get_string() < rhs.unsafe_get_string();
            case type::ARRAY:
               return lhs.unsafe_get_array() < rhs.unsafe_get_array();
            case type::OBJECT:
               return lhs.unsafe_get_object() < rhs.unsafe_get_object();
            case type::RAW_PTR:
               break;  // LCOV_EXCL_LINE
         }
         // LCOV_EXCL_START
         assert( false );
         return false;
         // LCOV_EXCL_STOP
      }

      template< template< typename ... > class Traits >
      basic_value< Traits > & operator+= ( basic_value< Traits > & v, std::initializer_list< pair< Traits > > && l )
      {
         v.insert( std::move( l ) );
         return v;
      }

      template< template< typename ... > class Traits >
      basic_value< Traits > & operator+= ( basic_value< Traits > & v, const std::initializer_list< pair< Traits > > & l )
      {
         v.insert( l );
         return v;
      }

      template< template< typename ... > class Traits >
      basic_value< Traits > & operator-= ( basic_value< Traits > & v, std::initializer_list< std::string > l )
      {
         auto & o = v.get_object();
         for ( const auto & k : l ) {
            if ( o.erase( k ) == 0 ) {
               throw std::runtime_error( "JSON object key not found: " + k );
            }
         }
         return v;
      }

      using value = basic_value< traits >;

   } // json

} // tao

#endif
