/******************************************************************************
 jStreamUtil.cc

	Useful functions for dealing with streams.

	Copyright © 1994 by John Lindal. All rights reserved.

 ******************************************************************************/

#include <jStreamUtil.h>
#include <iostream.h>
#include <JString.h>
#include <string.h>
#include <jMemory.h>
#include <limits.h>
#include <jAssert.h>

/******************************************************************************
 JCopyBinaryData

	Copies binary data from input to output.
	Caller must set initial read and write marks.

 ******************************************************************************/

void
JCopyBinaryData
	(
	istream&	input,
	ostream&	output,
	const JSize	byteCount
	)
{
	// allocate transfer space -- we want a big chunk but we don't want it to fail

	JSize chunkSize  = 10000;
	JCharacter* data = JCreateBuffer(&chunkSize);

	// copy the data in chunks

	JSize readCount = 0;
	while (!input.eof() && !input.fail() && readCount < byteCount)
		{
		// don't read past byteCount

		if (readCount + chunkSize > byteCount)
			{
			chunkSize = byteCount - readCount;
			}

		// transfer the chunk

		input.read(data, chunkSize);
		output.write(data, chunkSize);
		readCount += chunkSize;
		}

	delete [] data;

	assert( !input.bad() );

	// since nothing failed, we don't want to leave the eof bit set

	input.clear();
}

/******************************************************************************
 JRead

	Read the specified number of characters from the stream.

 ******************************************************************************/

JString
JRead
	(
	istream&	input,
	const JSize	count
	)
{
	JString str;
	str.Read(input, count);
	return str;
}

/******************************************************************************
 JReadUntil

	Read characters from the istream until delimiter is reached.
	delimiter is read in and discarded.

	If foundDelimiter is not NULL, it tells whether or not the delimiter
	was actually found.

 ******************************************************************************/

JString
JReadUntil
	(
	istream&			input,
	const JCharacter	delimiter,
	JBoolean*			foundDelimiter
	)
{
	JString str;
	JCharacter c;
	const JBoolean found = JReadUntil(input, 1, &delimiter, &str, &c);
	if (foundDelimiter != NULL)
		{
		*foundDelimiter = found;
		}
	return str;
}

/******************************************************************************
 JReadUntilws

	Read characters from the istream until white-space is reached.
	white-space is read in and discarded.

	If foundws is not NULL, it tells whether or not whitespace
	was actually encountered.

 ******************************************************************************/

JString
JReadUntilws
	(
	istream&	input,
	JBoolean*	foundws
	)
{
	JCharacter delimiters[] = { ' ', '\t', '\n' };
	const JSize delimiterCount = sizeof(delimiters)/sizeof(JCharacter);

	JString str;
	JCharacter c;
	const JBoolean found = JReadUntil(input, delimiterCount, delimiters, &str, &c);
	if (foundws != NULL)
		{
		*foundws = found;
		}
	input >> ws;
	return str;
}

/******************************************************************************
 JReadLine

	Read characters from the istream until newline ('\n') is reached.
	newline is read in and discarded.

	If foundNewLine is not NULL, it tells whether or not the end of a line
	was actually encountered.

	Not inline to avoid including JString.h in header file.

 ******************************************************************************/

JString
JReadLine
	(
	istream&	input,
	JBoolean*	foundNewLine
	)
{
	return JReadUntil(input, '\n', foundNewLine);
}

/******************************************************************************
 JReadAll

	Read characters until the end of the istream is reached.
	This function takes a JString* because the contents of the stream
	could be very large, and returning a JString requires twice as much
	memory because of the copy constructor.

	Not inline to avoid including JString.h in header file.

 ******************************************************************************/

void
JReadAll
	(
	istream&	input,
	JString*	str
	)
{
	JCharacter c;
	JReadUntil(input, 0, NULL, str, &c);
}

