#include "stdafx.h"

#include "FileIO.hpp"

#include <assert.h>


CFileIOException::CFileIOException(DWORD errorcode)
	: errorcode_(errorcode)
{
}

DWORD CFileIOException::GetErrorCode() const
{
	return errorcode_;
}

////////////////////////

CFileIO::CFileIO(const tstring& fname)
	: fname_(fname)
	, hFile_(INVALID_HANDLE_VALUE)
	, eof_(false)
	, unicode_(false)
	, wrote_(false)
	, buf_(4096)
{
	Init();
}

CFileIO::~CFileIO() throw()
{
	Close();
}

void CFileIO::Init() throw()
{
	unicode_ = false;
	wrote_ = false;
	eof_ = false;
	pt_ = endpt_ = buf_.end();
	pch_ = (rawbuffer::value_type) -1;
}

bool CFileIO::Open(bool save)
{
	assert(hFile_ == INVALID_HANDLE_VALUE && "すでにオープンしています。");

	hFile_ = ::CreateFile(
		fname_.c_str(),
		(save ? GENERIC_WRITE : 0) | GENERIC_READ,
		save ? 0 : FILE_SHARE_READ,
		NULL,
		save ? CREATE_ALWAYS : OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);

	if (save && hFile_ == INVALID_HANDLE_VALUE) {
		// 書き込み時、ファイルにアクセスできない場合は例外とする。
		// (読み込み時はファイルにアクセスできない場合は単にfalseを返す。)
		DWORD err = GetLastError();
		throw CFileIOException(err);
	}

	Init();

	if (hFile_ != INVALID_HANDLE_VALUE) {
		if ( !save) {
			// UNICODEチェック
			CheckUnicode();
		}
		else {
			// ファイルサイズを0に設定
			::SetFilePointer(hFile_, 0, NULL, FILE_BEGIN);
			::SetEndOfFile(hFile_);
		}
	}

	return hFile_ != INVALID_HANDLE_VALUE;
}

void CFileIO::Close() throw()
{
	if (hFile_ != INVALID_HANDLE_VALUE) {
		::CloseHandle(hFile_);
		hFile_ = INVALID_HANDLE_VALUE;
	}
}

void CFileIO::CheckUnicode()
{
	assert (hFile_ != INVALID_HANDLE_VALUE && "オープンされていません。");

	::SetFilePointer(hFile_, 0, 0, FILE_BEGIN);
	
	DWORD rd;
	unsigned char bom[2] = {0};

	if ( !ReadFile(hFile_, bom, 2, &rd, NULL)) {
		DWORD err = GetLastError();
		throw CFileIOException(err);
	}
	
	if (rd == 2 && bom[0] == 0xff && bom[1] == 0xfe) {
		// UNICODEのBOMを検出
		// ファイルポインタは現在位置のまま。
		unicode_ = true;
	}
	else {
		unicode_ = false;
		// ファイルポインタを先頭に戻す
		::SetFilePointer(hFile_, 0, NULL, FILE_BEGIN);
	}
}

bool CFileIO::IsUnicode() const
{
	assert(hFile_ != INVALID_HANDLE_VALUE && "ファイルがオープンされていません。");
	return unicode_;
}

void CFileIO::PutText(tstring& line)
{
	assert(hFile_ != INVALID_HANDLE_VALUE && "ファイルが開かれていません。");

	if (!wrote_) {
#ifdef _UNICODE
		// tstringがUNICODEであればBOMをつける。
		unsigned char bom[] = {0xff, 0xfe};
		DWORD wd;
		if (!WriteFile(hFile_, &bom[0], 2, &wd, NULL)) {
			DWORD err = GetLastError();
			throw CFileIOException(err);
		}
#endif
		wrote_ = true;
	}

	int len = line.length();
	DWORD wd;
	if ( !::WriteFile(hFile_, line.c_str(), len * sizeof(TCHAR), &wd, NULL)) {
		DWORD err = GetLastError();
		throw CFileIOException(err);
	}
}

