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

#ifndef TAO_JSON_EVENTS_CAPTURE_HPP
#define TAO_JSON_EVENTS_CAPTURE_HPP

#include <initializer_list>
#include <string_view>
#include <type_traits>
#include <utility>

#include "produce.hpp"

#include "../forward.hpp"

namespace tao::json::events
{
   class virtual_base;

   namespace capture
   {
      template< template< typename... > class Traits = traits, typename ConsumerBase = virtual_base >
      class value;

      template< template< typename... > class Traits = traits, typename ConsumerBase = virtual_base >
      using array = std::initializer_list< value< Traits, ConsumerBase > >;

      template< template< typename... > class Traits, typename ConsumerBase >
      struct member;

      template< template< typename... > class Traits = traits, typename ConsumerBase = virtual_base >
      using object = std::initializer_list< member< Traits, ConsumerBase > >;

   }  // namespace capture

   template< template< typename... > class Traits, typename Consumer, typename ConsumerBase >
   void produce( Consumer& c, const capture::value< Traits, ConsumerBase >& v )
   {
      v.produce( c );
   }

   template< template< typename... > class Traits, typename Consumer, typename ConsumerBase >
   void produce_elements( Consumer& c, const capture::array< Traits, ConsumerBase >& a )
   {
      for( const auto& e : a ) {
         e.produce( c );
         c.element();
      }
   }

   template< template< typename... > class Traits, typename Consumer, typename ConsumerBase >
   void produce( Consumer& c, const capture::array< Traits, ConsumerBase >& a )
   {
      c.begin_array( a.size() );
      events::produce_elements( c, a );
      c.end_array( a.size() );
   }

   template< template< typename... > class Traits, typename Consumer, typename ConsumerBase >
   void produce_members( Consumer& c, const capture::object< Traits, ConsumerBase >& o )
   {
      for( const auto& m : o ) {
         c.key( m.key );
         m.produce( c );
         c.member();
      }
   }

   template< template< typename... > class Traits, typename Consumer, typename ConsumerBase >
   void produce( Consumer& c, const capture::object< Traits, ConsumerBase >& o )
   {
      c.begin_object( o.size() );
      events::produce_members( c, o );
      c.end_object( o.size() );
   }

   namespace capture
   {
      union union_t
      {
         const void* p;
         bool b;
         char c;
         signed char sc;
         unsigned char uc;
         signed short ss;
         unsigned short us;
         signed int si;
         unsigned int ui;
         signed long sl;
         unsigned long ul;
         signed long long sll;
         unsigned long long ull;
         float f;
         double d;

         // clang-format off
         explicit union_t( const void* v ) noexcept : p( v ) {}
         explicit union_t( const bool v ) noexcept : b( v ) {}
         explicit union_t( const char v ) noexcept : c( v ) {}
         explicit union_t( const signed char v ) noexcept : sc( v ) {}
         explicit union_t( const unsigned char v ) noexcept : uc( v ) {}
         explicit union_t( const signed short v ) noexcept : ss( v ) {}
         explicit union_t( const unsigned short v ) noexcept : us( v ) {}
         explicit union_t( const signed int v ) noexcept : si( v ) {}
         explicit union_t( const unsigned int v ) noexcept : ui( v ) {}
         explicit union_t( const signed long v ) noexcept : sl( v ) {}
         explicit union_t( const unsigned long v ) noexcept : ul( v ) {}
         explicit union_t( const signed long long v ) noexcept : sll( v ) {}
         explicit union_t( const unsigned long long v ) noexcept : ull( v ) {}
         explicit union_t( const float v ) noexcept : f( v ) {}
         explicit union_t( const double v ) noexcept : d( v ) {}

         explicit operator bool() const noexcept { return b; }
         explicit operator char() const noexcept { return c; }
         explicit operator signed char() const noexcept { return sc; }
         explicit operator unsigned char() const noexcept { return uc; }
         explicit operator signed short() const noexcept { return ss; }
         explicit operator unsigned short() const noexcept { return us; }
         explicit operator signed int() const noexcept { return si; }
         explicit operator unsigned int() const noexcept { return ui; }
         explicit operator signed long() const noexcept { return sl; }
         explicit operator unsigned long() const noexcept { return ul; }
         explicit operator signed long long() const noexcept { return sll; }
         explicit operator unsigned long long() const noexcept { return ull; }
         explicit operator float() const noexcept { return f; }
         explicit operator double() const noexcept { return d; }
         // clang-format on
      };

      template< typename, typename = void >
      struct capture_traits
         : std::false_type
      {};

      template< typename T >
      struct capture_traits< T, std::enable_if_t< std::is_pointer_v< std::decay_t< T > > > >
         : std::true_type
      {
         [[nodiscard]] static const void* to_underlying( const T v ) noexcept
         {
            return v;
         }

         [[nodiscard]] static auto from_underlying( const union_t& v ) noexcept
         {
            return std::decay_t< T >( v.p );
         }
      };