/******************************************************************************
 JReadUntil

	Read characters from the istream until one of the delimiters is reached.
	delimiter is read in and discarded.

	Returns kTrue if a delimited is found.  *delimiter is then set to
	the delimiter that was found.

	Returns kFalse if it encounters an error or end-of-stream instead of
	a delimiter.  *delimiter is not changed.

	delimiter can be NULL.

 ******************************************************************************/

JBoolean
JReadUntil
	(
	istream&			input,
	const JSize			delimiterCount,
	const JCharacter*	delimiters,
	JString*			str,
	JCharacter*			delimiter
	)
{
	str->Clear();
	JBoolean isDelimiter = kFalse;

	const JSize bufSize = 100;
	JCharacter buf[ bufSize ];

	JIndex i = 0;
	while (1)
		{
		JCharacter c;
		input.get(c);
		if (input.fail())
			{
			break;
			}

		for (JIndex j=0; j<delimiterCount; j++)
			{
			if (c == delimiters[j])
				{
				isDelimiter = kTrue;
				if (delimiter != NULL)
					{
					*delimiter = c;
					}
				break;
				}
			}

		if (isDelimiter || input.eof())
			{
			break;
			}
		else
			{
			buf[i] = c;
			i++;
			if (i == bufSize)
				{
				str->Append(buf, bufSize);
				i=0;
				}
			}
		}

	str->Append(buf, i);
	return isDelimiter;
}

/******************************************************************************
 JIgnoreUntil

	Discard characters from the istream until delimiter is reached.
	delimiter is read in and discarded.

	If foundDelimiter is not NULL, it tells whether or not the delimiter
	was actually found.

	To keep the prototype as close to JReadUntil() as possible,
	foundDelimiter is not the return value.

 ******************************************************************************/

void
JIgnoreUntil
	(
	istream&			input,
	const JCharacter	delimiter,
	JBoolean*			foundDelimiter
	)
{
	JCharacter c;
	const JBoolean found = JIgnoreUntil(input, 1, &delimiter, &c);
	if (foundDelimiter != NULL)
		{
		*foundDelimiter = found;
		}
}

void 
JIgnoreUntil
	(
	istream&			input, 
	const JCharacter*	delimiter,
	JBoolean*			foundDelimiter
	)
{
	const JSize delimLength = strlen(delimiter);
	assert( delimLength > 0 );

	if (delimLength == 1)
		{
		JIgnoreUntil(input, delimiter[0], foundDelimiter);
		return;
		}

	if (foundDelimiter != NULL)
		{
		*foundDelimiter = kFalse;
		}

	JString window;
	window.SetBlockSize(delimLength);
	window.Read(input, delimLength);

	while (!input.eof() && !input.fail() && window != delimiter)
		{
		window.RemoveSubstring(1,1);
		window.AppendCharacter(input.get());
		}

	if (window == delimiter && foundDelimiter != NULL)
		{
		*foundDelimiter = kTrue;
		}
}

/******************************************************************************
 JIgnoreUntil

	Toss characters from the istream until one of the delimiters is reached.
	delimiter is read in and discarded.

	Returns kTrue if a delimited is found.  *delimiter is then set to
	the delimiter that was found.

	Returns kFalse if it encounters an error or end-of-stream instead of
	a delimiter.  *delimiter is not changed.

	delimiter can be NULL.

 ******************************************************************************/

JBoolean
JIgnoreUntil
	(
	istream&			input,
	const JSize			delimiterCount,
	const JCharacter*	delimiters,
	JCharacter*			delimiter
	)
{
	JBoolean isDelimiter = kFalse;

	while (1)
		{
		JCharacter c;
		input.get(c);
		if (input.fail())
			{
			break;
			}

		for (JIndex i=0; i<delimiterCount; i++)
			{
			if (c == delimiters[i])
				{
				isDelimiter = kTrue;
				if (delimiter != NULL)
					{
					*delimiter = c;
					}
				break;
				}
			}

		if (isDelimiter || input.eof())
			{
			break;
			}
		}

	return isDelimiter;
}
