#include "libMUSCLE/muscle.h"
#include "libMUSCLE/textfile.h"
#include <errno.h>

namespace muscle {

TextFile::TextFile(const char szFileName[], bool bWrite)
	{
	FILE *ptrFile = 0;
	if (bWrite)
		{
		if (0 == strcmp(szFileName, "-"))
			ptrFile = stdout;
		else
			ptrFile = fopen(szFileName, "wb");
		}
	else
		{
		if (0 == strcmp(szFileName, "-"))
			ptrFile = stdin;
		else
			ptrFile = fopen(szFileName, "rb");
		}
	if (0 == ptrFile)
		Quit("Cannot open '%s' errno=%d\n", szFileName, errno);
	Init(ptrFile, szFileName);
	}

void TextFile::Init(FILE *ptrFile, const char *ptrFileName)
	{
	m_ptrFile = ptrFile;
	m_ptrName = strdup(ptrFileName);
	m_uLineNr = 1;
	m_uColNr = 0;
	m_bLastCharWasEOL = true;
	m_cPushedBack = -1;
#if	DEBUG
	setbuf(m_ptrFile, 0);
#endif
	}

TextFile::TextFile(FILE *ptrFile, const char *ptrFileName)
	{
	Init(ptrFile, "-");
	}

TextFile::~TextFile()
	{
	if (m_ptrFile &&
	  m_ptrFile != stdin && m_ptrFile != stdout && m_ptrFile != stderr)
		fclose(m_ptrFile);
	free(m_ptrName);
	}

// Get line from file.
// Return true if end-of-file, quit if line too long.
bool TextFile::GetLine(char szLine[], unsigned uBytes)
	{
	if (0 == uBytes)
		Quit("TextFile::GetLine, buffer zero size");

	memset(szLine, 0, uBytes);

	unsigned uBytesCopied = 0;

// Loop until end of line or end of file.
	for (;;)
		{
		char c;
		bool bEof = GetChar(c);
		if (bEof)
			return true;
		if ('\r' == c)
			continue;
		if ('\n' == c)
			return false;
		if (uBytesCopied < uBytes - 1)
			szLine[uBytesCopied++] = (char) c;
		else
			Quit("TextFile::GetLine: input buffer too small, line %u",
			  m_uLineNr);
		}
	}

// As GetLine, but trim leading and trailing blanks; skip empty lines
bool TextFile::GetTrimLine(char szLine[], unsigned uBytes)
	{
	for (;;)
		{
		bool bEOF = GetLine(szLine, uBytes);
		if (bEOF)
			return true;
		TrimBlanks(szLine);
		if (0 != szLine[0])
			break;
		}
	return false;
	}

void TextFile::Rewind()
	{
	fseek(m_ptrFile, 0, SEEK_SET);
	m_uLineNr = 1;
	m_bLastCharWasEOL = true;
	}

void TextFile::PutChar(char c)
	{
	int i = fputc(c, m_ptrFile);
	assert(i == c);
	if ('\n' == c)
		{
		++m_uLineNr;
		m_uColNr = 1;
		}
	else
		++m_uColNr;
	}

void TextFile::PutString(const char szLine[])
	{
	int iError = fputs(szLine, m_ptrFile);
	assert(iError >= 0);
	}

void TextFile::PutFormat(const char szFormat[], ...)
	{
	char szStr[4096];
	va_list ArgList;
	va_start(ArgList, szFormat);
	vsprintf(szStr, szFormat, ArgList);
	PutString(szStr);
	}

void TextFile::GetLineX(char szLine[], unsigned uBytes)
	{
	bool bEof = GetLine(szLine, uBytes);
	if (bEof)
		Quit("end-of-file in GetLineX");
	}

bool TextFile::GetToken(char szToken[], unsigned uBytes, const char szCharTokens[])
	{
// Skip leading white space
	char c;
	for (;;)
		{
		bool bEof = GetChar(c);
		if (bEof)
			return true;
		if (!isspace(c))
			break;
		}

// Check for special case single-character tokens
	if (0 != strchr(szCharTokens, c))
		{
		assert(uBytes >= 2);
		szToken[0] = c;
		szToken[1] = 0;
		return false;
		}

// Loop until token terminated by white space, EOF or special
	unsigned uBytesCopied = 0;
	for (;;)
		{
		if (uBytesCopied < uBytes - 1)
			szToken[uBytesCopied++] = c;
		else
			Quit("TextFile::GetToken: input buffer too small, line %u",
			  m_uLineNr);
		bool bEof = GetChar(c);
		if (bEof)
			{
			szToken[uBytesCopied] = 0;
			return true;
			}
	// Check for special case single-character tokens
		if (0 != strchr(szCharTokens, c))
			{
			PushBack(c);
			assert(uBytesCopied > 0 && uBytesCopied < uBytes);
			szToken[uBytesCopied] = 0;
			return false;
			}
		if (isspace(c))
			{
			assert(uBytesCopied > 0 && uBytesCopied < uBytes);
			szToken[uBytesCopied] = 0;
			return false;
			}
		}
	}

void TextFile::GetTokenX(char szToken[], unsigned uBytes, const char szCharTokens[])
	{
	bool bEof = GetToken(szToken, uBytes, szCharTokens);
	if (bEof)
		Quit("End-of-file in GetTokenX");
	}

void TextFile::Skip()
	{
	for (;;)
		{
		char c;
		bool bEof = GetChar(c);
		if (bEof || '\n' == c)
			return;
		assert(isspace(c));
		}
	}

#ifdef _WIN32

TEXTFILEPOS TextFile::GetPos()
	{
	fpos_t p;
	int i = fgetpos(m_ptrFile, &p);
	assert(0 == i);
	assert(p >= 0);
	TEXTFILEPOS Pos;
	Pos.uOffset = (unsigned) p;
	Pos.uLineNr = m_uLineNr;
	Pos.uColNr = m_uColNr;
	return Pos;
	}

void TextFile::SetPos(TEXTFILEPOS Pos)
	{
	fpos_t p = (fpos_t) Pos.uOffset;
	int i = fsetpos(m_ptrFile, &p);
	assert(0 == i);
	m_uLineNr = Pos.uLineNr;
	m_uColNr = Pos.uColNr;
	}

#else

TEXTFILEPOS TextFile::GetPos()
	{
	TEXTFILEPOS Pos;
	Pos.uOffset = ftell(m_ptrFile);
	Pos.uLineNr = m_uLineNr;
	Pos.uColNr = m_uColNr;
	return Pos;
	}

void TextFile::SetPos(TEXTFILEPOS Pos)
	{
	fseek(m_ptrFile, Pos.uOffset, SEEK_SET);
	m_uLineNr = Pos.uLineNr;
	m_uColNr = Pos.uColNr;
	}

#endif

bool TextFile::GetChar(char &c)
	{
	if (-1 != m_cPushedBack)
		{
		c = (char) m_cPushedBack;
		m_cPushedBack = -1;
		return false;
		}

	int ic = fgetc(m_ptrFile);
	if (ic < 0)
		{
		if (feof(m_ptrFile))
			{
		// Hack to fix up a non-empty text file that is missing
		// and end-of-line character in the last line.
			if (!m_bLastCharWasEOL && m_uLineNr > 0)
				{
				c = '\n';
				m_bLastCharWasEOL = true;
				return false;
				}
			return true;
			}
		Quit("TextFile::GetChar, error %s", strerror(errno));
		}
	c = (char) ic;
	if ('\n' == c)
		{
		m_bLastCharWasEOL = true;
		++m_uLineNr;
		m_uColNr = 1;
		}
	else
		{
		m_bLastCharWasEOL = false;
		++m_uColNr;
		}
	return false;
	}

void TextFile::GetCharX(char &c)
	{
	bool bEof = GetChar(c);
	if (bEof)
		Quit("End-of-file in GetCharX");
	}

void TextFile::GetNonblankChar(char &c)
	{
	do
		{
		bool bEof = GetChar(c);
		if (bEof)
			Quit("End-of-file in GetCharX");
		}
	while (isspace(c));
	}

void TextFile::SkipLine()
	{
	if (m_bLastCharWasEOL)
		return;
	for (;;)
		{
		char c;
		bool bEof = GetChar(c);
		if (bEof)
			Quit("End-of-file in SkipLine");
		if ('\n' == c)
			break;
		}
	}

void TextFile::SkipWhite()
	{
	bool bEof = SkipWhiteX();
	if (bEof)
		Quit("End-of-file skipping white space");
	}

bool TextFile::SkipWhiteX()
	{
	for (;;)
		{
		char c;
		bool bEof = GetChar(c);
		if (bEof)
			return true;
		if (!isspace(c))
			{
			PushBack(c);
			break;
			}
		}
	return false;
	}
} 
