//-----------------------------------------------------------------------------
// AScript codecs.basic module
//-----------------------------------------------------------------------------
#include "Module_codecs_basic.h"

AScript_BeginModule(codecs_basic)

//-----------------------------------------------------------------------------
// USASCII
//-----------------------------------------------------------------------------
Codec::Result Codec_Encoder_USASCII::FeedChar(char ch, char &chConv)
{
	// acceptable character code is between 0x00 and 0x7f
	if (ch & 0x80) return RESULT_Error;
	if (IsProcessEOL() && ch == '\n') {
		StoreChar('\n');
		chConv = '\r';
	} else {
		chConv = ch;
	}
	return RESULT_Complete;
}

Codec::Result Codec_Decoder_USASCII::FeedChar(char ch, char &chConv)
{
	// acceptable character code is between 0x00 and 0x7f
	if (ch & 0x80) return RESULT_Error;
	if (IsProcessEOL() && ch == '\r') return RESULT_None;
	chConv = ch;
	return RESULT_Complete;
}

AScript_ImplementCodecFactory(USASCII, "us-ascii")

//-----------------------------------------------------------------------------
// UTF8
//-----------------------------------------------------------------------------
Codec::Result Codec_Encoder_UTF8::FeedChar(char ch, char &chConv)
{
	if (IsProcessEOL() && ch == '\n') {
		StoreChar('\n');
		chConv = '\r';
		return RESULT_Complete;
	}
	if (_cntTrails > 0) {
		if ((ch & 0xc0) != 0x80) return RESULT_Error;
		_cntTrails--;
	} else if ((ch & 0x80) == 0x00) {
		// nothing to do
	} else if ((ch & 0xe0) == 0xc0) {
		_cntTrails = 1;
	} else if ((ch & 0xf0) == 0xe0) {
		_cntTrails = 2;
	} else if ((ch & 0xf8) == 0xf0) {
		_cntTrails = 3;
	} else if ((ch & 0xfc) == 0xf8) {
		_cntTrails = 4;
	} else if ((ch & 0xfe) == 0xfc) {
		_cntTrails = 5;
	} else {
		return RESULT_Error;
	}
	chConv = ch;
	return RESULT_Complete;
}

Codec::Result Codec_Decoder_UTF8::FeedChar(char ch, char &chConv)
{
	if (IsProcessEOL() && ch == '\r') return RESULT_None;
	if (_cntTrails > 0) {
		if ((ch & 0xc0) != 0x80) return RESULT_Error;
		_cntTrails--;
	} else if ((ch & 0x80) == 0x00) {
		// nothing to do
	} else if ((ch & 0xe0) == 0xc0) {
		_cntTrails = 1;
	} else if ((ch & 0xf0) == 0xe0) {
		_cntTrails = 2;
	} else if ((ch & 0xf8) == 0xf0) {
		_cntTrails = 3;
	} else if ((ch & 0xfc) == 0xf8) {
		_cntTrails = 4;
	} else if ((ch & 0xfe) == 0xfc) {
		_cntTrails = 5;
	} else {
		return RESULT_Error;
	}
	chConv = ch;
	return RESULT_Complete;
}

AScript_ImplementCodecFactory(UTF8, "utf-8")

//-----------------------------------------------------------------------------
// UTF16LE
//-----------------------------------------------------------------------------
Codec::Result Codec_Encoder_UTF16LE::FeedUTF32(unsigned long codeUTF32, char &chConv)
{
	if (IsProcessEOL() && codeUTF32 == '\n') {
		StoreChar('\0');
		StoreChar('\n');
		StoreChar('\0');
		chConv = '\r';
	} else {
		StoreChar(static_cast<char>((codeUTF32 >> 8) & 0xff));
		chConv = static_cast<char>((codeUTF32 >> 0) & 0xff);
	}
	return RESULT_Complete;
}

Codec::Result Codec_Decoder_UTF16LE::FeedChar(char ch, char &chConv)
{
	if (_firstByteFlag) {
		_firstByteFlag = false;
		_codeUTF32 = static_cast<unsigned char>(ch);
		return RESULT_None;
	} else {
		_firstByteFlag = true;
		_codeUTF32 |=
			(static_cast<unsigned long>(static_cast<unsigned char>(ch)) << 8);
		if (IsProcessEOL() && _codeUTF32 == '\r') return RESULT_None;
		return FeedUTF32(_codeUTF32, chConv);
	}
}

