/*	Copyright  (c)	Günter Woigk 2016 - 2016
					mailto:kio@little-bat.de

	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.

	Permission to use, copy, modify, distribute, and sell this software and
	its documentation for any purpose is hereby granted without fee, provided
	that the above copyright notice appear in all copies and that both that
	copyright notice and this permission notice appear in supporting
	documentation, and that the name of the copyright holder not be used
	in advertising or publicity pertaining to distribution of the software
	without specific, written prior permission.  The copyright holder makes no
	representations about the suitability of this software for any purpose.
	It is provided "as is" without express or implied warranty.

	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
	PERFORMANCE OF THIS SOFTWARE.
*/

#define SAFE 3
#define LOG 1
#include "kio/kio.h"
#include "Stream.h"
#include <QMutex>


static QMutex iowrite_mutex;


/*	handle ACK block:
	CTL + ACK has already been read from buffer
*/
void Stream::handle_ack() throw(uint)
{
	XXXASSERT(transmission_mode == ec_transmission);

	uint blk = readByte();
	uint crc = readByte();
	TODO();
}

/*	handle NAK block:
	CTL + NAK has already been read from buffer
*/
void Stream::handle_nak() throw(uint)
{
	XXXASSERT(transmission_mode == ec_transmission);

	uint blk = readByte();
	uint crc = readByte();
	TODO();
}

/*	flush sio input
*/
void Stream::flushSio()
{
	if(selected_sio < 0) return;

	i = e = 0;
	while(ioport->read(bu,sizeof(bu)))
	{
		usleep(2000);
	}
}

/*	read more bytes from ioport
	the bytes are appended "as-is" at position e to the buffer bu[].
	may shift the buffer to make room at the end.
	reads at least one byte. (or throw)
*/
void Stream::read_raw()
{
	XXXASSERT(i==e);

	i = e = 0;

	for(;;)
	{
		e = ioport->read(bu,sizeof(bu));
		if(e!=0) return;
		usleep(5000);
	}
}


/*	Get Next Char in Escaped Transmission Mode:
	get next plain char or throw CTL code

	read next char from the unprocessed buffer,
	unescapes CTL, XON and XOFF.
	handles transparently asynchronous ACK and NAK blocks (TODO: skips them only)
	returns byte or throws CTL

	return value:
	$0xx	= regular char

	throw values:
	$1xx	= CTL + byte: command or stream number
	$2xx	= 2 CTLs + byte		// this is an error
	$nxx	= n CTLs + byte		// ""
*/
uint Stream::next_byte() throw(uint)
{
	if(i==e) read_raw();
	uint c = bu[i++];

	if(c != CTL) return c;		// regular char
	if(transmission_mode == plain_transmission) return c;

	return next_ctl();			// will probably throw a CTL code
}

/*	a CTL has been read: get next byte and return plain char or throw CTL code
	bytes in the buffer below i are fixed, so that they only contain the unescaped chars.
	the thrown CTL code (if any) is left in the buffer too.
*/
uint Stream::next_ctl() throw(uint)
{
	XXXASSERT(transmission_mode != plain_transmission);

	if(i==e) read_raw();
	uint c = bu[i++];

	switch(c)
	{
	// escaped regular bytes:
	case esc_CTL:  return CTL;
	case esc_XOFF: return XOFF;
	case esc_XON:  return XON;

	// asynchronous ACK/NAK block:
	case ACK:
		if(transmission_mode != ec_transmission) break;
		handle_ack();
		return next_byte();
	case NAK:
		if(transmission_mode != ec_transmission) break;
		handle_nak();
		return next_byte();

	// multiple CTL codes:
	// CTL CTL esc_XXX				--> CTL XXX
	// CTL CTL ACK|NAK BLK CRC XXX	--> CTL XXX
	case CTL:
		try{ c = next_ctl(); } catch(uint e){ c = e; } break;
	}

	// CTL control code or stream switch:
	throw 0x100 + c;
}


/*	Get N Bytes from ioport:
	get n plain argument bytes or throw premature CTL code
*/
void Stream::readBytes(uchar*  p, uint n) throw(uint)
{
	p--;
	while(n--)
	{
		if(i==e) read_raw();
		uint c = *++p = bu[i++];

		if(c != CTL) continue;		// regular char
		if(transmission_mode == plain_transmission) continue;

		*p = next_ctl();			// may throw
	}
}

