/*
 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
 * Version 1.1.6
 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
 * please contact Symisc Systems via:
 *       legal@symisc.net
 *       licensing@symisc.net
 *       contact@symisc.net
 * or visit:
 *      http://unqlite.org/licensing.html
 */
 /* $SymiscID: fastjson.c v1.1 FreeBSD 2012-12-05 22:52 stable <chm@symisc.net> $ */
#ifndef UNQLITE_AMALGAMATION
#include "unqliteInt.h"
#endif
/* JSON binary encoding, decoding and stuff like that */
#ifndef UNQLITE_FAST_JSON_NEST_LIMIT
#if defined(__WINNT__) || defined(__UNIXES__)
#define UNQLITE_FAST_JSON_NEST_LIMIT 64 /* Nesting limit */
#else
#define UNQLITE_FAST_JSON_NEST_LIMIT 32 /* Nesting limit */
#endif
#endif /* UNQLITE_FAST_JSON_NEST_LIMIT */
/* 
 * JSON to Binary using the FastJSON implementation (BigEndian).
 */
/*
 * FastJSON implemented binary token.
 */
#define FJSON_DOC_START    1 /* { */
#define FJSON_DOC_END      2 /* } */
#define FJSON_ARRAY_START  3 /* [ */
#define FJSON_ARRAY_END    4 /* ] */
#define FJSON_COLON        5 /* : */
#define FJSON_COMMA        6 /* , */
#define FJSON_ID           7 /* ID + 4 Bytes length */
#define FJSON_STRING       8 /* String + 4 bytes length */
#define FJSON_BYTE         9 /* Byte */
#define FJSON_INT64       10 /* Integer 64 + 8 bytes */
#define FJSON_REAL        18 /* Floating point value + 2 bytes */
#define FJSON_NULL        23 /* NULL */
#define FJSON_TRUE        24 /* TRUE */
#define FJSON_FALSE       25 /* FALSE */
/*
 * Encode a Jx9 value to binary JSON.
 */
