#include "JSONValidator.h"

#ifdef JSON_VALIDATE

inline bool isHex(json_char c) json_pure;
inline bool isHex(json_char c) json_nothrow {
    return (((c >= JSON_TEXT('0')) && (c <= JSON_TEXT('9'))) ||
		  ((c >= JSON_TEXT('A')) && (c <= JSON_TEXT('F'))) ||
		  ((c >= JSON_TEXT('a')) && (c <= JSON_TEXT('f'))));
}

bool JSONValidator::isValidNumber(const json_char * & ptr) json_nothrow {
    //ptr points at the first character in the number
    //ptr will end up past the last character
    bool decimal = false;
    bool scientific = false;

    //first letter is weird
    switch(*ptr){
	   #ifndef JSON_STRICT
		  case JSON_TEXT('.'):
			 decimal = true;
			 break;
		  case JSON_TEXT('+'):
	   #endif
	   case JSON_TEXT('-'):
	   case JSON_TEXT('1'):
	   case JSON_TEXT('2'):
	   case JSON_TEXT('3'):
	   case JSON_TEXT('4'):
	   case JSON_TEXT('5'):
	   case JSON_TEXT('6'):
	   case JSON_TEXT('7'):
	   case JSON_TEXT('8'):
	   case JSON_TEXT('9'):
		  break;
	   case JSON_TEXT('0'):
		  ++ptr;
		  switch(*ptr){
			 case JSON_TEXT('.'):
				decimal = true;
				break;
			 case JSON_TEXT('e'):
			 case JSON_TEXT('E'):
				scientific = true;
				++ptr;
				switch(*ptr){
				    case JSON_TEXT('\0'):
					   return false;
				    case JSON_TEXT('-'):
				    case JSON_TEXT('+'):
				    case JSON_TEXT('0'):
				    case JSON_TEXT('1'):
				    case JSON_TEXT('2'):
				    case JSON_TEXT('3'):
				    case JSON_TEXT('4'):
				    case JSON_TEXT('5'):
				    case JSON_TEXT('6'):
				    case JSON_TEXT('7'):
				    case JSON_TEXT('8'):
				    case JSON_TEXT('9'):
					   break;
				    default:
					   return false;
				}
				break;
			 #ifndef JSON_STRICT
				case JSON_TEXT('x'):
				    while(isHex(*++ptr)){};
				    return true;
				#ifdef JSON_OCTAL
				    #ifdef __GNUC__
				    case JSON_TEXT('0') ... JSON_TEXT('7'):  //octal
				    #else
				    case JSON_TEXT('0'):
				    case JSON_TEXT('1'):
				    case JSON_TEXT('2'):
				    case JSON_TEXT('3'):
				    case JSON_TEXT('4'):
				    case JSON_TEXT('5'):
				    case JSON_TEXT('6'):
				    case JSON_TEXT('7'):
				    #endif
					   while((*++ptr >= JSON_TEXT('0')) && (*ptr <= JSON_TEXT('7'))){};
					   return ((*ptr != JSON_TEXT('8')) && (*ptr != JSON_TEXT('9')));
				    case JSON_TEXT('8'):
				    case JSON_TEXT('9'):
					   break;
				#else
				    #ifdef __GNUC__
					   case JSON_TEXT('0') ... JSON_TEXT('9'):
				    #else
					   case JSON_TEXT('0'):
					   case JSON_TEXT('1'):
					   case JSON_TEXT('2'):
					   case JSON_TEXT('3'):
					   case JSON_TEXT('4'):
					   case JSON_TEXT('5'):
					   case JSON_TEXT('6'):
					   case JSON_TEXT('7'):
					   case JSON_TEXT('8'):
					   case JSON_TEXT('9'):
				    #endif
				    break;
				#endif
			 #else
				#ifdef __GNUC__
				    case JSON_TEXT('0') ... JSON_TEXT('9'):
				#else
				    case JSON_TEXT('0'):
				    case JSON_TEXT('1'):
				    case JSON_TEXT('2'):
				    case JSON_TEXT('3'):
				    case JSON_TEXT('4'):
				    case JSON_TEXT('5'):
				    case JSON_TEXT('6'):
				    case JSON_TEXT('7'):
				    case JSON_TEXT('8'):
				    case JSON_TEXT('9'):
				#endif
				    break;
			 #endif
			 default:  //just a 0
				return true;
		  }
		  break;
	   default:
		  return false;
    }
    ++ptr;

    //next digits
    while (true){
	   switch(*ptr){
		  case JSON_TEXT('.'):
			 if (json_unlikely(decimal)) return false; //multiple decimals
			 if (json_unlikely(scientific)) return false;
			 decimal = true;
			 break;
		  case JSON_TEXT('e'):
		  case JSON_TEXT('E'):
			 if (json_likely(scientific)) return false;
			 scientific = true;
			 ++ptr;
			 switch(*ptr){
				case JSON_TEXT('-'):
				case JSON_TEXT('+'):
				#ifdef __GNUC__
				    case JSON_TEXT('0') ... JSON_TEXT('9'):
				#else
				    case JSON_TEXT('0'):
				    case JSON_TEXT('1'):
				    case JSON_TEXT('2'):
				    case JSON_TEXT('3'):
				    case JSON_TEXT('4'):
				    case JSON_TEXT('5'):
				    case JSON_TEXT('6'):
				    case JSON_TEXT('7'):
				    case JSON_TEXT('8'):
				    case JSON_TEXT('9'):
				#endif
				    break;
				default:
				    return false;
			 }
			 break;
		  #ifdef __GNUC__
			 case JSON_TEXT('0') ... JSON_TEXT('9'):
		  #else
			 case JSON_TEXT('0'):
			 case JSON_TEXT('1'):
			 case JSON_TEXT('2'):
			 case JSON_TEXT('3'):
			 case JSON_TEXT('4'):
			 case JSON_TEXT('5'):
			 case JSON_TEXT('6'):
			 case JSON_TEXT('7'):
			 case JSON_TEXT('8'):
			 case JSON_TEXT('9'):
		  #endif
			 break;
		  default:
			 return true;
	   }
	   ++ptr;
    }
    return false;
}