      template< typename T >
      struct capture_traits< T, std::enable_if_t< std::is_arithmetic_v< T > > >
         : std::true_type
      {
         [[nodiscard]] static T to_underlying( const T v ) noexcept
         {
            return v;
         }

         [[nodiscard]] static T from_underlying( const union_t& v ) noexcept
         {
            return static_cast< T >( v );
         }
      };

      template< typename T >
      struct capture_traits< T, std::enable_if_t< std::is_enum_v< T > > >
         : std::true_type
      {
         using underlying_t = std::underlying_type_t< T >;

         [[nodiscard]] static underlying_t to_underlying( const T v ) noexcept
         {
            return static_cast< underlying_t >( v );
         }

         [[nodiscard]] static T from_underlying( const union_t& v ) noexcept
         {
            return static_cast< T >( static_cast< underlying_t >( v ) );
         }
      };

      namespace internal
      {
         template< template< typename... > class Traits, typename Consumer, typename T >
         void produce_from_underlying( Consumer& c, const union_t& u )
         {
            events::produce< Traits >( c, capture_traits< T >::from_underlying( u ) );
         }

         template< template< typename... > class Traits, typename Consumer, typename T >
         void produce_from_pointer( Consumer& c, const union_t& u )
         {
            events::produce< Traits >( c, *static_cast< const T* >( u.p ) );
         }

      }  // namespace internal

      template< template< typename... > class Traits, typename ConsumerBase >
      class value
      {
      private:
         const union_t m_value;

         using producer_t = void( ConsumerBase&, const union_t& );
         producer_t* const m_producer;

      public:
         template< typename T, typename = std::enable_if_t< capture_traits< T >::value > >
         value( const T v ) noexcept
            : m_value( capture_traits< T >::to_underlying( v ) ),
              m_producer( &internal::produce_from_underlying< Traits, ConsumerBase, T > )
         {
            static_assert( noexcept( capture_traits< T >::to_underlying( v ) ) );
         }

         template< typename T, typename = std::enable_if_t< !capture_traits< T >::value > >
         value( const T& v ) noexcept
            : m_value( &v ),
              m_producer( &internal::produce_from_pointer< Traits, ConsumerBase, T > )
         {}

         value( const object< Traits, ConsumerBase >& il ) noexcept
            : m_value( &il ),
              m_producer( &internal::produce_from_pointer< Traits, ConsumerBase, object< Traits, ConsumerBase > > )
         {}

         void produce( ConsumerBase& c ) const
         {
            m_producer( c, m_value );
         }
      };

      template< template< typename... > class Traits, typename ConsumerBase >
      struct member
         : value< Traits, ConsumerBase >
      {
         const std::string_view key;

         template< typename T >
         member( const std::string_view k, T&& v ) noexcept
            : value< Traits, ConsumerBase >( std::forward< T >( v ) ),
              key( k )
         {}

         template< typename T >
         member( T&& v ) noexcept
            : member( Traits< std::decay_t< T > >::template default_key< Traits >::as_string_view(), std::forward< T >( v ) )
         {}

         member( const std::string_view k, const object< Traits, ConsumerBase >& il ) noexcept
            : value< Traits, ConsumerBase >( il ),
              key( k )
         {}

         member( const member& ) = delete;
         member( member&& ) = delete;
      };

      template< template< typename... > class Traits, typename Consumer >
      class assign_t
      {
      private:
         Consumer& m_consumer;

      public:
         explicit assign_t( Consumer& consumer ) noexcept
            : m_consumer( consumer )
         {}

         assign_t& operator=( const value< Traits, Consumer >& v )
         {
            produce< Traits >( m_consumer, v );
            return *this;
         }

         template< typename ConsumerBase >
         assign_t& operator=( const value< Traits, ConsumerBase >& v )
         {
            produce< Traits >( m_consumer, v );
            return *this;
         }

         template< typename ConsumerBase >
         assign_t& operator=( const array< Traits, ConsumerBase >& v )
         {
            produce< Traits >( m_consumer, v );
            return *this;
         }

         template< typename ConsumerBase >
         assign_t& operator=( const object< Traits, ConsumerBase >& v )
         {
            produce< Traits >( m_consumer, v );
            return *this;
         }

         [[nodiscard]] Consumer& consumer() noexcept
         {
            return m_consumer;
         }

         [[nodiscard]] const Consumer& consumer() const noexcept
         {
            return m_consumer;
         }
      };

      template< template< typename... > class Traits, typename Consumer >
      class array_t
      {
      private:
         Consumer& m_consumer;

