/*
 * The original MSD format is simply:
 * 
 * #PARAM0:PARAM1:PARAM2:PARAM3;
 * #NEXTPARAM0:PARAM1:PARAM2:PARAM3;
 * 
 * (The first field is typically an identifier, but doesn't have to be.)
 *
 * The semicolon is not optional, though if we hit a # on a new line, eg:
 * #VALUE:PARAM1
 * #VALUE2:PARAM2
 * we'll recover.
 */

#include "global.h"
#include "MsdFile.h"
#include "RageFile.h"
#include "RageLog.h"
#include "RageUtil.h"

void MsdFile::AddParam( const char *buf, int len )
{
	values.back().params.push_back( RString(buf, len) );
}

void MsdFile::AddValue() /* (no extra charge) */
{
	values.push_back( value_t() );
	values.back().params.reserve( 32 );
}

void MsdFile::ReadBuf( const char *buf, int len, bool bUnescape )
{
	values.reserve( 64 );

	bool ReadingValue=false;
	int i = 0;
	char *cProcessed = new char[len];
	int iProcessedLen = -1;
	while( i < len )
	{
		if( i+1 < len && buf[i] == '/' && buf[i+1] == '/' )
		{
			/* Skip a comment entirely; don't copy the comment to the value/parameter */
			do
			{
				i++;
			} while( i < len && buf[i] != '\n' );

			continue;
		}

		if( ReadingValue && buf[i] == '#' )
		{
			/* Unfortunately, many of these files are missing ;'s.
			 * If we get a # when we thought we were inside a value, assume we
			 * missed the ;.  Back up and end the value. */
			// Make sure this # is the first non-whitespace character on the line.
			bool FirstChar = true;
			int j = iProcessedLen;
			while( j > 0 && cProcessed[j - 1] != '\r' && cProcessed[j - 1] != '\n' )
			{
				if( cProcessed[j - 1] == ' ' || cProcessed[j - 1] == '\t' )
				{
					--j;
					continue;
				}

				FirstChar = false;
				break;
			}

			if( !FirstChar )
			{
				/* We're not the first char on a line.  Treat it as if it were a normal character. */
				cProcessed[iProcessedLen++] = buf[i++];
				continue;
			}

			/* Skip newlines and whitespace before adding the value. */
			iProcessedLen = j;
			while( iProcessedLen > 0 &&
			       ( cProcessed[iProcessedLen - 1] == '\r' || cProcessed[iProcessedLen - 1] == '\n' ||
			         cProcessed[iProcessedLen - 1] == ' ' || cProcessed[iProcessedLen - 1] == '\t' ) )
				--iProcessedLen;

			AddParam( cProcessed, iProcessedLen );
			iProcessedLen = 0;
			ReadingValue=false;
		}

		/* # starts a new value. */
		if( !ReadingValue && buf[i] == '#' )
		{
			AddValue();
			ReadingValue=true;
		}

		if( !ReadingValue )
		{
			if( bUnescape && buf[i] == '\\' )
				i += 2;
			else
				++i;
			continue; /* nothing else is meaningful outside of a value */
		}

		/* : and ; end the current param, if any. */
		if( iProcessedLen != -1 && (buf[i] == ':' || buf[i] == ';') )
			AddParam( cProcessed, iProcessedLen );

		/* # and : begin new params. */
		if( buf[i] == '#' || buf[i] == ':' )
		{
			++i;
			iProcessedLen = 0;
			continue;
		}

		/* ; ends the current value. */
		if( buf[i] == ';' )
		{
			ReadingValue=false;
			++i;
			continue;
		}

		/* We've gone through all the control characters.  All that is left is either an escaped character, 
		 * ie \#, \\, \:, etc., or a regular character. */
		if( bUnescape && i < len && buf[i] == '\\' )
			++i;
		if( i < len )
		{
			cProcessed[iProcessedLen++] = buf[i++];
		}
	}

	/* Add any unterminated value at the very end. */
	if( ReadingValue )
		AddParam( cProcessed, iProcessedLen );

	delete [] cProcessed;
}

// returns true if successful, false otherwise
bool MsdFile::ReadFile( RString sNewPath, bool bUnescape )
{
	error = "";

	RageFile f;
	/* Open a file. */
	if( !f.Open( sNewPath ) )
	{
		error = f.GetError();
		return false;
	}

	// allocate a string to hold the file
	RString FileString;
	FileString.reserve( f.GetFileSize() );

	int iBytesRead = f.Read( FileString );
	if( iBytesRead == -1 )
	{
		error = f.GetError();
		return false;
	}

	ReadBuf( FileString.c_str(), iBytesRead, bUnescape );

	return true;
}

void MsdFile::ReadFromString( const RString &sString, bool bUnescape )
{
	ReadBuf( sString.c_str(), sString.size(), bUnescape );
}

RString MsdFile::GetParam(unsigned val, unsigned par) const
{
	if( val >= GetNumValues() || par >= GetNumParams(val) )
		return RString();

	return values[val].params[par];
}

/*
 * (c) 2001-2006 Chris Danford, Glenn Maynard
 *
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