#ifndef JSON_STRICT
    #define LETTERCASE(x, y)\
	   case JSON_TEXT(x):\
	   case JSON_TEXT(y)
    #define LETTERCHECK(x, y)\
	   if (json_unlikely((*++ptr != JSON_TEXT(x)) && (*ptr != JSON_TEXT(y)))) return false
#else
    #define LETTERCASE(x, y)\
	   case JSON_TEXT(x)
    #define LETTERCHECK(x, y)\
	   if (json_unlikely(*++ptr != JSON_TEXT(x))) return false
#endif
bool JSONValidator::isValidMember(const json_char * & ptr) json_nothrow {
    //ptr is on the first character of the member
    //ptr will end up immediately after the last character in the member
    switch(*ptr){
	   case JSON_TEXT('\"'):
		  return isValidString(++ptr);
	   case JSON_TEXT('{'):
		  return isValidObject(++ptr);
	   case JSON_TEXT('['):
		  return isValidArray(++ptr);
	   LETTERCASE('t', 'T'):
		  LETTERCHECK('r', 'R');
		  LETTERCHECK('u', 'U');
		  LETTERCHECK('e', 'E');
		  ++ptr;
		  return true;
	   LETTERCASE('f', 'F'):
		  LETTERCHECK('a', 'A');
		  LETTERCHECK('l', 'L');
		  LETTERCHECK('s', 'S');
		  LETTERCHECK('e', 'E');
		  ++ptr;
		  return true;
	   LETTERCASE('n', 'N'):
		  LETTERCHECK('u', 'U');
		  LETTERCHECK('l', 'L');
		  LETTERCHECK('l', 'L');
		  ++ptr;
		  return true;
	   #ifndef JSON_STRICT
		  case JSON_TEXT('}'):  //null in libjson
		  case JSON_TEXT(']'):  //null in libjson
		  case JSON_TEXT(','):  //null in libjson
			 return true;
	   #endif
	   case JSON_TEXT('\0'):
		  return false;
    }
    //a number
    return isValidNumber(ptr);
}

