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

#ifndef TAOCPP_JSON_INCLUDE_REFERENCE_HH
#define TAOCPP_JSON_INCLUDE_REFERENCE_HH

#include "value.hh"
#include "pointer.hh"
#include "internal/uri_fragment.hh"

namespace tao
{
   namespace json
   {
      namespace internal
      {
         // JSON Reference, see draft ("work in progress") RFC at
         // https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03

         // NOTE: Currently, only URI fragments are supported.
         // Remote references are ignored, i.e., left untouched.

         // JSON References are replaced with a RAW_PTR,
         // which might lead to infinite loops if you try
         // to traverse the value. Make sure you understand
         // the consequences and handle the resulting value
         // accordingly!

         // Self-references will throw an exception, as well as
         // references into JSON Reference additional members
         // (which shall be ignored as per the specification).

         template< template< typename ... > class Traits >
         void resolve_references( basic_value< Traits > & r, basic_value< Traits > & v )
         {
            switch ( v.type() ) {
               case type::UNINITIALIZED:
                  return;
               case type::DISCARDED:
                  throw std::logic_error( "attempt to use a discarded value" );
               case type::NULL_:
               case type::BOOLEAN:
               case type::SIGNED:
               case type::UNSIGNED:
               case type::DOUBLE:
               case type::STRING:
                  return;
               case type::ARRAY:
                  for ( auto & e : v.unsafe_get_array() ) {
                     resolve_references( r, e );
                  }
                  return;
               case type::OBJECT:
                  for ( auto & e : v.unsafe_get_object() ) {
                     resolve_references( r, e.second );
                  }
                  if ( const auto * ref = v.find( "$ref" )->skip_raw_ptr() ) {
                     if ( ref->is_string() ) {
                        const std::string & s = ref->unsafe_get_string();
                        if ( ! s.empty() && s[ 0 ] == '#' ) {
                           const pointer ptr = internal::uri_fragment_to_pointer( s );
                           const auto * p = & r;
                           auto it = ptr.begin();
                           while ( it != ptr.end() ) {
                              switch ( p->type() ) {
                              case type::ARRAY:
                                 p = p->at( it->index() ).skip_raw_ptr();
                                 break;
                              case type::OBJECT:
                                 if ( const auto * r = p->find( "$ref" ) ) {
                                    if ( r->is_string() ) {
                                       throw std::runtime_error( "invalid JSON Reference: referencing additional data members is invalid" );
                                    }
                                 }
                                 p = p->at( it->key() ).skip_raw_ptr();
                                 break;
                              default:
                                 throw invalid_type( ptr.begin(), std::next( it ) );
                              }
                              ++it;
                           }
                           if ( p == & v ) {
                              throw std::runtime_error( "JSON Reference: invalid self reference" );
                           }
                           v = p;
                           resolve_references( r, v );
                           return;
                        }
                        else {
                           // Ignore remote references for now...
                           // throw std::runtime_error( "JSON Reference: unsupported or invalid URI: " + s );
                        }
                     }
                  }
                  return;
               case type::RAW_PTR:
                  return;
            }
            throw std::logic_error( "invalid value for tao::json::type" );  // LCOV_EXCL_LINE
         }

      } // internal

      template< template< typename ... > class Traits >
      void resolve_references( basic_value< Traits > & r )
      {
         resolve_references( r, r );
      }

  } // json

} // tao

#endif
