/*
 * Copyright (c) 2015 Cryptonomex, Inc., and contributors.
 *
 * The MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#pragma once

#include <fc/io/varint.hpp>
#include <fc/io/raw_fwd.hpp>
#include <fc/reflect/reflect.hpp>

namespace graphene { namespace chain {

template< typename T >
struct extension
{
   extension() {}

   T value;
};

template< typename T >
struct graphene_extension_pack_count_visitor
{
   graphene_extension_pack_count_visitor( const T& v ) : value(v) {}

   template<typename Member, class Class, Member (Class::*member)>
   void operator()( const char* name )const
   {
      count += ((value.*member).valid()) ? 1 : 0;
   }

   const T& value;
   mutable uint32_t count = 0;
};

template< typename Stream, typename T >
struct graphene_extension_pack_read_visitor
{
   graphene_extension_pack_read_visitor( Stream& s, const T& v ) : stream(s), value(v) {}

   template<typename Member, class Class, Member (Class::*member)>
   void operator()( const char* name )const
   {
      if( (value.*member).valid() )
      {
         fc::raw::pack( stream, unsigned_int( which ) );
         fc::raw::pack( stream, *(value.*member) );
      }
      ++which;
   }

   Stream& stream;
   const T& value;
   mutable uint32_t which = 0;
};

template< typename Stream, class T >
void operator<<( Stream& stream, const graphene::chain::extension<T>& value )
{
   graphene_extension_pack_count_visitor<T> count_vtor( value.value );
   fc::reflector<T>::visit( count_vtor );
   fc::raw::pack( stream, unsigned_int( count_vtor.count ) );
   graphene_extension_pack_read_visitor<Stream,T> read_vtor( stream, value.value );
   fc::reflector<T>::visit( read_vtor );
}



template< typename Stream, typename T >
struct graphene_extension_unpack_visitor
{
   graphene_extension_unpack_visitor( Stream& s, T& v ) : stream(s), value(v)
   {
      unsigned_int c;
      fc::raw::unpack( stream, c );
      count_left = c.value;
      maybe_read_next_which();
   }

   void maybe_read_next_which()const
   {
      if( count_left > 0 )
      {
         unsigned_int w;
         fc::raw::unpack( stream, w );
         next_which = w.value;
      }
   }

   template< typename Member, class Class, Member (Class::*member)>
   void operator()( const char* name )const
   {
      if( (count_left > 0) && (which == next_which) )
      {
         typename Member::value_type temp;
         fc::raw::unpack( stream, temp );
         (value.*member) = temp;
         --count_left;
         maybe_read_next_which();
      }
      else
         (value.*member).reset();
      ++which;
   }

   mutable uint32_t      which = 0;
   mutable uint32_t next_which = 0;
   mutable uint32_t count_left = 0;

   Stream& stream;
   T& value;
};

template< typename Stream, typename T >
void operator>>( Stream& s, graphene::chain::extension<T>& value )
{
   value = graphene::chain::extension<T>();
   graphene_extension_unpack_visitor<Stream, T> vtor( s, value.value );
   fc::reflector<T>::visit( vtor );
   FC_ASSERT( vtor.count_left == 0 ); // unrecognized extension throws here
}

} } // graphene::chain

namespace fc {

template< typename T >
struct graphene_extension_from_variant_visitor
{
   graphene_extension_from_variant_visitor( const variant_object& v, T& val, uint32_t max_depth )
      : vo( v ), value( val ), _max_depth(max_depth - 1)
   {
      FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" );
      count_left = vo.size();
   }

   template<typename Member, class Class, Member (Class::*member)>
   void operator()( const char* name )const
   {
      auto it = vo.find(name);
      if( it != vo.end() )
      {
         from_variant( it->value(), (value.*member), _max_depth );
         assert( count_left > 0 );    // x.find(k) returns true for n distinct values of k only if x.size() >= n
         --count_left;
      }
   }

   const variant_object& vo;
   T& value;
   const uint32_t _max_depth;
   mutable uint32_t count_left = 0;
};

template< typename T >
void from_variant( const fc::variant& var, graphene::chain::extension<T>& value, uint32_t max_depth )
{
   value = graphene::chain::extension<T>();
   if( var.is_null() )
      return;
   if( var.is_array() )
   {
      FC_ASSERT( var.size() == 0 );
      return;
   }

   graphene_extension_from_variant_visitor<T> vtor( var.get_object(), value.value, max_depth );
   fc::reflector<T>::visit( vtor );
   FC_ASSERT( vtor.count_left == 0 );    // unrecognized extension throws here
}

template< typename T >
struct graphene_extension_to_variant_visitor
{
   graphene_extension_to_variant_visitor( const T& v, uint32_t max_depth ) : value(v), mvo(max_depth) {}

   template<typename Member, class Class, Member (Class::*member)>
   void operator()( const char* name )const
   {
      if( (value.*member).valid() )
         mvo( name, value.*member );
   }

   const T& value;
   mutable limited_mutable_variant_object mvo;
};

template< typename T >
void to_variant( const graphene::chain::extension<T>& value, fc::variant& var, uint32_t max_depth )
{
   graphene_extension_to_variant_visitor<T> vtor( value.value, max_depth );
   fc::reflector<T>::visit( vtor );
   var = vtor.mvo;
}

} // fc