bool JSONValidator::isValidString(const json_char * & ptr) json_nothrow {
    //ptr is pointing to the first character after the quote
    //ptr will end up behind the closing "
    while(true){
	   switch(*ptr){
		  case JSON_TEXT('\\'):
			 switch(*(++ptr)){
				case JSON_TEXT('\"'):
				case JSON_TEXT('\\'):
				case JSON_TEXT('/'):
				case JSON_TEXT('b'):
				case JSON_TEXT('f'):
				case JSON_TEXT('n'):
				case JSON_TEXT('r'):
				case JSON_TEXT('t'):
				    break;
				case JSON_TEXT('u'):
				    if (json_unlikely(!isHex(*++ptr))) return false;
				    if (json_unlikely(!isHex(*++ptr))) return false;
				    //fallthrough to \x
				#ifndef JSON_STRICT
				case JSON_TEXT('x'):  //hex
				#endif
				    if (json_unlikely(!isHex(*++ptr))) return false;
				    if (json_unlikely(!isHex(*++ptr))) return false;
				    break;
				#ifndef JSON_OCTAL
				    #ifdef __GNUC__
				    case JSON_TEXT('0') ... JSON_TEXT('7'):  //octal
				    #else
				    case JSON_TEXT('0'):
				    case JSON_TEXT('1'):
				    case JSON_TEXT('2'):
				    case JSON_TEXT('3'):
				    case JSON_TEXT('4'):
				    case JSON_TEXT('5'):
				    case JSON_TEXT('6'):
				    case JSON_TEXT('7'):
				    #endif
					   if (json_unlikely((*++ptr < JSON_TEXT('0')) || (*ptr > JSON_TEXT('7')))) return false;
					   if (json_unlikely((*++ptr < JSON_TEXT('0')) || (*ptr > JSON_TEXT('7')))) return false;
					   break;
				#endif
				default:
				    return false;
			 }
			 break;
		  case JSON_TEXT('\"'):
			 ++ptr;
			 return true;
		  case JSON_TEXT('\0'):
			 return false;
	   }
	   ++ptr;
    }
    return false;
}

bool JSONValidator::isValidNamedObject(const json_char * &ptr) json_nothrow {
    if (json_unlikely(!isValidString(++ptr))) return false;
    if (json_unlikely(*ptr++ != ':')) return false;
    if (json_unlikely(!isValidMember(ptr))) return false;
    switch(*ptr){
	   case JSON_TEXT(','):
		  return isValidNamedObject(++ptr);
	   case JSON_TEXT('}'):
		  ++ptr;
		  return true;
	   default:
		  return false;
    }
}

bool JSONValidator::isValidObject(const json_char * & ptr) json_nothrow {
    //ptr should currently be pointing past the {, so this must be the start of a name, or the closing }
    //ptr will end up past the last }
    do{
	   switch(*ptr){
		  case JSON_TEXT('\"'):
			 return isValidNamedObject(ptr);
		  case JSON_TEXT('}'):
			 ++ptr;
			 return true;
		  default:
			 return false;
	   }
    } while (*++ptr);
    return false;
}

bool JSONValidator::isValidArray(const json_char * & ptr) json_nothrow {
    //ptr should currently be pointing past the [, so this must be the start of a member, or the closing ]
    //ptr will end up past the last ]
    do{
	   switch(*ptr){
		  case JSON_TEXT(']'):
			 ++ptr;
			 return true;
		  default:
			 if (json_unlikely(!isValidMember(ptr))) return false;
			 switch(*ptr){
				case JSON_TEXT(','):
				    break;
				case JSON_TEXT(']'):
				    ++ptr;
				    return true;
				default:
				    return false;
			 }
			 break;
	   }
    } while (*++ptr);
    return false;
}

bool JSONValidator::isValidRoot(const json_char * json) json_nothrow {
    const json_char * ptr = json;
    switch(*ptr){
	   case JSON_TEXT('{'):
		  if (json_likely(isValidObject(++ptr))){
			 return *ptr == JSON_TEXT('\0');
		  }
		  return false;
	   case JSON_TEXT('['):
		  if (json_likely(isValidArray(++ptr))){
			 return *ptr == JSON_TEXT('\0');
		  }
		  return false;
    }
    return false;
}

#endif