bool CFileIO::GetText(tstring& line)
{
	assert(hFile_ != INVALID_HANDLE_VALUE && "ファイルが開かれていません。");

	line.clear();

	// lineバッファ
	rawbuffer linebuf;
	std::back_insert_iterator<rawbuffer> bi = std::back_inserter(linebuf);

	// 前回バッファの残りがある場合、まず、それを転送する。
	bool lineTerm = false;
	while (!lineTerm) {
		while (pt_ != endpt_) {
			rawbuffer::value_type ch = *pt_++;
			rawbuffer::value_type pch = pch_;
			*bi++ = ch;
			pch_ = ch;
			if ((unicode_ && pch == '\n' && ch == 0) || (!unicode_ && ch == '\n')) {
				// UNICODEの場合は 0x0a 0x00 で改行、
				// MBCSの場合は0x0aで改行
				lineTerm = true;
				break;
			}
		}
		if (eof_ && pt_ == endpt_) {
			// すでにファイルが終端に達していてバッファにも残りがなければ
			// これ以上は読み込まない。
			break;
		}

		if ( !eof_ && pt_ == endpt_) {
			// まだファイルの終端に達しておらず、バッファが空になったら
			// 新たにバッファを詰め直す。
			pt_ = buf_.begin();
			endpt_ = pt_;

			DWORD rd = 0;
			DWORD siz = buf_.size();
			if (!::ReadFile(hFile_, &buf_[0], siz, &rd, NULL)) {
				DWORD err = GetLastError();
				throw CFileIOException(err);
			}
			endpt_ += (size_t) rd;
			if (rd == 0) {
				eof_ = true;
			}
		}
	}

#ifdef _UNICODE
	if (unicode_) {
		std::back_insert_iterator<tstring> outpt = std::back_inserter(line);
		WCHAR wchar = 0;
		bool sw = false;
		for (rawbuffer::iterator st = linebuf.begin();
			st != linebuf.end();
			++st) {
			rawbuffer::value_type ch = *st;
			if (!sw) {
				wchar = ((WCHAR) ch) & 0xff;
			}
			else {
				wchar |= (((WCHAR) ch) << 8) & 0xff00;
			}
			if (sw) {
				if (wchar != '\r' && wchar != '\n') {
					*outpt++ = wchar;
				}
				wchar = 0;
			}
			sw = !sw;
		}

	}
	else {
		if ( !linebuf.empty()) {
			int convbuf_siz = linebuf.size();
			std::vector<WCHAR> convbuf(convbuf_siz);
			int len = MultiByteToWideChar(
				CP_ACP,
				MB_PRECOMPOSED | MB_ERR_INVALID_CHARS,
				(LPCSTR) &linebuf[0],
				linebuf.size(),
				(LPWSTR) &convbuf[0],
				convbuf_siz);
			if (len == 0) {
				DWORD err = GetLastError();
				throw CFileIOException(err);
			}
			if (len > 0) {
				std::back_insert_iterator<tstring> outpt = std::back_inserter(line);
				for (std::vector<WCHAR>::iterator st = convbuf.begin();
					st != convbuf.end() && len > 0;
					++st, len--) {
					WCHAR ch = *st;
					if (ch != '\r' && ch != '\n') {
						*outpt++ = ch;
					}
				}
			}
		}
	}
#else
	if ( !unicode_) {
		std::back_insert_iterator<tstring> outpt = std::back_inserter(line);
		for (rawbuffer::iterator st = linebuf.begin();
			st != linebuf.end();
			++st) {
			tstring::value_type ch = *st;
			if (ch != '\r' && ch != '\n') {
				*outpt++ = ch;
			}
		}
	}
	else {
		if ( !linebuf.empty()) {
			BOOL charerr = FALSE;
			int convbuf_siz = linebuf.size();
			std::vector<CHAR> convbuf(convbuf_siz);
			int len = WideCharToMultiByte(
				CP_ACP,
				WC_COMPOSITECHECK | WC_DISCARDNS | WC_SEPCHARS | WC_DEFAULTCHAR,
				(LPCWSTR) &linebuf[0],
				convbuf_siz / 2,
				(LPSTR) &convbuf[0],
				convbuf_siz,
				NULL,
				&charerr);
			if (len == 0 || charerr) {
				DWORD err = GetLastError();
				if (err == 0 && charerr) {
					// 文字変換に失敗した場合のエラー(代替用)
					err = ERROR_INVALID_PARAMETER;
				}
				throw CFileIOException(err);
			}
			if (len > 0) {
				std::back_insert_iterator<tstring> outpt = std::back_inserter(line);
				for (std::vector<CHAR>::iterator st = convbuf.begin();
					st != convbuf.end() && len > 0;
					++st, len--) {
					CHAR ch = *st;
					if (ch != '\r' && ch != '\n') {
						*outpt++ = ch;
					}
				}
			}
		}
	}
#endif
	// 返す内容が空でないか、まだファイルの終端に達していないか、
	// まだバッファに残りがある場合はtrue。
	// 返す結果がなく、且つ、終端に達していて、且つ、バッファも空の場合はfalse
	return !line.empty() || !eof_ || (pt_ != endpt_);
}


