#pragma once

#include <fc/reflect.hpp>
#include <fc/optional.hpp>
#include <fc/value.hpp>
#include <fc/value_cast.hpp>
#include <fc/tuple.hpp>
#include <fc/error_report.hpp>

namespace fc {
  template<typename T>
  T value_cast( const value& v );

  struct void_t{};

  template<typename T>
  void pack(fc::value& jsv, const T& v ); 

  template<typename T> 
  void unpack( const fc::value& jsv, T& v ); 

  template<typename A, typename B, typename C, typename D>
  void pack( fc::value& v, const tuple<A,B,C,D>& t );
  template<typename A, typename B, typename C, typename D>
  void unpack( const fc::value& val, tuple<A,B,C,D>& t ); 

  template<typename T> 
  void pack( fc::value& jsv, const fc::optional<T>& v );

  template<typename T> 
  void unpack( const fc::value& jsv, fc::optional<T>& v );

  inline void pack( fc::value& jsv, const char& v )         { jsv = fc::string(&v,1); }
  inline void pack( fc::value& jsv, const fc::value& v )    { jsv = v; }
  inline void pack( fc::value& jsv, fc::value& v )          { jsv = v; }
  inline void pack( fc::value& jsv, fc::value&& v )         { jsv = fc::move(v); }
  inline void pack( fc::value& jsv, const void_t& v )       { jsv = fc::value(); }
  inline void pack( fc::value& jsv, const bool& v )         { jsv = v; }
  inline void pack( fc::value& jsv, const float& v )        { jsv = v; }
  inline void pack( fc::value& jsv, const double& v )       { jsv = v; }
  inline void pack( fc::value& jsv, const uint8_t& v )      { jsv = v; }
  inline void pack( fc::value& jsv, const uint16_t& v )     { jsv = v; }
  inline void pack( fc::value& jsv, const uint32_t& v )     { jsv = v; }
  inline void pack( fc::value& jsv, const uint64_t& v )     { jsv = v; }
  inline void pack( fc::value& jsv, const int8_t& v )       { jsv = v; }
  inline void pack( fc::value& jsv, const int16_t& v )      { jsv = v; }
  inline void pack( fc::value& jsv, const int32_t& v )      { jsv = v; }
  inline void pack( fc::value& jsv, const int64_t& v )      { jsv = v; }
  inline void pack( fc::value& jsv, const fc::string& v )   { jsv = value(v); }
  inline void pack( fc::value& jsv, fc::string& v )         { jsv = v; }
  inline void pack( fc::value& jsv, fc::string&& v )        { jsv = fc::move(v); }
  inline void pack( fc::value& jsv, const char* v )         { jsv = fc::string(v); }

  void pack( fc::value& jsv, const fc::vector<char>& value );
  template<typename T>
  void pack( fc::value& jsv, const fc::vector<T>& value );


  inline void unpack( const fc::value& jsv, fc::value& v ) { v = jsv; }
  template<typename T>
  void unpack( const fc::value& jsv, const T& v ); 
  template<typename T> 
  void unpack( const fc::value& jsv, T& v ); 
  void unpack( const fc::value& jsv, bool& v );

  inline void unpack( const fc::value& jsv, void_t& v ){ };

  void unpack( const fc::value& jsv, float& v );
  void unpack( const fc::value& jsv, double& v );
  void unpack( const fc::value& jsv, uint8_t& v );
  void unpack( const fc::value& jsv, uint16_t& v );
  void unpack( const fc::value& jsv, uint32_t& v );
  void unpack( const fc::value& jsv, uint64_t& v );
  void unpack( const fc::value& jsv, int8_t& v );
  void unpack( const fc::value& jsv, int16_t& v );
  void unpack( const fc::value& jsv, int32_t& v );
  void unpack( const fc::value& jsv, int64_t& v );
  void unpack( const fc::value& jsv, fc::string& v );

  void unpack( const fc::value& jsv, fc::vector<double>& value );

  void unpack( const fc::value& jsv, fc::vector<char>& value );
  template<typename T>
  void unpack( const fc::value& jsv, fc::vector<T>& value );