/*	read what's available without physically reading more data and without throwing a CTL:
	• handles escaped bytes but breaks on any other CTL code.
	• may return earlier than actually required.
*/
uint Stream::readAvail(uchar* p, uint sz)
{
	p--;
	uint n = 0;

	while(n<sz && i<e)
	{
		uint c = *++p = bu[i++]; n++;
		if(c != CTL) continue;		// regular char
		if(transmission_mode == plain_transmission) continue;

		if(i<e)
			switch(bu[i++])
			{
			case esc_CTL:  *p = CTL;  continue;
			case esc_XON:  *p = XON;  continue;
			case esc_XOFF: *p = XOFF; continue;
			default: i--;
			}

		i--;
		n--;
		break;
	}
	return n;
}

uint Stream::readAvail(uchar* p, uint sz, bool(*test)(uchar))
{
	p--;
	uint n = 0;

	while(n<sz && i<e)
	{
		uint c = *++p = bu[i++]; n++;
		if(c != CTL || transmission_mode == plain_transmission)
		{
			if(test(c)) continue;		// regular char
		}
		else if(i<e)
		{
			switch(bu[i++])
			{
			case esc_CTL:  if(test(CTL))  { *p = CTL;  continue; } else break;
			case esc_XON:  if(test(XON))  { *p = XON;  continue; } else break;
			case esc_XOFF: if(test(XOFF)) { *p = XOFF; continue; } else break;
			default: i--;
			}
		}

		i--;
		n--;
		break;
	}
	return n;
}



/*	Get Next 2-Char Word in Escaped Transmission Mode:
	get next plain 2-char word or throw CTL code
*/
uint Stream::readUWord() throw(uint)
{
	uchar bu[2];
	readBytes(bu,2);
	return peek2X(bu);
}
int Stream::readSWord() throw(uint)
{
	uchar bu[2];
	readBytes(bu,2);
	return (int16)peek2X(bu);
}

/*	Get Next 4-Char Long Word in Escaped Transmission Mode:
	get next plain 4-char long word or throw CTL code
*/
uint Stream::readLong() throw(uint)
{
	uchar bu[4];
	readBytes(bu,4);
	return peek4X(bu);
}




/*	write reply to ioport
	escapes message if required
	switches stream if required
	thread-safe
	if stream is oorange then write to the current istream
*/
void Stream::write(uchar* bu, uint cnt, uint stream)
{
	XXXASSERT(cnt>0);

	QMutexLocker _z(&iowrite_mutex);

	if(transmission_mode == plain_transmission) { ioport->write(bu,cnt); return; }

	uchar* q;
	uchar* e;
	uint   c;

	int n = 0;
	if(software_handshake)  { for(q=bu,e=bu+cnt;q<e;) if((c=*q++)==XON || c==XOFF || c==CTL) n++; }
	else					{ for(q=bu,e=bu+cnt;q<e;) if( *q++ == CTL ) n++; }
	n -= bu[0] == CTL;

	if(stream >= max_streams) stream = istream;
	if(n==0 && stream==ostream) { ioport->write(bu,cnt); return; }

	uchar  zbu[cnt+2+n];
	uchar* z = zbu;

	if(ostream != stream) { *z++ = CTL; *z++ = stream; ostream = stream; }

	q = bu;
	e = bu + cnt;

	if(*q==CTL) *z++ = *q++;

	if(software_handshake)
		while(q<e)
		{
			c = *q++;
			if(c==CTL)  { *z++=CTL; *z++=esc_CTL;  } else
			if(c==XON)  { *z++=CTL; *z++=esc_XON;  } else
			if(c==XOFF) { *z++=CTL; *z++=esc_XOFF; } else *z++ = c;
		}
	else
		while(q<e)
		{
			if((*z++ = *q++) == CTL) *z++ = esc_CTL;
		};

	ioport->write(zbu,z-zbu);
}

void Stream::write(cstr s, uint stream)
{
	write((uchar*)s, strlen(s), stream);
}


void Stream::setPlainTransmission()
{
	transmission_mode  = plain_transmission;
	software_handshake = no;
	ostream = 0;
	istream = 0;
}

void Stream::setEscapedTransmission(bool swhsk, uint max_streams)
{
	transmission_mode  = escaped_transmission;
	software_handshake = swhsk;
	this->max_streams  = max_streams;
}

void Stream::setECTransmission(bool swhsk, uint max_streams, uint ec_windowsize, uint ec_maxblocksize)
{
	transmission_mode  = ec_transmission;
	software_handshake = swhsk;
	this->max_streams  = max_streams;
	this->ec_windowsize   = ec_windowsize;
	this->ec_maxblocksize = ec_maxblocksize;
}

void Stream::reset()
{
	transmission_mode = plain_transmission;
	software_handshake = false;
	max_streams = 1;
	e=i=0;
	ostream=0;
	istream=0;
}










