/* 
 *	Copyright (C) 2003-2006 Gabest
 *	http://www.gabest.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "stdafx.h"
#include "Stream.h"
#include <stdio.h>

namespace ssf
{
	Stream::Stream()
		: m_encoding(none)
		, m_line(0)
		, m_col(-1)
	{
		
	}

	Stream::~Stream()
	{
	}

	void Stream::ThrowError(LPCTSTR fmt, ...)
	{
		va_list args;
		va_start(args, fmt);
		int len = _vsctprintf(fmt, args) + 1;
		CString str;
		if(len > 0) _vstprintf_s(str.GetBufferSetLength(len), len, fmt, args);
		va_end(args);

		throw Exception(_T("Error (Ln %d Col %d): %s"), m_line+1, m_col+1, str);
	}

	bool Stream::IsWhiteSpace(int c, LPCWSTR morechars)
	{
		return c != 0xa0 && iswspace(c) || morechars && wcschr(morechars, (WCHAR)c);
	}

	//

	InputStream::InputStream()
	{
		
	}

	InputStream::~InputStream()
	{
	}

	int InputStream::NextChar()
	{
		if(m_encoding == none)
		{
			m_encoding = unknown;

			switch(NextByte())
			{
			case 0xef: 
				if(NextByte() == 0xbb && NextByte() == 0xbf) m_encoding = utf8;
				break;
			case 0xff: 
				if(NextByte() == 0xfe) m_encoding = utf16le;
				break;
			case 0xfe:
				if(NextByte() == 0xff) m_encoding = utf16be;
				break;
			}
		}

		if(m_encoding == unknown)
		{
			throw Exception(_T("unknown character encoding, missing BOM"));
		}

		int i, c;

		int cur = NextByte();

		switch(m_encoding)
		{
		case utf8: 
			for(i = 7; i >= 0 && (cur & (1 << i)); i--);
			cur &= (1 << i) - 1;
			while(++i < 7) {c = NextByte(); if(c == EOS) {cur = EOS; break;} cur = (cur << 6) | (c & 0x3f);}
			break;
		case utf16le: 
			c = NextByte();
			if(c == EOS) {cur = EOS; break;}
			cur = (c << 8) | cur;
			break;
		case utf16be: 
			c = NextByte();
			if(c == EOS) {cur = EOS; break;}
			cur = cur | (c << 8);
			break;
		case wchar:
			break;
		}

		return cur;
	}

	int InputStream::PushChar()
	{
		int c = NextChar();
		m_queue.AddTail(c);
		return c;
	}

	int InputStream::PopChar()
	{
		if(m_queue.IsEmpty()) ThrowError(_T("fatal stream error"));

		int c = m_queue.RemoveHead();

		if(c != EOS)
		{
			if(c == '\n') {m_line++; m_col = -1;}
			m_col++;
		}

		return c;
	}

	int InputStream::PeekChar()
	{
		while(m_queue.GetCount() < 2) PushChar();

		ASSERT(m_queue.GetCount() == 2);

		if(m_queue.GetHead() == '/' && m_queue.GetTail() == '/')
		{
			while(!m_queue.IsEmpty()) PopChar();
			int c;
			do {PushChar(); c = PopChar();} while(!(c == '\n' || c == EOS));
			return PeekChar();
		}
		else if(m_queue.GetHead() == '/' && m_queue.GetTail() == '*')
		{
			while(!m_queue.IsEmpty()) PopChar();
			int c1, c2;
			PushChar();
			do {c2 = PushChar(); c1 = PopChar();} while(!((c1 == '*' && c2 == '/') || c1 == EOS));
			PopChar();
			return PeekChar();
		}

		return m_queue.GetHead();
	}

	int InputStream::GetChar()
	{
		if(m_queue.GetCount() < 2) PeekChar();
		return PopChar();
	}

	int InputStream::SkipWhiteSpace(LPCWSTR morechars)
	{
		int c = PeekChar();
		for(; IsWhiteSpace(c, morechars); c = PeekChar()) 
			GetChar();
		return c;
	}

	// FileInputStream

	FileInputStream::FileInputStream(const TCHAR* fn) 
		: m_file(NULL)
	{
		if(_tfopen_s(&m_file, fn, _T("r")) != 0) ThrowError(_T("cannot open file '%s'"), fn);
	}

	FileInputStream::~FileInputStream()
	{
		if(m_file) {fclose(m_file); m_file = NULL;}
	}

	int FileInputStream::NextByte()
	{
		if(!m_file) ThrowError(_T("file pointer is NULL"));
		return fgetc(m_file);
	}

	// MemoryInputStream

	MemoryInputStream::MemoryInputStream(BYTE* pBytes, int len, bool fCopy, bool fFree)
		: m_pBytes(NULL)
		, m_pos(0)
		, m_len(len)
	{
		if(fCopy)
		{
			m_pBytes = new BYTE[len];
			if(m_pBytes) memcpy(m_pBytes, pBytes, len);
			m_fFree = true;
		}
		else
		{
			m_pBytes = pBytes;
			m_fFree = fFree;
		}

		if(!m_pBytes) ThrowError(_T("memory stream pointer is NULL"));
	}

	MemoryInputStream::~MemoryInputStream()
	{
		if(m_fFree) delete [] m_pBytes;
		m_pBytes = NULL;
	}

	int MemoryInputStream::NextByte()
	{
		if(!m_pBytes) ThrowError(_T("memory stream pointer is NULL"));
		if(m_pos >= m_len) return Stream::EOS;
		return (int)m_pBytes[m_pos++];
	}

	// WCharInputStream
	
	WCharInputStream::WCharInputStream(CStringW str)
		: m_str(str)
		, m_pos(0)
	{
		m_encoding = Stream::wchar; // HACK: it should return real bytes from NextByte (two per wchar_t), but this way it's a lot more simple...
	}

	int WCharInputStream::NextByte()
	{
		if(m_pos >= m_str.GetLength()) return Stream::EOS;
		return m_str[m_pos++];
	}

	// OutputStream

	OutputStream::OutputStream(encoding_t e)
	{
		m_encoding = e;
		m_bof = true;
	}

	OutputStream::~OutputStream()
	{
	}

	void OutputStream::PutChar(WCHAR c)
	{
		if(m_bof)
		{
			m_bof = false;

			switch(m_encoding)
			{
			case utf8:
			case utf16le: 
			case utf16be:
				PutChar(0xfeff);
				break;
			}
		}

		switch(m_encoding)
		{
		case utf8: 
			if(0 <= c && c < 0x80) // 0xxxxxxx
			{
				NextByte(c);
			}
			else if(0x80 <= c && c < 0x800) // 110xxxxx 10xxxxxx
			{
				NextByte(0xc0 | ((c<<2)&0x1f));
				NextByte(0x80 | ((c<<0)&0x3f));
			}
			else if(0x800 <= c && c < 0xFFFF) // 1110xxxx 10xxxxxx 10xxxxxx
			{
				NextByte(0xe0 | ((c<<4)&0x0f));
				NextByte(0x80 | ((c<<2)&0x3f));
				NextByte(0x80 | ((c<<0)&0x3f));
			}
			else
			{
				NextByte('?');
			}
			break;
		case utf16le:
			NextByte(c & 0xff);
			NextByte((c >> 8) & 0xff);
			break;
		case utf16be: 
			NextByte((c >> 8) & 0xff);
			NextByte(c & 0xff);
			break;
		case wchar:
			NextByte(c);
			break;
		}
	}

	void OutputStream::PutString(LPCWSTR fmt, ...)
	{
		CStringW str;

		va_list args;
		va_start(args, fmt);
		int len = _vscwprintf(fmt, args) + 1;
		if(len > 0) vswprintf_s(str.GetBufferSetLength(len), len, fmt, args);
		va_end(args);

		LPCWSTR s = str;
		while(*s) PutChar(*s++);
	}

	// WCharOutputStream

	WCharOutputStream::WCharOutputStream()
		: OutputStream(wchar)
	{
	}

	void WCharOutputStream::NextByte(int b)
	{
		m_str += (WCHAR)b;
	}

	// DebugOutputStream

	DebugOutputStream::DebugOutputStream()
		: OutputStream(wchar)
	{
	}

	DebugOutputStream::~DebugOutputStream()
	{
		TRACE(_T("%s\n"), m_str);
	}

	void DebugOutputStream::NextByte(int b)
	{
		if(b == '\n') {TRACE(_T("%s\n"), m_str); m_str.Empty();}
		else if(b != '\r') m_str += (WCHAR)b;
	}
}