  namespace detail {
    template<typename Class>
    struct pack_object_visitor {
      pack_object_visitor(const Class& _c, fc::value& _val)
      :c(_c),obj(_val){}

      /**
      VC++ does not understand the difference of return types, so an extra layer is needed.
      */
      template<typename T>
      inline void pack_helper( const T& v, const char* name )const {
		value* o = &obj[name];
        fc::pack( *o, v ); 
      }
      template<typename T>
      inline void pack_helper( const fc::optional<T>& v, const char* name )const {
        if( !!v ) {
          fc::pack( obj[name], *v ); 
        }
      }
      template<typename T, typename C, T (C::*p)>
      inline void operator()( const char* name )const {
        pack_helper( c.*p, name );
      }

      private:            
        const Class&       c;
        fc::value& obj;
    };

    template<typename T>
    struct is_optional {
      typedef fc::false_type type;
    };
    template<typename T>
    struct is_optional<fc::optional<T> > {
      typedef fc::true_type type;
    };

    template<typename Class>
    struct unpack_object_visitor  {
      unpack_object_visitor(Class& _c, const fc::value& _val)
      :c(_c),obj(_val){}

      template<typename T, typename C, T (C::*p)>
      void operator()( const char* name )const {
         if( obj.find(name) != obj.end()) {
            try { 
             fc::unpack( obj[name], c.*p );
            } catch ( fc::error_report& er ) {
                throw FC_REPORT_PUSH( er, "Error parsing field '${field_name}'", fc::value().set("field_name",name) );
            }
         }
         else {
            if( !is_optional< typename fc::remove_reference<decltype(c.*p)>::type >::type::value ) {
                wlog( "unable to find name: '%s'",name);
            }
         }
      }
      Class&                  c;
      const fc::value& obj;
    };

    template<typename IsReflected=fc::false_type>
    struct if_enum {
      template<typename T>
      static inline void pack( fc::value& jsv, const T& v ) { 
         jsv = fc::value::object();
         detail::pack_object_visitor<T> pov(v,jsv);
         fc::reflector<T>::visit(pov);
      }
      template<typename T>
      static inline void unpack( const fc::value& jsv, T& v ) { 
         detail::unpack_object_visitor<T> pov(v,jsv );
         fc::reflector<T>::visit(pov);
      }
    };

    template<> struct if_enum<fc::true_type> {
      template<typename T>
      static inline void pack( fc::value& jsv, const T& v ) { 
         fc::pack( jsv, fc::reflector<T>::to_string(v) );
      }
      template<typename T>
      static inline void unpack( const fc::value& jsv, T& v ) { 
         if( strcmp( jsv.type(), "string" ) == 0 ) {
            v = fc::reflector<T>::from_string( fc::value_cast<fc::string>(jsv).c_str() );
         } else {
            // throw if invalid int, by attempting to convert to string
            fc::reflector<T>::to_string( v = value_cast<int64_t>(jsv) );
         }
      }
    };


    template<typename IsReflected=fc::false_type>
    struct if_reflected {
      template<typename T>
      static inline void pack(fc::value& s, const T& v ) { 
        v.did_not_implement_reflect_macro();
        wlog( "warning, ignoring unknown type" );
      }
      template<typename T>
      static inline void unpack( const fc::value& s, T& v ) { 
        v.did_not_implement_reflect_macro();
        wlog( "warning, ignoring unknown type" );
      }
    };

    template<>
    struct if_reflected<fc::true_type> {
      template<typename T>
      static inline void pack( fc::value& jsv, const T& v ) { 
         if_enum<typename fc::reflector<T>::is_enum>::pack( jsv,v );
      }
      template<typename T>
      static inline void unpack( const fc::value& jsv, T& v ) { 
         if_enum<typename fc::reflector<T>::is_enum>::unpack( jsv,v );
      }
    };

  } // namesapce detail

