//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Contains a branch-neutral binary packer for KeyValues trees. 
//
// $NoKeywords: $
//
//=============================================================================//

#include <KeyValues.h>
#include "kvpacker.h"

#include "tier0/dbg.h"
#include "utlbuffer.h"

// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>

#define KEYVALUES_TOKEN_SIZE	1024

// writes KeyValue as binary data to buffer
bool KVPacker::WriteAsBinary( KeyValues *pNode, CUtlBuffer &buffer )
{
	if ( buffer.IsText() ) // must be a binary buffer
		return false;

	if ( !buffer.IsValid() ) // must be valid, no overflows etc
		return false;

	// Write subkeys:

	// loop through all our peers
	for ( KeyValues *dat = pNode; dat != NULL; dat = dat->GetNextKey() )
	{
		// write type
		switch ( dat->GetDataType() )
		{
		case KeyValues::TYPE_NONE:
			{
				buffer.PutUnsignedChar( PACKTYPE_NONE );
				break;
			}
		case KeyValues::TYPE_STRING:
			{
				buffer.PutUnsignedChar( PACKTYPE_STRING );
				break;
			}
		case KeyValues::TYPE_WSTRING:
			{
				buffer.PutUnsignedChar( PACKTYPE_WSTRING );
				break;
			}

		case KeyValues::TYPE_INT:
			{
				buffer.PutUnsignedChar( PACKTYPE_INT );
				break;
			}

		case KeyValues::TYPE_UINT64:
			{
				buffer.PutUnsignedChar( PACKTYPE_UINT64 );
				break;
			}

		case KeyValues::TYPE_FLOAT:
			{
				buffer.PutUnsignedChar( PACKTYPE_FLOAT );
				break;
			}
		case KeyValues::TYPE_COLOR:
			{
				buffer.PutUnsignedChar( PACKTYPE_COLOR );
				break;
			}
		case KeyValues::TYPE_PTR:
			{
				buffer.PutUnsignedChar( PACKTYPE_PTR );
				break;
			}

		default:
			break;
		}

		// write name
		buffer.PutString( dat->GetName() );

		// write value
		switch ( dat->GetDataType() )
		{
		case KeyValues::TYPE_NONE:
			{
				if( !WriteAsBinary( dat->GetFirstSubKey(), buffer ) )
					return false;
				break;
			}
		case KeyValues::TYPE_STRING:
			{
				if (dat->GetString() && *(dat->GetString()))
				{
					buffer.PutString( dat->GetString() );
				}
				else
				{
					buffer.PutString( "" );
				}
				break;
			}
		case KeyValues::TYPE_WSTRING:
			{
				int nLength = dat->GetWString() ? Q_wcslen( dat->GetWString() ) : 0;
				buffer.PutShort( nLength );
				for( int k = 0; k < nLength; ++ k )
				{
					buffer.PutShort( ( unsigned short ) dat->GetWString()[k] );
				}
				break;
			}

		case KeyValues::TYPE_INT:
			{
				buffer.PutInt( dat->GetInt() );				
				break;
			}

		case KeyValues::TYPE_UINT64:
			{
				buffer.PutInt64( dat->GetUint64() );
				break;
			}

		case KeyValues::TYPE_FLOAT:
			{
				buffer.PutFloat( dat->GetFloat() );
				break;
			}
		case KeyValues::TYPE_COLOR:
			{
				Color color = dat->GetColor();
				buffer.PutUnsignedChar( color[0] );
				buffer.PutUnsignedChar( color[1] );
				buffer.PutUnsignedChar( color[2] );
				buffer.PutUnsignedChar( color[3] );
				break;
			}
		case KeyValues::TYPE_PTR:
			{
				buffer.PutUnsignedInt( (uintptr_t)dat->GetPtr() );
				break;
			}

		default:
			break;
		}
	}

	// write tail, marks end of peers
	buffer.PutUnsignedChar( PACKTYPE_NULLMARKER ); 

	return buffer.IsValid();
}

// read KeyValues from binary buffer, returns true if parsing was successful
bool KVPacker::ReadAsBinary( KeyValues *pNode, CUtlBuffer &buffer )
{
	if ( buffer.IsText() ) // must be a binary buffer
		return false;

	if ( !buffer.IsValid() ) // must be valid, no overflows etc
		return false;

	pNode->Clear();

	char		token[KEYVALUES_TOKEN_SIZE];
	KeyValues	*dat = pNode;
	EPackType		ePackType = (EPackType)buffer.GetUnsignedChar();

	// loop through all our peers
	while ( true )
	{
		if ( ePackType == PACKTYPE_NULLMARKER )
			break; // no more peers

		buffer.GetString( token );
		token[KEYVALUES_TOKEN_SIZE-1] = 0;

		dat->SetName( token );

		switch ( ePackType )
		{
		case PACKTYPE_NONE:
			{
				KeyValues *pNewNode = new KeyValues("");
				dat->AddSubKey( pNewNode );
				if( !ReadAsBinary( pNewNode, buffer ) )
					return false;
				break;
			}
		case PACKTYPE_STRING:
			{
				buffer.GetString( token );
				token[KEYVALUES_TOKEN_SIZE-1] = 0;
				dat->SetStringValue( token );
				break;
			}
		case PACKTYPE_WSTRING:
			{
				int nLength = buffer.GetShort();
				if ( nLength >= 0 && nLength*sizeof( uint16 ) <= (uint)buffer.GetBytesRemaining() )
				{
					if ( nLength > 0 )
					{
						wchar_t *pTemp = (wchar_t *)malloc( sizeof( wchar_t ) * (1 + nLength) );

						for ( int k = 0; k < nLength; ++k )
						{
							pTemp[k] = buffer.GetShort(); // ugly, but preserving existing behavior
						}

						pTemp[nLength] = 0;
						dat->SetWString( NULL, pTemp );

						free( pTemp );
					}
					else
						dat->SetWString( NULL, L"" );

				}
				break;
			}

		case PACKTYPE_INT:
			{
				dat->SetInt( NULL, buffer.GetInt() );
				break;
			}

		case PACKTYPE_UINT64:
			{
				dat->SetUint64( NULL, (uint64)buffer.GetInt64() );
				break;
			}

		case PACKTYPE_FLOAT:
			{
				dat->SetFloat( NULL, buffer.GetFloat() );
				break;
			}
		case PACKTYPE_COLOR:
			{
				Color color( 
					buffer.GetUnsignedChar(),
					buffer.GetUnsignedChar(),
					buffer.GetUnsignedChar(),
					buffer.GetUnsignedChar() );
				dat->SetColor( NULL, color );
				break;
			}
		case PACKTYPE_PTR:
			{
				dat->SetPtr( NULL, buffer.GetPtr() );
				break;
			}

		default:
			break;
		}

		if ( !buffer.IsValid() ) // error occured
			return false;

		ePackType = (EPackType)buffer.GetUnsignedChar();

		if ( ePackType == PACKTYPE_NULLMARKER )
			break;

		// new peer follows
		KeyValues *pNewPeer = new KeyValues("");
		dat->SetNextKey( pNewPeer );
		dat = pNewPeer;
	}

	return buffer.IsValid();
}