AScript_ImplementCodecFactory(UTF16LE, "utf-16")

//-----------------------------------------------------------------------------
// Base64
//-----------------------------------------------------------------------------
const char Codec_Encoder_Base64::_chars[] =
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

Codec::Result Codec_Encoder_Base64::FeedChar(char ch, char &chConv)
{
	_buff.push_back(ch);
	if (_buff.size() < 3) return RESULT_None;
	unsigned long accum = static_cast<unsigned char >(_buff[0]);
	accum = (accum << 8) | static_cast<unsigned char >(_buff[1]);
	accum = (accum << 8) | static_cast<unsigned char >(_buff[2]);
	StoreChar(_chars[accum & 0x3f]); accum >>= 6;
	StoreChar(_chars[accum & 0x3f]); accum >>= 6;
	StoreChar(_chars[accum & 0x3f]); accum >>= 6;
	chConv = _chars[accum & 0x3f];
	_buff.clear();
	return RESULT_Complete;
}

Codec::Result Codec_Encoder_Base64::Flush(char &chConv)
{
	size_t size = _buff.size();
	if (size == 0) return RESULT_None;
	while (_buff.size() < 3) _buff.push_back(0x00);
	unsigned long accum = static_cast<unsigned char >(_buff[0]);
	accum = (accum << 8) | static_cast<unsigned char >(_buff[1]);
	accum = (accum << 8) | static_cast<unsigned char >(_buff[2]);
	StoreChar('='); accum >>= 6;
	StoreChar((size == 1)? '=' : _chars[accum & 0x3f]); accum >>= 6;
	StoreChar(_chars[accum & 0x3f]); accum >>= 6;
	chConv = _chars[accum & 0x3f];
	_buff.clear();
	return RESULT_Complete;
}

Codec::Result Codec_Decoder_Base64::FeedChar(char ch, char &chConv)
{
	if ('A' <= ch && ch <= 'Z') {
		_accum = (_accum << 6) | (ch - 'A');
	} else if ('a' <= ch && ch <= 'z') {
		_accum = (_accum << 6) | (ch - 'a' + 26);
	} else if ('0' <= ch && ch <= '9') {
		_accum = (_accum << 6) | (ch - '0' + 52);
	} else if (ch == '+') {
		_accum = (_accum << 6) | 62;
	} else if (ch == '/') {
		_accum = (_accum << 6) | 63;
	} else if (ch == '=') {
		_nInvalid++;
		_accum = (_accum << 6);
	} else if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') {
		return RESULT_None;
	} else {
		return RESULT_Error;
	}
	_nChars++;
	if (_nChars < 4) return RESULT_None;
	if (_nInvalid == 0) {
		StoreChar(static_cast<unsigned char>(_accum & 0xff)); _accum >>= 8;
		StoreChar(static_cast<unsigned char>(_accum & 0xff)); _accum >>= 8;
		chConv = static_cast<unsigned char>(_accum & 0xff);
	} else if (_nInvalid == 1) {
		_accum >>= 8;
		StoreChar(static_cast<unsigned char>(_accum & 0xff)); _accum >>= 8;
		chConv = static_cast<unsigned char>(_accum & 0xff);
	} else if (_nInvalid == 2) {
		_accum >>= 8;
		_accum >>= 8;
		chConv = static_cast<unsigned char>(_accum & 0xff);
	} else {
		_nChars = 0, _nInvalid = 0, _accum = 0;
		return RESULT_None;
	}
	_nChars = 0, _nInvalid = 0, _accum = 0;
	return RESULT_Complete;
}

AScript_ImplementCodecFactory(Base64, "base64")

//-----------------------------------------------------------------------------
// AScript module functions: codecs.basic
//-----------------------------------------------------------------------------
// Module entry
AScript_ModuleEntry()
{
	AScript_RegisterCodecFactory(USASCII);
	AScript_RegisterCodecFactory(UTF8);
	AScript_RegisterCodecFactory(UTF16LE);
	AScript_RegisterCodecFactory(Base64);
}

AScript_ModuleTerminate()
{
}

AScript_EndModule(codecs_basic, basic)

AScript_RegisterModule(codecs_basic)