      public:
         explicit array_t( Consumer& consumer ) noexcept( noexcept( m_consumer.begin_array() ) )
            : m_consumer( consumer )
         {
            m_consumer.begin_array();
         }

         array_t( const array_t& ) = delete;
         array_t( array_t&& ) = delete;

         ~array_t()
         {
            m_consumer.end_array();
         }

         array_t& operator=( const array_t& ) = delete;
         array_t& operator=( array_t&& ) = delete;

         array_t& operator+=( const array< Traits, Consumer >& v )
         {
            produce_elements< Traits >( m_consumer, v );
            return *this;
         }

         template< typename ConsumerBase >
         array_t& operator+=( const array< Traits, ConsumerBase >& v )
         {
            produce_elements< Traits >( m_consumer, v );
            return *this;
         }

         template< typename Container >
         void append( const Container& c )
         {
            for( const auto& e : c ) {
               this->push_back( e );
            }
         }

         template< typename V >
         void push_back( V&& v )
         {
            events::produce< Traits >( m_consumer, std::forward< V >( v ) );
            m_consumer.element();
         }

         [[nodiscard]] Consumer& consumer() noexcept
         {
            return m_consumer;
         }

         [[nodiscard]] const Consumer& consumer() const noexcept
         {
            return m_consumer;
         }
      };

      template< template< typename... > class Traits, typename Consumer >
      class object_t
      {
      private:
         Consumer& m_consumer;

      public:
         explicit object_t( Consumer& consumer ) noexcept( noexcept( m_consumer.begin_object() ) )
            : m_consumer( consumer )
         {
            m_consumer.begin_object();
         }

         object_t( const object_t& ) = delete;
         object_t( object_t&& ) = delete;

         ~object_t()
         {
            m_consumer.end_object();
         }

         object_t& operator=( const object_t& ) = delete;
         object_t& operator=( object_t&& ) = delete;

         object_t& operator+=( const object< Traits, Consumer >& v )
         {
            produce_members< Traits >( m_consumer, v );
            return *this;
         }

         template< typename ConsumerBase >
         object_t& operator+=( const object< Traits, ConsumerBase >& v )
         {
            produce_members< Traits >( m_consumer, v );
            return *this;
         }

         template< typename Container >
         void append( const Container& c )
         {
            for( const auto& [ k, v ] : c ) {
               this->insert( k, v );
            }
         }

         template< typename K, typename V >
         void insert( K&& k, V&& v )
         {
            m_consumer.key( std::forward< K >( k ) );
            events::produce< Traits >( m_consumer, std::forward< V >( v ) );
            m_consumer.member();
         }

         template< typename V >
         void insert( V&& v )
         {
            this->insert( Traits< std::decay_t< V > >::template default_key< Traits >::as_string_view(), std::forward< V >( v ) );
         }

         [[nodiscard]] Consumer& consumer() noexcept
         {
            return m_consumer;
         }

         [[nodiscard]] const Consumer& consumer() const noexcept
         {
            return m_consumer;
         }
      };

   }  // namespace capture

   template< template< typename... > class Traits = traits, typename Consumer >
   [[nodiscard]] capture::assign_t< Traits, Consumer > assign( Consumer& c ) noexcept( noexcept( capture::assign_t< Traits, Consumer >( c ) ) )
   {
      return capture::assign_t< Traits, Consumer >( c );
   }

   template< template< typename... > class Traits = traits, typename Consumer >
   [[nodiscard]] capture::assign_t< Traits, Consumer > assign( Consumer&& c ) noexcept( noexcept( assign< Traits >( c ) ) )
   {
      return assign< Traits >( c );
   }

   template< template< typename... > class Traits = traits, typename Consumer >
   [[nodiscard]] capture::array_t< Traits, Consumer > array( Consumer& c ) noexcept( noexcept( capture::array_t< Traits, Consumer >( c ) ) )
   {
      return capture::array_t< Traits, Consumer >( c );
   }

   template< template< typename... > class Traits = traits, typename Consumer >
   [[nodiscard]] capture::array_t< Traits, Consumer > array( Consumer&& c ) noexcept( noexcept( array< Traits >( c ) ) )
   {
      return array< Traits >( c );
   }

   template< template< typename... > class Traits = traits, typename Consumer >
   [[nodiscard]] capture::object_t< Traits, Consumer > object( Consumer& c ) noexcept( noexcept( capture::object_t< Traits, Consumer >( c ) ) )
   {
      return capture::object_t< Traits, Consumer >( c );
   }

   template< template< typename... > class Traits = traits, typename Consumer >
   [[nodiscard]] capture::object_t< Traits, Consumer > object( Consumer&& c ) noexcept( noexcept( object< Traits >( c ) ) )
   {
      return object< Traits >( c );
   }

}  // namespace tao::json::events

#endif
