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

#ifndef TAO_JSON_CBOR_EVENTS_TO_STREAM_HPP
#define TAO_JSON_CBOR_EVENTS_TO_STREAM_HPP

#include <cstdint>
#include <ostream>
#include <string>
#include <string_view>

#include "../internal/major.hpp"

#include "../../binary_view.hpp"

#include "../../internal/endian.hpp"

#if defined( _MSC_VER )
#pragma warning( push )
#pragma warning( disable : 4310 )
#endif

namespace tao::json::cbor::events
{
   class to_stream
   {
   private:
      std::ostream& os;

   public:
      explicit to_stream( std::ostream& in_os ) noexcept
         : os( in_os )
      {}

      void null()
      {
         os.put( char( std::uint8_t( internal::major::OTHER ) + 22 ) );
      }

      void boolean( const bool v )
      {
         os.put( char( std::uint8_t( internal::major::OTHER ) + 20 + std::uint8_t( v ) ) );
      }

      void number( const internal::major m, const std::uint64_t v )
      {
         if( v < 24 ) {
            os.put( char( std::uint8_t( m ) + v ) );
         }
         else if( v < 256 ) {
            os.put( char( std::uint8_t( m ) + 24 ) );
            os.put( char( v ) );
         }
         else if( v < 65536 ) {
            os.put( char( std::uint8_t( m ) + 25 ) );
            const std::uint16_t x = json::internal::h_to_be( std::uint16_t( v ) );
            os.write( reinterpret_cast< const char* >( &x ), sizeof( x ) );
         }
         else if( v < 4294967296ULL ) {
            os.put( char( std::uint8_t( m ) + 26 ) );
            const std::uint32_t x = json::internal::h_to_be( std::uint32_t( v ) );
            os.write( reinterpret_cast< const char* >( &x ), sizeof( x ) );
         }
         else {
            os.put( char( std::uint8_t( m ) + 27 ) );
            const std::uint64_t x = json::internal::h_to_be( v );
            os.write( reinterpret_cast< const char* >( &x ), sizeof( x ) );
         }
      }

      void number( const std::int64_t v )
      {
         if( v >= 0 ) {
            number( internal::major::UNSIGNED, v );
         }
         else {
            number( internal::major::NEGATIVE, -( v + 1 ) );
         }
      }

      void number( const std::uint64_t v )
      {
         number( internal::major::UNSIGNED, v );
      }

      void number( const double v )
      {
         std::uint64_t n;
         std::memcpy( &n, &v, sizeof( n ) );
         n = json::internal::h_to_be( n );
         os.put( char( std::uint8_t( internal::major::OTHER ) + 27 ) );
         os.write( reinterpret_cast< const char* >( &n ), sizeof( n ) );
      }

      void string( const std::string_view v )
      {
         number( internal::major::STRING, v.size() );
         os.write( v.data(), v.size() );
      }

      void binary( const tao::binary_view v )
      {
         number( internal::major::BINARY, v.size() );
         os.write( reinterpret_cast< const char* >( v.data() ), v.size() );
      }

      void begin_array()
      {
         os.put( char( std::uint8_t( internal::major::ARRAY ) + internal::minor_mask ) );
      }

      void begin_array( const std::size_t size )
      {
         number( internal::major::ARRAY, size );
      }

      void element() noexcept
      {}

      void end_array()
      {
         os.put( char( 0xff ) );
      }

      void end_array( const std::size_t /*unused*/ ) noexcept
      {}

      void begin_object()
      {
         os.put( char( std::uint8_t( internal::major::OBJECT ) + internal::minor_mask ) );
      }

      void begin_object( const std::size_t size )
      {
         number( internal::major::OBJECT, size );
      }

      void key( const std::string_view v )
      {
         string( v );
      }

      void member() noexcept
      {}

      void end_object()
      {
         os.put( char( 0xff ) );
      }

      void end_object( const std::size_t /*unused*/ ) noexcept
      {}
   };

}  // namespace tao::json::cbor::events

#if defined( _MSC_VER )
#pragma warning( pop )
#endif

#endif