  inline void unpack( const fc::value& jsv, char& v )         { 
    auto s = value_cast<fc::string>(jsv); 
    if( s.size() ) v = s[0];
  }
  inline void unpack( const fc::value& jsv, bool& v )         { v = value_cast<bool>(jsv);          }
  inline void unpack( const fc::value& jsv, float& v )        { v = value_cast<float>(jsv);         }
  inline void unpack( const fc::value& jsv, double& v )       { v = value_cast<double>(jsv);        }
  inline void unpack( const fc::value& jsv, uint8_t& v )      { v = value_cast<uint8_t>(jsv);       }
  inline void unpack( const fc::value& jsv, uint16_t& v )     { v = value_cast<uint16_t>(jsv);      }
  inline void unpack( const fc::value& jsv, uint32_t& v )     { v = value_cast<uint32_t>(jsv);      }
  inline void unpack( const fc::value& jsv, uint64_t& v )     { v = value_cast<uint64_t>(jsv);      }
  inline void unpack( const fc::value& jsv, int8_t& v )       { v = value_cast<int8_t>(jsv);        }
  inline void unpack( const fc::value& jsv, int16_t& v )      { v = value_cast<int16_t>(jsv);       }
  inline void unpack( const fc::value& jsv, int32_t& v )      { v = value_cast<int32_t>(jsv);       }
  inline void unpack( const fc::value& jsv, int64_t& v )      { v = value_cast<int64_t>(jsv);       }
  inline void unpack( const fc::value& jsv, fc::string& v )   { v = value_cast<fc::string>(jsv);    }

  template<typename T> 
  void pack( fc::value& jsv, const fc::optional<T>& v ) {
    if( v ) pack(  jsv, *v );
    else jsv = fc::value();
  }
  template<typename T> 
  void unpack( const fc::value& jsv, fc::optional<T>& v ) {
    if( strcmp( jsv.type(), "void" ) != 0 ) {
      T tmp;
      unpack(  jsv, tmp );
      v = fc::move(tmp);
    }
  }


  template<typename T>
  inline void pack( fc::value& jsv, const fc::vector<T>& value ) {
      jsv = fc::value::array();
      jsv.resize(value.size());
      typename fc::vector<T>::const_iterator itr = value.begin();
      typename fc::vector<T>::const_iterator end = value.end();
      uint32_t i = 0;
      while( itr != end ) {
          fc::pack( jsv[i], *itr );
          ++itr;
          ++i;
      }
  }
  struct tuple_to_value_visitor {
    tuple_to_value_visitor( value& v ):_val(v),_count(0) { }
    template<typename T>
    void operator()( T&& t ) {
      _val[_count] = value(fc::forward<T>(t) );
      ++_count;
    }
    value& _val;
    int    _count;
  };
  struct tuple_from_value_visitor {
    tuple_from_value_visitor( const value& v ):_val(v),_count(0) { }
    template<typename T>
    void operator()( T&& t ) {
      if( _count < _val.size() ) unpack( _val[_count], t );
      ++_count;
    }
    const value& _val;
    int    _count;
  };

  template<typename A, typename B, typename C, typename D>
  inline void pack( fc::value& val, const tuple<A,B,C,D>& t ) {
    val = fc::value::array( tuple<A,B,C,D>::size );
    t.visit( tuple_to_value_visitor(val) );
  }
  template<typename A, typename B, typename C, typename D>
  inline void unpack( const fc::value& val, tuple<A,B,C,D>& t ) {
    t.visit( tuple_from_value_visitor(val) );
  }

  template<typename T>
  inline void unpack( const fc::value& jsv, fc::vector<T>& val ) {
      val.resize( jsv.size() );
      uint32_t s = jsv.size();
      for( uint32_t i = 0; i < s; ++i ) {
          unpack( jsv[i], val[i] );
      }
  }

  // default case
  template<typename T> 
  inline void pack( fc::value& jsv, const T& v ) {
      detail::if_reflected< typename fc::reflector<T>::is_defined >::pack(jsv,v);
  }

  template<typename T> 
  inline void unpack( const fc::value& jsv, T& v ) {
      detail::if_reflected< typename fc::reflector<T>::is_defined >::unpack(jsv,v);
  }

} // namespace fc 

