/*
 * $Id$
 *
    Copyright 2006-2014 Chung, Hyung-Hwan.
    This file is part of QSE.
    QSE is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as 
    published by the Free Software Foundation, either version 3 of 
    the License, or (at your option) any later version.
    QSE 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 Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public 
    License along with QSE. If not, see .
 */
#include 
#include  
#include "mem.h"
#define STATUS_OUTPUT_DYNBUF (1 << 0)
#define STATUS_INPUT_DYNBUF  (1 << 1)
#define STATUS_INPUT_ILLSEQ  (1 << 2)
#define STATUS_INPUT_EOF     (1 << 3)
static int detach_in (qse_tio_t* tio, int fini);
static int detach_out (qse_tio_t* tio, int fini);
qse_tio_t* qse_tio_open (qse_mmgr_t* mmgr, qse_size_t xtnsize, int flags)
{
	qse_tio_t* tio;
	tio = QSE_MMGR_ALLOC (mmgr, QSE_SIZEOF(qse_tio_t) + xtnsize);
	if (tio)
	{
		if (qse_tio_init (tio, mmgr, flags) <= -1)
		{
			QSE_MMGR_FREE (mmgr, tio);
			return QSE_NULL;
		}
		else QSE_MEMSET (QSE_XTN(tio), 0, xtnsize);
	}
	return tio;
}
int qse_tio_close (qse_tio_t* tio)
{
	int n = qse_tio_fini (tio);
	QSE_MMGR_FREE (tio->mmgr, tio);
	return n;
}
int qse_tio_init (qse_tio_t* tio, qse_mmgr_t* mmgr, int flags)
{
	QSE_MEMSET (tio, 0, QSE_SIZEOF(*tio));
	tio->mmgr = mmgr;
	tio->cmgr = qse_getdflcmgr();
	tio->flags = flags;
	/*
	tio->input_func = QSE_NULL;
	tio->input_arg = QSE_NULL;
	tio->output_func = QSE_NULL;
	tio->output_arg = QSE_NULL;
	tio->status = 0;
	tio->inbuf_cur = 0;
	tio->inbuf_len = 0;
	tio->outbuf_len = 0;
	*/
	tio->errnum = QSE_TIO_ENOERR;
	return 0;
}
int qse_tio_fini (qse_tio_t* tio)
{
	int ret = 0;
	qse_tio_flush (tio); /* don't care about the result */
	if (detach_in (tio, 1) <= -1) ret = -1;
	if (detach_out (tio, 1) <= -1) ret = -1;
	return ret;
}
qse_mmgr_t* qse_tio_getmmgr (qse_tio_t* tio)
{
	return tio->mmgr;
}
void* qse_tio_getxtn (qse_tio_t* tio)
{
	return QSE_XTN (tio);
}
qse_tio_errnum_t qse_tio_geterrnum (const qse_tio_t* tio)
{
	return tio->errnum;
}
void qse_tio_seterrnum (qse_tio_t* tio, qse_tio_errnum_t errnum)
{
	tio->errnum = errnum;
}
qse_cmgr_t* qse_tio_getcmgr (qse_tio_t* tio)
{
	return tio->cmgr;
}
void qse_tio_setcmgr (qse_tio_t* tio, qse_cmgr_t* cmgr)
{
	tio->cmgr = cmgr;
}
int qse_tio_attachin (
	qse_tio_t* tio, qse_tio_io_impl_t input,
	qse_mchar_t* bufptr, qse_size_t bufcapa)
{
	qse_mchar_t* xbufptr;
	if (input == QSE_NULL || bufcapa < QSE_TIO_MININBUFCAPA) 
	{
		tio->errnum = QSE_TIO_EINVAL;
		return -1;
	}
	if (qse_tio_detachin(tio) <= -1) return -1;
	QSE_ASSERT (tio->in.fun == QSE_NULL);
	xbufptr = bufptr;
	if (xbufptr == QSE_NULL)
	{
		xbufptr = QSE_MMGR_ALLOC (
			tio->mmgr, QSE_SIZEOF(qse_mchar_t) * bufcapa);
		if (xbufptr == QSE_NULL)
		{
			tio->errnum = QSE_TIO_ENOMEM;
			return -1;	
		}
	}
	tio->errnum = QSE_TIO_ENOERR;
	if (input (tio, QSE_TIO_OPEN, QSE_NULL, 0) <= -1) 
	{
		if (tio->errnum == QSE_TIO_ENOERR) tio->errnum = QSE_TIO_EOTHER;
		if (xbufptr != bufptr) QSE_MMGR_FREE (tio->mmgr, xbufptr);
		return -1;
	}
	/* if i defined tio->io[2] instead of tio->in and tio-out, 
	 * i would be able to shorten code amount. but fields to initialize
	 * are not symmetric between input and output.
	 * so it's just a bit clumsy that i repeat almost the same code
	 * in qse_tio_attachout().
	 */
	tio->in.fun = input;
	tio->in.buf.ptr = xbufptr;
	tio->in.buf.capa = bufcapa;
	tio->status &= ~(STATUS_INPUT_ILLSEQ | STATUS_INPUT_EOF);
	tio->inbuf_cur = 0;
	tio->inbuf_len = 0;
	if (xbufptr != bufptr) tio->status |= STATUS_INPUT_DYNBUF;
	return 0;
}
static int detach_in (qse_tio_t* tio, int fini)
{
	int ret = 0;
	if (tio->in.fun)
	{
		tio->errnum = QSE_TIO_ENOERR;
		if (tio->in.fun (tio, QSE_TIO_CLOSE, QSE_NULL, 0) <= -1) 
		{
			if (tio->errnum == QSE_TIO_ENOERR) tio->errnum = QSE_TIO_EOTHER;
			/* returning with an error here allows you to retry detaching */
			if (!fini) return -1; 
			/* otherwise, you can't retry since the input handler information
			 * is reset below */
			ret = -1; 
		}
		if (tio->status & STATUS_INPUT_DYNBUF) 
		{
			QSE_MMGR_FREE (tio->mmgr, tio->in.buf.ptr);
			tio->status &= ~STATUS_INPUT_DYNBUF;
		}
		tio->in.fun = QSE_NULL;
		tio->in.buf.ptr = QSE_NULL;
		tio->in.buf.capa = 0;
	}
		
	return ret;
}
int qse_tio_detachin (qse_tio_t* tio)
{
	return detach_in (tio, 0);
}
int qse_tio_attachout (
	qse_tio_t* tio, qse_tio_io_impl_t output, 
	qse_mchar_t* bufptr, qse_size_t bufcapa)
{
	qse_mchar_t* xbufptr;
	if (output == QSE_NULL || bufcapa < QSE_TIO_MINOUTBUFCAPA)  
	{
		tio->errnum = QSE_TIO_EINVAL;
		return -1;
	}
	if (qse_tio_detachout(tio) == -1) return -1;
	QSE_ASSERT (tio->out.fun == QSE_NULL);
	xbufptr = bufptr;
	if (xbufptr == QSE_NULL)
	{
		xbufptr = QSE_MMGR_ALLOC (
			tio->mmgr, QSE_SIZEOF(qse_mchar_t) * bufcapa);
		if (xbufptr == QSE_NULL)
		{
			tio->errnum = QSE_TIO_ENOMEM;
			return -1;	
		}
	}
	tio->errnum = QSE_TIO_ENOERR;
	if (output (tio, QSE_TIO_OPEN, QSE_NULL, 0) <= -1) 
	{
		if (tio->errnum == QSE_TIO_ENOERR) tio->errnum = QSE_TIO_EOTHER;
		if (xbufptr != bufptr) QSE_MMGR_FREE (tio->mmgr, xbufptr);
		return -1;
	}
	tio->out.fun = output;
	tio->out.buf.ptr = xbufptr;
	tio->out.buf.capa = bufcapa;
	tio->outbuf_len = 0;
	if (xbufptr != bufptr) tio->status |= STATUS_OUTPUT_DYNBUF;
	return 0;
}
static int detach_out (qse_tio_t* tio, int fini)
{
	int ret = 0;
	if (tio->out.fun)
	{
		qse_tio_flush (tio); /* don't care about the result */
		tio->errnum = QSE_TIO_ENOERR;
		if (tio->out.fun (tio, QSE_TIO_CLOSE, QSE_NULL, 0) <= -1) 
		{
			if (tio->errnum == QSE_TIO_ENOERR) tio->errnum = QSE_TIO_EOTHER;
			/* returning with an error here allows you to retry detaching */
			if (!fini) return -1;
			/* otherwise, you can't retry since the input handler information
			 * is reset below */
			ret = -1;
		}
	
		if (tio->status & STATUS_OUTPUT_DYNBUF) 
		{
			QSE_MMGR_FREE (tio->mmgr, tio->out.buf.ptr);
			tio->status &= ~STATUS_OUTPUT_DYNBUF;
		}
		tio->out.fun = QSE_NULL;
		tio->out.buf.ptr = QSE_NULL;
		tio->out.buf.capa = 0;
	}
		
	return ret;
}
int qse_tio_detachout (qse_tio_t* tio)
{
	return detach_out (tio, 0);
}
qse_ssize_t qse_tio_flush (qse_tio_t* tio)
{
	qse_size_t left, count;
	qse_ssize_t n;
	qse_mchar_t* cur;
	if (tio->out.fun == QSE_NULL)
	{
		tio->errnum = QSE_TIO_ENOUTF;
		return (qse_ssize_t)-1;
	}
	left = tio->outbuf_len;
	cur = tio->out.buf.ptr;
	while (left > 0) 
	{
		tio->errnum = QSE_TIO_ENOERR;
		n = tio->out.fun (tio, QSE_TIO_DATA, cur, left);
		if (n <= -1) 
		{
			if (tio->errnum == QSE_TIO_ENOERR) tio->errnum = QSE_TIO_EOTHER;
			if (cur != tio->out.buf.ptr)
			{
				QSE_MEMCPY (tio->out.buf.ptr, cur, left);
				tio->outbuf_len = left;
			}
			return -1;
		}
		if (n == 0) 
		{
			if (cur != tio->out.buf.ptr)
				QSE_MEMCPY (tio->out.buf.ptr, cur, left);
			break;
		}
	
		left -= n;
		cur += n;
	}
	count = tio->outbuf_len - left;
	tio->outbuf_len = left;
	return (qse_ssize_t)count;
}
void qse_tio_drain (qse_tio_t* tio)
{
	tio->status &= ~(STATUS_INPUT_ILLSEQ | STATUS_INPUT_EOF);
	tio->inbuf_cur = 0;
	tio->inbuf_len = 0;
	tio->outbuf_len = 0;
	tio->errnum = QSE_TIO_ENOERR;
}
/* ------------------------------------------------------------- */
qse_ssize_t qse_tio_readmbs (qse_tio_t* tio, qse_mchar_t* buf, qse_size_t size)
{
	qse_size_t nread;
	qse_ssize_t n;
	/*QSE_ASSERT (tio->in.fun != QSE_NULL);*/
	if (tio->in.fun == QSE_NULL) 
	{
		tio->errnum = QSE_TIO_ENINPF;
		return -1;
	}
	/* note that this function doesn't check if
	 * tio->status is set with STATUS_INPUT_ILLSEQ
	 * since this function can simply return the next
	 * available byte. */
	if (size > QSE_TYPE_MAX(qse_ssize_t)) size = QSE_TYPE_MAX(qse_ssize_t);
	nread = 0;
	while (nread < size)
	{
		if (tio->inbuf_cur >= tio->inbuf_len) 
		{
			tio->errnum = QSE_TIO_ENOERR;
			n = tio->in.fun (
				tio, QSE_TIO_DATA, 
				tio->in.buf.ptr, tio->in.buf.capa);
			if (n == 0) break;
			if (n <= -1) 
			{
				if (tio->errnum == QSE_TIO_ENOERR) tio->errnum = QSE_TIO_EOTHER;
				return -1;
			}
			tio->inbuf_cur = 0;
			tio->inbuf_len = (qse_size_t)n;
		}
		do
		{
			buf[nread] = tio->in.buf.ptr[tio->inbuf_cur++];
			/* TODO: support a different line terminator */
			if (buf[nread++] == QSE_MT('\n')) goto done;
		}
		while (tio->inbuf_cur < tio->inbuf_len && nread < size);
	}
done:
	return nread;
}
static QSE_INLINE qse_ssize_t tio_read_widechars (
	qse_tio_t* tio, qse_wchar_t* buf, qse_size_t bufsize)
{
	qse_size_t mlen, wlen;
	qse_ssize_t n;
	int x;
	if (tio->inbuf_cur >= tio->inbuf_len) 
	{
		tio->inbuf_cur = 0;
		tio->inbuf_len = 0;
	getc_conv:
		if (tio->status & STATUS_INPUT_EOF) n = 0;
		else
		{
			tio->errnum = QSE_TIO_ENOERR;
			n = tio->in.fun (
				tio, QSE_TIO_DATA,
				&tio->in.buf.ptr[tio->inbuf_len], 
				tio->in.buf.capa - tio->inbuf_len);
		}
		if (n == 0) 
		{
			tio->status |= STATUS_INPUT_EOF;
			if (tio->inbuf_cur < tio->inbuf_len)
			{
				/* no more input from the underlying input handler.
				 * but some incomplete bytes in the buffer. */
				if (tio->flags & QSE_TIO_IGNOREMBWCERR) 
				{
					/* tread them as illegal sequence */
					goto ignore_illseq;
				}
				else
				{
					tio->errnum = QSE_TIO_EICSEQ;
					return -1;
				}
			}
			return 0;
		}
		if (n <= -1) 
		{
			if (tio->errnum == QSE_TIO_ENOERR) tio->errnum = QSE_TIO_EOTHER;
			return -1;
		}
		tio->inbuf_len += n;
	}
	mlen = tio->inbuf_len - tio->inbuf_cur;
	wlen = bufsize;
	x = qse_mbsntowcsnuptowithcmgr (
		&tio->in.buf.ptr[tio->inbuf_cur],
		&mlen, buf, &wlen, QSE_WT('\n'), tio->cmgr);
	tio->inbuf_cur += mlen;
	if (x == -3)
	{
		/* incomplete sequence */
		if (wlen <= 0)
		{
			/* not even a single character was handled. 
			 * shift bytes in the buffer to the head. */
			QSE_ASSERT (mlen <= 0);
			tio->inbuf_len = tio->inbuf_len - tio->inbuf_cur;
			QSE_MEMCPY (&tio->in.buf.ptr[0], 
			            &tio->in.buf.ptr[tio->inbuf_cur],
			            tio->inbuf_len * QSE_SIZEOF(tio->in.buf.ptr[0]));
			tio->inbuf_cur = 0;
			goto getc_conv; /* and read more */
		}
		/* get going if some characters are handled */
	}
	else if (x == -2)
	{
		/* buffer not large enough */
		QSE_ASSERT (wlen > 0);
		
		/* the wide-character buffer is not just large enough to
		 * hold the entire conversion result. lets's go on so long as 
		 * 1 wide-character is produced though it may be inefficient.
		 */
	}
	else if (x <= -1)
	{
		/* illegal sequence */
		if (tio->flags & QSE_TIO_IGNOREMBWCERR)
		{
		ignore_illseq:
			tio->inbuf_cur++; /* skip one byte */
			buf[wlen++] = QSE_WT('?');
		}
		else if (wlen <= 0)
		{
			tio->errnum = QSE_TIO_EILSEQ;
			return -1;
		}
		else
		{
			/* some characters are already handled.
			 * mark that an illegal sequence encountered
			 * and carry on. */
			tio->status |= STATUS_INPUT_ILLSEQ;
		}
	}
	
	return wlen;
}
qse_ssize_t qse_tio_readwcs (qse_tio_t* tio, qse_wchar_t* buf, qse_size_t size)
{
	qse_size_t nread = 0;
	qse_ssize_t n;
	/*QSE_ASSERT (tio->in.fun != QSE_NULL);*/
	if (tio->in.fun == QSE_NULL) 
	{
		tio->errnum = QSE_TIO_ENINPF;
		return -1;
	}
	if (size > QSE_TYPE_MAX(qse_ssize_t)) size = QSE_TYPE_MAX(qse_ssize_t);
	while (nread < size)
	{
		if (tio->status & STATUS_INPUT_ILLSEQ) 
		{
			tio->status &= ~STATUS_INPUT_ILLSEQ;
			tio->errnum = QSE_TIO_EILSEQ;
			return -1;
		}
		
		n = tio_read_widechars (tio, &buf[nread], size - nread);
		if (n == 0) break;
		if (n <= -1) return -1;
		nread += n;
		if (buf[nread-1] == QSE_WT('\n')) break;
	}
	return nread;
}
/* ------------------------------------------------------------- */
qse_ssize_t qse_tio_writembs (
	qse_tio_t* tio, const qse_mchar_t* mptr, qse_size_t mlen)
{
	if (tio->outbuf_len >= tio->out.buf.capa) 
	{
		/* maybe, previous flush operation has failed a few 
		 * times previously. so the buffer is full.
		 */
		tio->errnum = QSE_TIO_ENOSPC;	
		return -1;
	}
	if (mlen == (qse_size_t)-1)
	{
		qse_size_t pos = 0;
		if (tio->flags & QSE_TIO_NOAUTOFLUSH)
		{
			while (mptr[pos] != QSE_MT('\0')) 
			{
				tio->out.buf.ptr[tio->outbuf_len++] = mptr[pos++];
				if (tio->outbuf_len >= tio->out.buf.capa &&
				    qse_tio_flush (tio) <= -1) return -1;
				if (pos >= QSE_TYPE_MAX(qse_ssize_t)) break;
			}
		}
		else
		{
			int nl = 0;
			while (mptr[pos] != QSE_MT('\0')) 
			{
				tio->out.buf.ptr[tio->outbuf_len++] = mptr[pos];
				if (tio->outbuf_len >= tio->out.buf.capa)
				{
					if (qse_tio_flush (tio) <= -1) return -1;
					nl = 0;
				}
				else if (mptr[pos] == QSE_T('\n')) nl = 1; 
				/* TODO: different line terminator */
				if (++pos >= QSE_TYPE_MAX(qse_ssize_t)) break;
			}
			if (nl && qse_tio_flush(tio) <= -1) return -1;
		}
		return pos;
	}
	else
	{	
		const qse_mchar_t* xptr, * xend;
		qse_size_t capa;
		int nl = 0;
		/* adjust mlen for the type difference between the parameter
		 * and the return value */
		if (mlen > QSE_TYPE_MAX(qse_ssize_t)) mlen = QSE_TYPE_MAX(qse_ssize_t);
		xptr = mptr;
		/* handle the parts that can't fit into the internal buffer */
		while (mlen >= (capa = tio->out.buf.capa - tio->outbuf_len))
		{
			for (xend = xptr + capa; xptr < xend; xptr++)
				tio->out.buf.ptr[tio->outbuf_len++] = *xptr;
			if (qse_tio_flush (tio) <= -1) return -1;
			mlen -= capa;
		}
		if (tio->flags & QSE_TIO_NOAUTOFLUSH)
		{
			/* handle the last part that can fit into the internal buffer */
			for (xend = xptr + mlen; xptr < xend; xptr++)
				tio->out.buf.ptr[tio->outbuf_len++] = *xptr;
		}
		else
		{
			/* handle the last part that can fit into the internal buffer */
			for (xend = xptr + mlen; xptr < xend; xptr++)
			{
				/* TODO: support different line terminating characeter */
				if (*xptr == QSE_MT('\n'))
				{
					nl = 1; 
					break;
				}
				tio->out.buf.ptr[tio->outbuf_len++] = *xptr;
			}
			/* continue copying without checking for nl */
			while (xptr < xend) tio->out.buf.ptr[tio->outbuf_len++] = *xptr++;
		}
		/* if the last part contains a new line, flush the internal
		 * buffer. note that this flushes characters after nl also.*/
		if (nl && qse_tio_flush (tio) <= -1) return -1;
		/* returns the number multi-byte characters handled */
		return xptr - mptr;
	}
}
qse_ssize_t qse_tio_writewcs (
	qse_tio_t* tio, const qse_wchar_t* wptr, qse_size_t wlen)
{
	qse_size_t capa, wcnt, mcnt, xwlen;
	int n, nl = 0;
	if (tio->outbuf_len >= tio->out.buf.capa) 
	{
		/* maybe, previous flush operation has failed a few 
		 * times previously. so the buffer is full. */
		tio->errnum = QSE_TIO_ENOSPC;	
		return -1;
	}
	if (wlen == (qse_size_t)-1) wlen = qse_wcslen(wptr);
	if (wlen > QSE_TYPE_MAX(qse_ssize_t)) wlen = QSE_TYPE_MAX(qse_ssize_t);
	xwlen = wlen;
	while (xwlen > 0)
	{
		capa = tio->out.buf.capa - tio->outbuf_len;
		wcnt = xwlen; mcnt = capa;
		n = qse_wcsntombsnwithcmgr (
			wptr, &wcnt, &tio->out.buf.ptr[tio->outbuf_len], &mcnt, tio->cmgr);
		tio->outbuf_len += mcnt;
		if (n == -2)
		{
			/* the buffer is not large enough to 
			 * convert more. so flush now and continue.
			 * note that the buffer may not be full though 
			 * it is not large enough in this case */
			if (qse_tio_flush (tio) <= -1) return -1;
			nl = 0;
		}
		else 
		{
			if (tio->outbuf_len >= tio->out.buf.capa)
			{
				/* flush the full buffer regardless of conversion
				 * result. */
				if (qse_tio_flush (tio) <= -1) return -1;
				nl = 0;		  
			}
			if (n <= -1)
			{
				/* an invalid wide-character is encountered. */
				if (tio->flags & QSE_TIO_IGNOREMBWCERR)
				{
					/* insert a question mark for an illegal 
					 * character. */
					QSE_ASSERT (tio->outbuf_len < tio->out.buf.capa);
					tio->out.buf.ptr[tio->outbuf_len++] = QSE_MT('?');
					wcnt++; /* skip this illegal character */
					/* don't need to increment mcnt since
					 * it's not used below */
				}
				else
				{
					tio->errnum = QSE_TIO_EILCHR;
					return -1;
				}
			}
			else
			{
				if (!(tio->flags & QSE_TIO_NOAUTOFLUSH) && !nl)
				{
					/* checking for a newline this way looks damn ugly.
					 * TODO: how can i do this more elegantly? */
					qse_size_t i = wcnt;
					while (i > 0)
					{
						/* scan backward assuming a line terminator
						 * is typically at the back */
						if (wptr[--i] == QSE_WT('\n'))  
						{
							/* TOOD: differetn line terminator */
							nl = 1; 
							break;
						}
					}
				}
			}
		}
		wptr += wcnt; xwlen -= wcnt;
	}
	if (nl && qse_tio_flush (tio) <= -1) return -1;
	return wlen;
}