// 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_URI_FRAGMENT_HH
#define TAOCPP_JSON_INCLUDE_URI_FRAGMENT_HH

#include "../pointer.hh"

#include <stdexcept>

namespace tao
{
   namespace json
   {
      namespace internal
      {
         const bool allowed_uri_fragment_characters[] = {
            false, false, false, false, false, false, false, false, // 0
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false, // 16
            false, false, false, false, false, false, false, false,
            false, true,  false, false, true,  false, true,  true,  // 32
            true,  true,  true,  true,  true,  true,  true,  true,
            true,  true,  true,  true,  true,  true,  true,  true,  // 48
            true,  true,  true,  true,  false, true,  false, true,
            true,  true,  true,  true,  true,  true,  true,  true,  // 64
            true,  true,  true,  true,  true,  true,  true,  true,
            true,  true,  true,  true,  true,  true,  true,  true,  // 80
            true,  true,  true,  false, false, false, false, true,
            false, true,  true,  true,  true,  true,  true,  true,  // 96
            true,  true,  true,  true,  true,  true,  true,  true,
            true,  true,  true,  true,  true,  true,  true,  true,  // 112
            true,  true,  true,  false, false, false, true,  false,
            false, false, false, false, false, false, false, false, // 128
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false
         };

         inline char xdigit_value( const char c )
         {
            switch ( c ) {
            case '0': return 0;
            case '1': return 1;
            case '2': return 2;
            case '3': return 3;
            case '4': return 4;
            case '5': return 5;
            case '6': return 6;
            case '7': return 7;
            case '8': return 8;
            case '9': return 9;
            case 'a': case 'A': return 10;
            case 'b': case 'B': return 11;
            case 'c': case 'C': return 12;
            case 'd': case 'D': return 13;
            case 'e': case 'E': return 14;
            case 'f': case 'F': return 15;
            }
            throw std::invalid_argument( "invalid URI Fragment escape sequence, '%' must be followed by two hexadecimal digits" );
         }

         inline pointer uri_fragment_to_pointer( const std::string & v )
         {
            pointer result;
            if ( v.empty() || v[ 0 ] != '#' ) {
               throw std::invalid_argument( "invalid URI Fragment value, must begin with '#'" );
            }
            if ( v.size() > 1 ) {
               const char * p = v.data() + 1;
               const char * const e = v.data() + v.size();
               if ( *p++ != '/' ) {
                  throw std::invalid_argument( "invalid JSON Pointer value, must be empty or begin with '/'" );
               }
               std::string token;
               while ( p != e ) {
                  const char c = *p++;
                  switch( c ) {
                  case '!': case '$': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': case '-': case '.':
                  case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
                  case ':': case ';': case '=': case '?': case '@':
                  case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M':
                  case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
                  case '_':
                  case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm':
                  case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
                     token += c;
                     continue;

                  case '%':
                     if ( p != e ) {
                        const char h1 = xdigit_value( *p++ );
                        if ( p != e ) {
                           const char h2 = xdigit_value( *p++ );
                           token += h1 * 16 + h2;
                           continue;
                        }
                     }
                     throw std::invalid_argument( "invalid URI Fragment escape sequence, '%' must be followed by two hexadecimal digits" );

                  case '~':
                     if ( p != e ) {
                        switch ( *p++ ) {
                        case '0':
                           token += '~';
                           continue;
                        case '1':
                           token += '/';
                           continue;
                        }
                     }
                     throw std::invalid_argument( "invalid JSON Pointer escape sequence, '~' must be followed by '0' or '1'" );

                  case '/':
                     result += std::move( token );
                     token.clear();
                     continue;

                  default:
                     throw std::invalid_argument( "invalid URI Fragment character" );
                  }
               }
               result += std::move( token );
            }
            return result;
         }

         inline std::string tokens_to_uri_fragment( std::vector< token >::const_iterator it, const std::vector< token >::const_iterator & end )
         {
            static const char * hex = "0123456789ABCDEF";

            std::string result = "#";
            while ( it != end ) {
               result += '/';
               for ( const unsigned char c : it->key() ) {
                  switch ( c ) {
                     case '~':
                        result += "~0";
                        break;
                     case '/':
                        result += "~1";
                        break;
                     default:
                        if ( allowed_uri_fragment_characters[ c ] ) {
                           result += c;
                        }
                        else {
                           result += '%';
                           result += hex[ c >> 4 ];
                           result += hex[ c & 15 ];
                        }
                  }
               }
               ++it;
            }
            return result;
         }

         inline std::string to_uri_fragment( const pointer & p )
         {
            return tokens_to_uri_fragment( p.begin(), p.end() );
         }

      } // internal

   } // json

} // tao

#endif