UNQLITE_PRIVATE sxi32 FastJsonEncode(
	jx9_value *pValue, /* Value to encode */
	SyBlob *pOut,      /* Store encoded value here */
	int iNest          /* Nesting limit */ 
	)
{
	sxi32 iType = pValue ? pValue->iFlags : MEMOBJ_NULL;
	sxi32 rc = SXRET_OK;
	int c;
	if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){
		/* Nesting limit reached */
		return SXERR_LIMIT;
	}
	if( iType & (MEMOBJ_NULL|MEMOBJ_RES) ){
		/*
		 * Resources are encoded as null also.
		 */
		c = FJSON_NULL;
		rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
	}else if( iType & MEMOBJ_BOOL ){
		c = pValue->x.iVal ? FJSON_TRUE : FJSON_FALSE;
		rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
	}else if( iType & MEMOBJ_STRING ){
		unsigned char zBuf[sizeof(sxu32)]; /* String length */
		c = FJSON_STRING;
		SyBigEndianPack32(zBuf,SyBlobLength(&pValue->sBlob));
		rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
		if( rc == SXRET_OK ){
			rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf));
			if( rc == SXRET_OK ){
				rc = SyBlobAppend(pOut,SyBlobData(&pValue->sBlob),SyBlobLength(&pValue->sBlob));
			}
		}
	}else if( iType & MEMOBJ_INT ){
		unsigned char zBuf[8];
		/* 64bit big endian integer */
		c = FJSON_INT64;
		rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
		if( rc == SXRET_OK ){
			SyBigEndianPack64(zBuf,(sxu64)pValue->x.iVal);
			rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf));
		}
	}else if( iType & MEMOBJ_REAL ){
		/* Real number */
		c = FJSON_REAL;
		rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
		if( rc == SXRET_OK ){
			sxu32 iOfft = SyBlobLength(pOut);
			rc = SyBlobAppendBig16(pOut,0);
			if( rc == SXRET_OK ){
				unsigned char *zBlob;
				SyBlobFormat(pOut,"%.15g",pValue->x.rVal);
				zBlob = (unsigned char *)SyBlobDataAt(pOut,iOfft);
				SyBigEndianPack16(zBlob,(sxu16)(SyBlobLength(pOut) - ( 2 + iOfft)));
			}
		}
	}else if( iType & MEMOBJ_HASHMAP ){
		/* A JSON object or array */
		jx9_hashmap *pMap = (jx9_hashmap *)pValue->x.pOther;
		jx9_hashmap_node *pNode;
		jx9_value *pEntry;
		/* Reset the hashmap loop cursor */
		jx9HashmapResetLoopCursor(pMap);
		if( pMap->iFlags & HASHMAP_JSON_OBJECT ){
			jx9_value sKey;
			/* A JSON object */
			c = FJSON_DOC_START; /* { */
			rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
			if( rc == SXRET_OK ){
				jx9MemObjInit(pMap->pVm,&sKey);
				/* Encode object entries */
				while((pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){
					/* Extract the key */
					jx9HashmapExtractNodeKey(pNode,&sKey);
					/* Encode it */
					rc = FastJsonEncode(&sKey,pOut,iNest+1);
					if( rc != SXRET_OK ){
						break;
					}
					c = FJSON_COLON;
					rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
					if( rc != SXRET_OK ){
						break;
					}
					/* Extract the value */
					pEntry = jx9HashmapGetNodeValue(pNode);
					/* Encode it */
					rc = FastJsonEncode(pEntry,pOut,iNest+1);
					if( rc != SXRET_OK ){
						break;
					}
					/* Delimit the entry */
					c = FJSON_COMMA;
					rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
					if( rc != SXRET_OK ){
						break;
					}
				}
				jx9MemObjRelease(&sKey);
				if( rc == SXRET_OK ){
					c = FJSON_DOC_END; /* } */
					rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
				}
			}
		}else{
			/* A JSON array */
			c = FJSON_ARRAY_START; /* [ */
			rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
			if( rc == SXRET_OK ){
				/* Encode array entries */
				while( (pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){
					/* Extract the value */
					pEntry = jx9HashmapGetNodeValue(pNode);
					/* Encode it */
					rc = FastJsonEncode(pEntry,pOut,iNest+1);
					if( rc != SXRET_OK ){
						break;
					}
					/* Delimit the entry */
					c = FJSON_COMMA;
					rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
					if( rc != SXRET_OK ){
						break;
					}
				}
				if( rc == SXRET_OK ){
					c = FJSON_ARRAY_END; /* ] */
					rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
				}
			}
		}
	}
	return rc;
}
/*
 * Decode a FastJSON binary blob.
 */
UNQLITE_PRIVATE sxi32 FastJsonDecode(
	const void *pIn,  /* Binary JSON  */
	sxu32 nByte,      /* Chunk delimiter */
	jx9_value *pOut,  /* Decoded value */
	const unsigned char **pzPtr,
	int iNest /* Nesting limit */
	)
{
	const unsigned char *zIn = (const unsigned char *)pIn;
	const unsigned char *zEnd = &zIn[nByte];
	sxi32 rc = SXRET_OK;
	int c;
	if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){
		/* Nesting limit reached */
		return SXERR_LIMIT;
	}
	c = zIn[0];
	/* Advance the stream cursor */
	zIn++;
	/* Process the binary token */
	switch(c){
	case FJSON_NULL:
		/* null */
		jx9_value_null(pOut);
		break;
	case FJSON_FALSE:
		/* Boolean FALSE */
		jx9_value_bool(pOut,0);
		break;
	case FJSON_TRUE:
		/* Boolean TRUE */
		jx9_value_bool(pOut,1);
		break;
	case FJSON_INT64: {
		/* 64Bit integer */
		sxu64 iVal;
		/* Sanity check */
		if( &zIn[8] >= zEnd ){
			/* Corrupt chunk */
			rc = SXERR_CORRUPT;
			break;
		}
		SyBigEndianUnpack64(zIn,&iVal);
		/* Advance the pointer */
		zIn += 8;
		jx9_value_int64(pOut,(jx9_int64)iVal);
		break;
					  }
	case FJSON_REAL: {
		/* Real number */
		double iVal = 0; /* cc warning */
		sxu16 iLen;
		/* Sanity check */
		if( &zIn[2] >= zEnd ){
			/* Corrupt chunk */
			rc = SXERR_CORRUPT;
			break;
		}
		SyBigEndianUnpack16(zIn,&iLen);
		if( &zIn[iLen] >= zEnd ){
			/* Corrupt chunk */
			rc = SXERR_CORRUPT;
			break;
		}
		zIn += 2;
		SyStrToReal((const char *)zIn,(sxu32)iLen,&iVal,0);
		/* Advance the pointer */
		zIn += iLen;
		jx9_value_double(pOut,iVal);
		break;
					 }
	case FJSON_STRING: {
		/* UTF-8/Binary chunk */
		sxu32 iLength;
		/* Sanity check */
		if( &zIn[4] >= zEnd ){
			/* Corrupt chunk */
			rc = SXERR_CORRUPT;
			break;
		}
		SyBigEndianUnpack32(zIn,&iLength);
		if( &zIn[iLength] >= zEnd ){
			/* Corrupt chunk */
			rc = SXERR_CORRUPT;
			break;
		}
		zIn += 4;
		/* Invalidate any prior representation */
		if( pOut->iFlags & MEMOBJ_STRING ){
			/* Reset the string cursor */
			SyBlobReset(&pOut->sBlob);
		}
		rc = jx9MemObjStringAppend(pOut,(const char *)zIn,iLength);
		/* Update pointer */
		zIn += iLength;
		break;
					   }
	case FJSON_ARRAY_START: {
		/* Binary JSON array */
		jx9_hashmap *pMap;
		jx9_value sVal;
		/* Allocate a new hashmap */
		pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0);
		if( pMap == 0 ){
			rc = SXERR_MEM;
			break;
		}
		jx9MemObjInit(pOut->pVm,&sVal);
		jx9MemObjRelease(pOut);
		MemObjSetType(pOut,MEMOBJ_HASHMAP);
		pOut->x.pOther = pMap;
		rc = SXRET_OK;
		for(;;){
			/* Jump leading binary commas */
			while (zIn < zEnd && zIn[0] == FJSON_COMMA ){
				zIn++;
			}
			if( zIn >= zEnd || zIn[0] == FJSON_ARRAY_END ){
				if( zIn < zEnd ){
					zIn++; /* Jump the trailing binary ] */
				}
				break;
			}
			/* Decode the value */
			rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1);
			if( rc != SXRET_OK ){
				break;
			}
			/* Insert the decoded value */
			rc = jx9HashmapInsert(pMap,0,&sVal);
			if( rc != UNQLITE_OK ){
				break;
			}
		}
		if( rc != SXRET_OK ){
			jx9MemObjRelease(pOut);
		}
		jx9MemObjRelease(&sVal);
		break;
							}
	case FJSON_DOC_START: {
		/* Binary JSON object */
		jx9_value sVal,sKey;
		jx9_hashmap *pMap;
		/* Allocate a new hashmap */
		pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0);
		if( pMap == 0 ){
			rc = SXERR_MEM;
			break;
		}
		jx9MemObjInit(pOut->pVm,&sVal);
		jx9MemObjInit(pOut->pVm,&sKey);
		jx9MemObjRelease(pOut);
		MemObjSetType(pOut,MEMOBJ_HASHMAP);
		pOut->x.pOther = pMap;
		rc = SXRET_OK;
		for(;;){
			/* Jump leading binary commas */
			while (zIn < zEnd && zIn[0] == FJSON_COMMA ){
				zIn++;
			}
			if( zIn >= zEnd || zIn[0] == FJSON_DOC_END ){
				if( zIn < zEnd ){
					zIn++; /* Jump the trailing binary } */
				}
				break;
			}
			/* Extract the key */
			rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sKey,&zIn,iNest+1);
			if( rc != UNQLITE_OK ){
				break;
			}
			if( zIn >= zEnd || zIn[0] != FJSON_COLON ){
				rc = UNQLITE_CORRUPT;
				break;
			}
			zIn++; /* Jump the binary colon ':' */
			if( zIn >= zEnd ){
				rc = UNQLITE_CORRUPT;
				break;
			}
			/* Decode the value */
			rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1);
			if( rc != SXRET_OK ){
				break;
			}
			/* Insert the key and its associated value */
			rc = jx9HashmapInsert(pMap,&sKey,&sVal);
			if( rc != UNQLITE_OK ){
				break;
			}
		}
		if( rc != SXRET_OK ){
			jx9MemObjRelease(pOut);
		}
		jx9MemObjRelease(&sVal);
		jx9MemObjRelease(&sKey);
		break;
						  }
	default:
		/* Corrupt data */
		rc = SXERR_CORRUPT;
		break;
	}
	if( pzPtr ){
		*pzPtr = zIn;
	}
	return rc;
}
