/*
    Copyright (c) 2006-2020 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "hawk-prv.h"

enum io_mask_t
{
	IO_MASK_READ  = 0x0100,
	IO_MASK_WRITE = 0x0200,
	IO_MASK_RDWR  = 0x0400,
	IO_MASK_CLEAR = 0x00FF
};

static hawk_rio_type_t in_type_map[] =
{
	/* the order should match the order of the
	 * HAWK_IN_XXX values in tree.h */
	HAWK_RIO_PIPE,
	HAWK_RIO_PIPE,
	HAWK_RIO_FILE,
	HAWK_RIO_CONSOLE
};

static int in_mode_map[] =
{
	/* the order should match the order of the
	 * HAWK_IN_XXX values in tree.h */
	HAWK_RIO_PIPE_READ,
	HAWK_RIO_PIPE_RW,
	HAWK_RIO_FILE_READ,
	HAWK_RIO_CONSOLE_READ
};

static int in_mask_map[] =
{
	IO_MASK_READ,
	IO_MASK_RDWR,
	IO_MASK_READ,
	IO_MASK_READ
};

static hawk_rio_type_t out_type_map[] =
{
	/* the order should match the order of the
	 * HAWK_OUT_XXX values in tree.h */
	HAWK_RIO_PIPE,
	HAWK_RIO_PIPE,
	HAWK_RIO_FILE,
	HAWK_RIO_FILE,
	HAWK_RIO_CONSOLE
};

static int out_mode_map[] =
{
	/* the order should match the order of the
	 * HAWK_OUT_XXX values in tree.h */
	HAWK_RIO_PIPE_WRITE,
	HAWK_RIO_PIPE_RW,
	HAWK_RIO_FILE_WRITE,
	HAWK_RIO_FILE_APPEND,
	HAWK_RIO_CONSOLE_WRITE
};

static int out_mask_map[] =
{
	IO_MASK_WRITE,
	IO_MASK_RDWR,
	IO_MASK_WRITE,
	IO_MASK_WRITE,
	IO_MASK_WRITE
};

hawk_rio_type_t hawk_rtx_intoriotype (hawk_rtx_t* rtx, hawk_in_type_t in_type)
{
	return in_type_map[in_type];
}

hawk_rio_type_t hawk_rtx_outtoriotype (hawk_rtx_t* rtx, hawk_out_type_t out_type)
{
	return out_type_map[out_type];
}

static int find_rio_in (
	hawk_rtx_t* rtx, hawk_in_type_t in_type, const hawk_ooch_t* name,
	int mbs_if_new, hawk_rio_arg_t** rio, hawk_rio_impl_t* fun)
{
	hawk_rio_arg_t* p = rtx->rio.chain;
	hawk_rio_impl_t handler;
	int io_type, io_mode, io_mask;

	HAWK_ASSERT(in_type >= 0 && in_type <= HAWK_COUNTOF(in_type_map));
	HAWK_ASSERT(in_type >= 0 && in_type <= HAWK_COUNTOF(in_mode_map));
	HAWK_ASSERT(in_type >= 0 && in_type <= HAWK_COUNTOF(in_mask_map));

	/* translate the in_type into the relevant io type and mode */
	io_type = in_type_map[in_type];
	io_mode = in_mode_map[in_type];
	io_mask = in_mask_map[in_type];

	/* get the I/O handler provided by a user */
	handler = rtx->rio.handler[io_type];
	if (handler == HAWK_NULL)
	{
		/* no I/O handler provided */
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIOUSER);
		return -1;
	}

	/* search the chain for exiting an existing io name */
	while (p)
	{
		if (p->type == (io_type | io_mask) &&
		    hawk_comp_oocstr(p->name, name, 0) == 0) break;
		p = p->next;
	}

	if (p == HAWK_NULL)
	{
		hawk_ooi_t x;

		/* if the name doesn't exist in the chain, create an entry
		 * to the chain */
		p = (hawk_rio_arg_t*)hawk_rtx_callocmem(rtx, HAWK_SIZEOF(hawk_rio_arg_t));
		if (p == HAWK_NULL) return -1;

		p->name = hawk_rtx_dupoocstr(rtx, name, HAWK_NULL);
		if (p->name == HAWK_NULL)
		{
			hawk_rtx_freemem(rtx, p);
			return -1;
		}

		p->type = (io_type | io_mask);
		p->mode = io_mode;
		p->rwcmode = HAWK_RIO_CMD_CLOSE_FULL;
		/*
		p->handle = HAWK_NULL;
		p->next = HAWK_NULL;
		p->rwcstate = 0;

		p->in.buf[0] = HAWK_T('\0');
		p->in.pos = 0;
		p->in.len = 0;
		p->in.eof = 0;
		p->in.eos = 0;
		*/
		p->in.mbs = !!mbs_if_new;

		/* request to open the stream */
		x = handler(rtx, HAWK_RIO_CMD_OPEN, p, HAWK_NULL, 0);
		if (x <= -1)
		{
			hawk_rtx_freemem(rtx, p->name);
			hawk_rtx_freemem(rtx, p);
			return -1;
		}

		/* chain it */
		p->next = rtx->rio.chain;
		rtx->rio.chain = p;
	}

	*rio = p;
	*fun = handler;

	return 0;
}

static HAWK_INLINE int resolve_rs (hawk_rtx_t* rtx, hawk_val_t* rs, hawk_oocs_t* rrs)
{
	int ret = 0;
	hawk_val_type_t rs_vtype;

	rs_vtype = HAWK_RTX_GETVALTYPE(rtx, rs);

	switch (rs_vtype)
	{
		case HAWK_VAL_NIL:
			rrs->ptr = HAWK_NULL;
			rrs->len = 0;
			break;

		case HAWK_VAL_STR:
			rrs->ptr = ((hawk_val_str_t*)rs)->val.ptr;
			rrs->len = ((hawk_val_str_t*)rs)->val.len;
			break;

		default:
			rrs->ptr = hawk_rtx_valtooocstrdup(rtx, rs, &rrs->len);
			if (HAWK_UNLIKELY(!rrs->ptr)) ret = -1;
			break;
	}

	return ret;
}

static HAWK_INLINE int resolve_brs (hawk_rtx_t* rtx, hawk_val_t* brs, hawk_bcs_t* rrs)
{
	/* record separator for bytes reading */

	int ret = 0;
	hawk_val_type_t brs_vtype;

	brs_vtype = HAWK_RTX_GETVALTYPE(rtx, brs);

	switch (brs_vtype)
	{
		case HAWK_VAL_NIL:
			rrs->ptr = HAWK_NULL;
			rrs->len = 0;
			break;

		case HAWK_VAL_MBS:
			rrs->ptr = ((hawk_val_mbs_t*)brs)->val.ptr;
			rrs->len = ((hawk_val_mbs_t*)brs)->val.len;
			break;

		case HAWK_VAL_BOB:
			rrs->ptr = (hawk_bch_t*)((hawk_val_bob_t*)brs)->val.ptr;
			rrs->len = ((hawk_val_bob_t*)brs)->val.len;
			break;

		default:
			rrs->ptr = hawk_rtx_valtobcstrdup(rtx, brs, &rrs->len);
			if (rrs->ptr == HAWK_NULL) ret = -1;
			break;
	}

	return ret;
}

static HAWK_INLINE int match_long_rs (hawk_rtx_t* rtx, hawk_ooecs_t* buf, hawk_rio_arg_t* p)
{
	hawk_oocs_t match;
	int ret;

	HAWK_ASSERT(rtx->gbl.rs[0] != HAWK_NULL);
	HAWK_ASSERT(rtx->gbl.rs[1] != HAWK_NULL);

	ret = hawk_rtx_matchrexwithoocs(rtx, rtx->gbl.rs[rtx->gbl.ignorecase], HAWK_OOECS_OOCS(buf), HAWK_OOECS_OOCS(buf), &match, HAWK_NULL);
	if (ret >= 1)
	{
		if (p->in.eof)
		{
			/* when EOF is reached, the record buffer
			 * is not added with a new character. It's
			 * just called again with the same record buffer
			 * as the previous call to this function.
			 * A match in this case must end at the end of
			 * the current record buffer */
			HAWK_ASSERT(HAWK_OOECS_PTR(buf) + HAWK_OOECS_LEN(buf) == match.ptr + match.len);

			/* drop the RS part. no extra character after RS to drop
			 * because we're at EOF and the EOF condition didn't
			 * add a new character to the buffer before the call
			 * to this function.
			 */
			HAWK_OOECS_LEN(buf) -= match.len;
		}
		else
		{
			/* If the match is found before the end of the current buffer,
			 * I see it as the longest match. A match ending at the end
			 * of the buffer is not indeterministic as we don't have the
			 * full input yet.
			 */
			const hawk_ooch_t* be = HAWK_OOECS_PTR(buf) + HAWK_OOECS_LEN(buf);
			const hawk_ooch_t* me = match.ptr + match.len;

			if (me < be)
			{
				/* the match ends before the ending boundary.
				 * it must be the longest match. drop the RS part
				 * and the characters after RS. */
				HAWK_OOECS_LEN(buf) -= match.len + (be - me);
				p->in.pos -= (be - me);
			}
			else
			{
				/* the match is at the ending boundary. switch to no match */
				ret = 0;
			}
		}
	}

	return ret;
}

static HAWK_INLINE int match_long_brs(hawk_rtx_t* rtx, hawk_becs_t* buf, hawk_rio_arg_t* p)
{
	hawk_bcs_t match;
	int ret;

	HAWK_ASSERT(rtx->gbl.rs[0] != HAWK_NULL);
	HAWK_ASSERT(rtx->gbl.rs[1] != HAWK_NULL);


	ret = hawk_rtx_matchrexwithbcs(rtx, rtx->gbl.rs[rtx->gbl.ignorecase], HAWK_BECS_BCS(buf), HAWK_BECS_BCS(buf), &match, HAWK_NULL);
	if (ret >= 1)
	{
		if (p->in.eof)
		{
			/* when EOF is reached, the record buffer
			 * is not added with a new character. It's
			 * just called again with the same record buffer
			 * as the previous call to this function.
			 * A match in this case must end at the end of
			 * the current record buffer */
			HAWK_ASSERT(HAWK_BECS_PTR(buf) + HAWK_BECS_LEN(buf) == match.ptr + match.len);

			/* drop the RS part. no extra character after RS to drop
			 * because we're at EOF and the EOF condition didn't
			 * add a new character to the buffer before the call
			 * to this function.
			 */
			HAWK_BECS_LEN(buf) -= match.len;
		}
		else
		{
			/* If the match is found before the end of the current buffer,
			 * I see it as the longest match. A match ending at the end
			 * of the buffer is not indeterministic as we don't have the
			 * full input yet.
			 */
			const hawk_bch_t* be = HAWK_BECS_PTR(buf) + HAWK_BECS_LEN(buf);
			const hawk_bch_t* me = match.ptr + match.len;

			if (me < be)
			{
				/* the match ends before the ending boundary.
				 * it must be the longest match. drop the BRS part
				 * and the characters after BRS. */
				HAWK_BECS_LEN(buf) -= match.len + (be - me);
				p->in.pos -= (be - me);
			}
			else
			{
				/* the match is at the ending boundary. switch to no match */
				ret = 0;
			}
		}
	}

	return ret;
}

int hawk_rtx_readio (hawk_rtx_t* rtx, hawk_in_type_t in_type, const hawk_ooch_t* name, hawk_ooecs_t* buf)
{
	hawk_rio_arg_t* p;
	hawk_rio_impl_t handler;
	int ret;
	int esc_lq_rq;
	int quoted;
	hawk_ooch_t esc, lq, rq;

	hawk_val_t* rs;
	hawk_oocs_t rrs;

	hawk_val_t* fs;
	hawk_oocs_t ffs;

	hawk_oow_t line_len = 0;
	hawk_ooch_t c = HAWK_T('\0'), pc;

	if (find_rio_in(rtx, in_type, name, 0, &p, &handler) <= -1) return -1;
	if (p->in.eos) return 0; /* no more streams left */
	if (p->in.mbs)
	{
		if (name[0] == '\0')
			hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EPERM, HAWK_T("disallowed mixed mode input"));
		else
			hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EPERM, HAWK_T("disallowed mixed mode input on %js"), name);
		return -1;
	}

	/* ready to read a record(typically a line). clear the buffer. */
	hawk_ooecs_clear(buf);

	/* get the record separator */
	rs = hawk_rtx_getgbl(rtx, HAWK_GBL_RS);
	hawk_rtx_refupval(rtx, rs);

	fs = hawk_rtx_getgbl(rtx, HAWK_GBL_FS);
	hawk_rtx_refupval(rtx, fs);

	if (resolve_rs(rtx, rs, &rrs) <= -1) /* TODO: resolve it upon assignment for optimization? */
	{
		hawk_rtx_refdownval(rtx, rs);
		return -1;
	}

	if (resolve_rs(rtx, fs, &ffs) <= -1)
	{
		hawk_rtx_refdownval(rtx, fs);
		hawk_rtx_refdownval(rtx, rs);
		return -1;
	}

	/* RS set to @nil, FS set to a special string starting with ?, followed by esc lq rq */
	esc_lq_rq = 0;
	quoted = 0;
	if (ffs.len == 5 && ffs.ptr[0] == '?' && !rrs.ptr)
	{
		esc = ffs.ptr[2];
		lq = ffs.ptr[3];
		rq = ffs.ptr[4];

		esc_lq_rq = 1;
		esc_lq_rq += (esc == lq && esc == rq);
	}

	ret = 1;

	/* call the I/O handler */
	while (1)
	{
		if (p->in.pos >= p->in.len)
		{
			hawk_ooi_t x;

			/* no more data in the read buffer.
			 * let the I/O handler read more */

			if (p->in.eof)
			{
				/* it has reached EOF at the previous call. */
				if (HAWK_OOECS_LEN(buf) == 0)
				{
					/* we return EOF if the record buffer is empty */
					ret = 0;
				}
				break;
			}

			/* used for console only. but reset it regardless of the stream type.
			 * it must be faster than checking the type and resetting it */
			p->console_switched = 0;
			x = handler(rtx, HAWK_RIO_CMD_READ, p, p->in.u.buf, HAWK_COUNTOF(p->in.u.buf));
			if (x <= -1)
			{
				ret = -1;
				break;
			}
			else if (x == 0)
			{
				/* EOF reached */
				p->in.eof = 1;

				if (HAWK_OOECS_LEN(buf) == 0)
				{
					/* We can return EOF now if the record buffer is empty */
					ret = 0;
				}
				else if (rrs.ptr && rrs.len == 0)
				{
					/* TODO: handle different line terminator */
					/* drop the line terminator from the record
					 * if RS is a blank line and EOF is reached. */
					if (HAWK_OOECS_LASTCHAR(buf) == HAWK_T('\n'))
					{
						HAWK_OOECS_LEN(buf) -= 1;
						if (rtx->hawk->opt.trait & HAWK_CRLF)
						{
							/* drop preceding CR */
							if (HAWK_OOECS_LEN(buf) > 0 && HAWK_OOECS_LASTCHAR(buf) == HAWK_T('\r')) HAWK_OOECS_LEN(buf) -= 1;
						}
					}
				}
				else if (rrs.len >= 2)
				{
					/* When RS is multiple characters, it should
					 * check for the match at the end of the
					 * input stream also because the previous
					 * match could fail as it didn't end at the
					 * desired position to be the longest match.
					 * At EOF, the match at the end is considered
					 * the longest as there are no more characters
					 * left */
					int n = match_long_rs(rtx, buf, p);
					if (n != 0)
					{
						if (n <= -1) ret = -1;
						break;
					}
				}

				break;
			}

			p->in.len = x;
			p->in.pos = 0;

			/* the new data is a chunk from a new underlying stream.
			 * let's end the record even if it didn't match RS yet.
			 * for example, `hawk -v RS=separator -f script f1.txt f2.txt`
			 * the console handler is invoked over all the specified files.
			 * if `separator` is not found when the end of `f1.txt` is reached,
			 * `f2.txt` is opened and read. When the handler returns the
			 * first part of the second file, it must indicate this special
			 * condition. if it's not indicated, it goes on without breaking
			 * the record at the end of the first file.
			 */
			if (p->console_switched) break;
		}

		if (rrs.ptr == HAWK_NULL)
		{
			hawk_oow_t start_pos = p->in.pos;
			hawk_oow_t end_pos, tmp;

			do
			{
				pc = c;
				c = p->in.u.buf[p->in.pos++];
				end_pos = p->in.pos;

				if (esc_lq_rq == 2)
				{
					/* if FS is something like [?,"""] and RS is @nil,
					 * it supports multi-line quoted vlaues. */
					if (quoted == 2)
					{
						quoted = (c == rq);
						/* no continue here as c could be a new line */
					}
					else if (quoted == 1)
					{
						if (c == rq) quoted = 2;
						continue;
					}
					else if (c == lq)
					{
						quoted = 1;
						continue;
					}
				}

				/* TODO: handle different line terminator */
				/* separate by a new line */
				if (c == HAWK_T('\n'))
				{
					end_pos--;
					if (pc == HAWK_T('\r'))
					{
						if (end_pos > start_pos)
						{
							/* CR is the part of the read buffer.
							 * decrementing the end_pos variable can
							 * simply drop it */
							end_pos--;
						}
						else
						{
							/* CR must have come from the previous
							 * read. drop CR that must be found at
							 * the end of the record buffer. */
							HAWK_ASSERT(end_pos == start_pos);
							HAWK_ASSERT(HAWK_OOECS_LEN(buf) > 0);
							HAWK_ASSERT(HAWK_OOECS_LASTCHAR(buf) == HAWK_T('\r'));
							HAWK_OOECS_LEN(buf)--;
						}
					}
					break;
				}
			}
			while (p->in.pos < p->in.len);

			tmp = hawk_ooecs_ncat(buf, &p->in.u.buf[start_pos], end_pos - start_pos);
			if (tmp == (hawk_oow_t)-1)
			{
				ret = -1;
				break;
			}

			if (end_pos < p->in.len) break; /* RS found */
		}
		else if (rrs.len == 0)
		{
			int done = 0;

			do
			{
				pc = c;
				c = p->in.u.buf[p->in.pos++];

				/* TODO: handle different line terminator */
				/* separate by a blank line */
				if (c == HAWK_T('\n'))
				{
					if (pc == HAWK_T('\r') && HAWK_OOECS_LEN(buf) > 0)
					{
						/* shrink the line length and the record
						 * by dropping of CR before NL */
						HAWK_ASSERT(line_len > 0);
						line_len--;

						/* we don't drop CR from the record buffer
						 * if we're in CRLF mode. POINT-X */
						if (!(rtx->hawk->opt.trait & HAWK_CRLF))
							HAWK_OOECS_LEN(buf) -= 1;
					}

					if (line_len == 0)
					{
						/* we got a blank line */

						if (rtx->hawk->opt.trait & HAWK_CRLF)
						{
							if (HAWK_OOECS_LEN(buf) > 0 && HAWK_OOECS_LASTCHAR(buf) == HAWK_T('\r'))
							{
								/* drop CR not dropped in POINT-X above */
								HAWK_OOECS_LEN(buf) -= 1;
							}

							if (HAWK_OOECS_LEN(buf) <= 0)
							{
								/* if the record is empty when a blank
								 * line is encountered, the line
								 * terminator should not be added to
								 * the record */
								continue;
							}

							/* drop NL */
							HAWK_OOECS_LEN(buf) -= 1;

							/* drop preceding CR */
							if (HAWK_OOECS_LEN(buf) > 0 && HAWK_OOECS_LASTCHAR(buf) == HAWK_T('\r')) HAWK_OOECS_LEN(buf) -= 1;
						}
						else
						{
							if (HAWK_OOECS_LEN(buf) <= 0)
							{
								/* if the record is empty when a blank
								 * line is encountered, the line
								 * terminator should not be added to
								 * the record */
								continue;
							}

							/* drop NL of the previous line */
							HAWK_OOECS_LEN(buf) -= 1; /* simply drop NL */
						}

						done = 1;
						break;
					}

					line_len = 0;
				}
				else line_len++;

				if (hawk_ooecs_ccat(buf, c) == (hawk_oow_t)-1)
				{
					ret = -1;
					done = 1;
					break;
				}
			}
			while (p->in.pos < p->in.len);

			if (done) break;
		}
		else if (rrs.len == 1)
		{
			hawk_oow_t start_pos = p->in.pos;
			hawk_oow_t end_pos, tmp;

			do
			{
				c = p->in.u.buf[p->in.pos++];
				end_pos = p->in.pos;
				if (c == rrs.ptr[0])
				{
					end_pos--;
					break;
				}
			}
			while (p->in.pos < p->in.len);

			tmp = hawk_ooecs_ncat(buf, &p->in.u.buf[start_pos], end_pos - start_pos);
			if (tmp == (hawk_oow_t)-1)
			{
				ret = -1;
				break;
			}

			if (end_pos < p->in.len) break; /* RS found */
		}
		else
		{
			hawk_oow_t tmp;
			int n;

			/* if RS is composed of multiple characters,
			 * I perform the matching after having added the
			 * current character 'c' to the record buffer 'buf'
			 * to find the longest match. If a match found ends
			 * one character before this character just added
			 * to the buffer, it is the longest match.
			 */

			tmp = hawk_ooecs_ncat(buf, &p->in.u.buf[p->in.pos], p->in.len - p->in.pos);
			if (tmp == (hawk_oow_t)-1)
			{
				ret = -1;
				break;
			}

			p->in.pos = p->in.len;

			n = match_long_rs(rtx, buf, p);
			if (n != 0)
			{
				if (n <= -1) ret = -1;
				break;
			}
		}
	}

	if (rrs.ptr && HAWK_RTX_GETVALTYPE(rtx, rs) != HAWK_VAL_STR) hawk_rtx_freemem(rtx, rrs.ptr);
	hawk_rtx_refdownval(rtx, fs);
	hawk_rtx_refdownval(rtx, rs);

	return ret;
}

int hawk_rtx_readiobytes (hawk_rtx_t* rtx, hawk_in_type_t in_type, const hawk_ooch_t* name, hawk_becs_t* buf)
{
	hawk_rio_arg_t* p;
	hawk_rio_impl_t handler;
	int ret;
	int esc_lq_rq;
	int quoted;
	hawk_bch_t esc, lq, rq;

	hawk_val_t* brs;
	hawk_bcs_t rrs;

	hawk_val_t* bfs;
	hawk_bcs_t ffs;

	hawk_oow_t line_len = 0;
	hawk_bch_t c = '\0', pc;

	if (find_rio_in(rtx, in_type, name, 1, &p, &handler) <= -1) return -1;
	if (p->in.eos) return 0; /* no more streams left */
	if (!p->in.mbs)
	{
		if (name[0] == '\0')
			hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EPERM, HAWK_T("disallowed mixed mode input"));
		else
			hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EPERM, HAWK_T("disallowed mixed mode input on %js"), name);
		return -1;
	}

	/* ready to read a record(typically a line). clear the buffer. */
	hawk_becs_clear(buf);

	/* get the record separator */
	brs = hawk_rtx_getgbl(rtx, HAWK_GBL_RS);
	hawk_rtx_refupval(rtx, brs);

	bfs = hawk_rtx_getgbl(rtx, HAWK_GBL_FS);
	hawk_rtx_refupval(rtx, bfs);

	if (resolve_brs(rtx, brs, &rrs) <= -1)
	{
		hawk_rtx_refdownval(rtx, brs);
		return -1;
	}

	if (resolve_brs(rtx, bfs, &ffs) <= -1)
	{
		hawk_rtx_refdownval(rtx, bfs);
		hawk_rtx_refdownval(rtx, brs);
		return -1;
	}

	/* RS set to @nil, FS set to a special string starting with ?, followed by esc lq rq */
	esc_lq_rq = 0;
	quoted = 0;
	if (ffs.len == 5 && ffs.ptr[0] == '?' && !rrs.ptr)
	{
		esc = ffs.ptr[2];
		lq = ffs.ptr[3];
		rq = ffs.ptr[4];

		esc_lq_rq = 1;
		esc_lq_rq += (esc == lq && esc == rq);
	}

	ret = 1;

	/* call the I/O handler */
	while (1)
	{
		if (p->in.pos >= p->in.len)
		{
			hawk_ooi_t x;

			/* no more data in the read buffer.
			 * let the I/O handler read more */

			if (p->in.eof)
			{
				/* it has reached EOF at the previous call. */
				if (HAWK_BECS_LEN(buf) == 0)
				{
					/* we return EOF if the record buffer is empty */
					ret = 0;
				}
				break;
			}

			p->console_switched = 0;
			x = handler(rtx, HAWK_RIO_CMD_READ_BYTES, p, p->in.u.bbuf, HAWK_COUNTOF(p->in.u.bbuf));
			if (x <= -1)
			{
				ret = -1;
				break;
			}

			if (x == 0)
			{
				/* EOF reached */
				p->in.eof = 1;

				if (HAWK_BECS_LEN(buf) == 0)
				{
					/* We can return EOF now if the record buffer is empty */
					ret = 0;
				}
				else if (rrs.ptr && rrs.len == 0)
				{
					/* TODO: handle different line terminator */
					/* drop the line terminator from the record
					 * if RS is a blank line and EOF is reached. */
					if (HAWK_BECS_LASTCHAR(buf) == '\n')
					{
						HAWK_BECS_LEN(buf) -= 1;
						if (rtx->hawk->opt.trait & HAWK_CRLF)
						{
							/* drop preceding CR */
							if (HAWK_BECS_LEN(buf) > 0 && HAWK_BECS_LASTCHAR(buf) == '\r') HAWK_BECS_LEN(buf) -= 1;
						}
					}
				}
				else if (rrs.len >= 2)
				{
					/* When RS is multiple characters, it should
					 * check for the match at the end of the
					 * input stream also because the previous
					 * match could fail as it didn't end at the
					 * desired position to be the longest match.
					 * At EOF, the match at the end is considered
					 * the longest as there are no more characters
					 * left */
					int n = match_long_brs(rtx, buf, p);
					if (n != 0)
					{
						if (n <= -1) ret = -1;
						break;
					}
				}

				break;
			}

			p->in.len = x;
			p->in.pos = 0;

			/* the new data is a chunk from a new underlying medium.
			 * let's end the record even if it didn't match RS yet. */
			if (p->console_switched) break;
		}

		if (rrs.ptr == HAWK_NULL)
		{
			hawk_oow_t start_pos = p->in.pos;
			hawk_oow_t end_pos, tmp;

			do
			{
				pc = c;
				c = p->in.u.bbuf[p->in.pos++];
				end_pos = p->in.pos;

				if (esc_lq_rq == 2)
				{
					/* if FS is something like [?,"""] and RS is @nil,
					 * it supports multi-line quoted vlaues. */
					if (quoted == 2)
					{
						quoted = (c == rq);
						/* no continue here as c could be a new line */
					}
					else if (quoted == 1)
					{
						if (c == rq) quoted = 2;
						continue;
					}
					else if (c == lq)
					{
						quoted = 1;
						continue;
					}
				}

				/* TODO: handle different line terminator */
				/* separate by a new line */
				if (c == '\n')
				{
					end_pos--;
					if (pc == '\r')
					{
						if (end_pos > start_pos)
						{
							/* CR is the part of the read buffer.
							 * decrementing the end_pos variable can
							 * simply drop it */
							end_pos--;
						}
						else
						{
							/* CR must have come from the previous
							 * read. drop CR that must be found  at
							 * the end of the record buffer. */
							HAWK_ASSERT(end_pos == start_pos);
							HAWK_ASSERT(HAWK_BECS_LEN(buf) > 0);
							HAWK_ASSERT(HAWK_BECS_LASTCHAR(buf) == '\r');
							HAWK_BECS_LEN(buf)--;
						}
					}
					break;
				}
			}
			while (p->in.pos < p->in.len);

			tmp = hawk_becs_ncat(buf, &p->in.u.bbuf[start_pos], end_pos - start_pos);
			if (tmp == (hawk_oow_t)-1)
			{
				ret = -1;
				break;
			}

			if (end_pos < p->in.len) break; /* RS found */
		}
		else if (rrs.len == 0)
		{
			int done = 0;

			do
			{
				pc = c;
				c = p->in.u.bbuf[p->in.pos++];

				/* TODO: handle different line terminator */
				/* separate by a blank line */
				if (c == '\n')
				{
					if (pc == '\r' && HAWK_BECS_LEN(buf) > 0)
					{
						/* shrink the line length and the record
						 * by dropping of CR before NL */
						HAWK_ASSERT(line_len > 0);
						line_len--;

						/* we don't drop CR from the record buffer
						 * if we're in CRLF mode. POINT-X */
						if (!(rtx->hawk->opt.trait & HAWK_CRLF))
							HAWK_BECS_LEN(buf) -= 1;
					}

					if (line_len == 0)
					{
						/* we got a blank line */

						if (rtx->hawk->opt.trait & HAWK_CRLF)
						{
							if (HAWK_BECS_LEN(buf) > 0 && HAWK_BECS_LASTCHAR(buf) == '\r')
							{
								/* drop CR not dropped in POINT-X above */
								HAWK_BECS_LEN(buf) -= 1;
							}

							if (HAWK_BECS_LEN(buf) <= 0)
							{
								/* if the record is empty when a blank
								 * line is encountered, the line
								 * terminator should not be added to
								 * the record */
								continue;
							}

							/* drop NL */
							HAWK_BECS_LEN(buf) -= 1;

							/* drop preceding CR */
							if (HAWK_BECS_LEN(buf) > 0 && HAWK_BECS_LASTCHAR(buf) == '\r') HAWK_BECS_LEN(buf) -= 1;
						}
						else
						{
							if (HAWK_BECS_LEN(buf) <= 0)
							{
								/* if the record is empty when a blank
								 * line is encountered, the line
								 * terminator should not be added to
								 * the record */
								continue;
							}

							/* drop NL of the previous line */
							HAWK_BECS_LEN(buf) -= 1; /* simply drop NL */
						}

						done = 1;
						break;
					}

					line_len = 0;
				}
				else line_len++;

				if (hawk_becs_ccat(buf, c) == (hawk_oow_t)-1)
				{

					ret = -1;
					done = 1;
					break;
				}
			}
			while (p->in.pos < p->in.len);

			if (done) break;
		}
		else if (rrs.len == 1)
		{
			hawk_oow_t start_pos = p->in.pos;
			hawk_oow_t end_pos, tmp;

			do
			{
				c = p->in.u.bbuf[p->in.pos++];
				end_pos = p->in.pos;
				if (c == rrs.ptr[0])
				{
					end_pos--;
					break;
				}
			}
			while (p->in.pos < p->in.len);

			tmp = hawk_becs_ncat(buf, &p->in.u.bbuf[start_pos], end_pos - start_pos);
			if (tmp == (hawk_oow_t)-1)
			{
				ret = -1;
				break;
			}

			if (end_pos < p->in.len) break; /* RS found */
		}
		else
		{
			hawk_oow_t tmp;
			int n;

			/* if RS is composed of multiple characters,
			 * I perform the matching after having added the
			 * current character 'c' to the record buffer 'buf'
			 * to find the longest match. If a match found ends
			 * one character before this character just added
			 * to the buffer, it is the longest match.
			 */

			tmp = hawk_becs_ncat(buf, &p->in.u.bbuf[p->in.pos], p->in.len - p->in.pos);
			if (tmp == (hawk_oow_t)-1)
			{
				ret = -1;
				break;
			}

			p->in.pos = p->in.len;

			n = match_long_brs(rtx, buf, p);
			if (n != 0)
			{
				if (n <= -1) ret = -1;
				break;
			}
		}
	}

	if (rrs.ptr && HAWK_RTX_GETVALTYPE(rtx, brs) != HAWK_VAL_MBS) hawk_rtx_freemem(rtx, rrs.ptr);
	hawk_rtx_refdownval(rtx, bfs);
	hawk_rtx_refdownval(rtx, brs);

	return ret;
}


int hawk_rtx_writeioval (hawk_rtx_t* rtx, hawk_out_type_t out_type, const hawk_ooch_t* name, hawk_val_t* v)
{
	hawk_val_type_t vtype;
	vtype = HAWK_RTX_GETVALTYPE (rtx, v);

	switch (vtype)
	{
		case HAWK_VAL_CHAR:
		{
			hawk_ooch_t tmp = HAWK_RTX_GETCHARFROMVAL(rtx, v);
			return hawk_rtx_writeiostr(rtx, out_type, name, &tmp, 1);
		}

		case HAWK_VAL_STR:
			return hawk_rtx_writeiostr(rtx, out_type, name, ((hawk_val_str_t*)v)->val.ptr, ((hawk_val_str_t*)v)->val.len);

		case HAWK_VAL_BCHR:
		{
			hawk_bch_t tmp = HAWK_RTX_GETBCHRFROMVAL(rtx, v);
			return hawk_rtx_writeiobytes(rtx, out_type, name, &tmp, 1);
		}

		case HAWK_VAL_MBS:
			return hawk_rtx_writeiobytes(rtx, out_type, name, ((hawk_val_mbs_t*)v)->val.ptr, ((hawk_val_mbs_t*)v)->val.len);

		case HAWK_VAL_BOB:
			return hawk_rtx_writeiobytes(rtx, out_type, name, (hawk_bch_t*)((hawk_val_bob_t*)v)->val.ptr, ((hawk_val_bob_t*)v)->val.len);

		default:
		{
			hawk_rtx_valtostr_out_t out;
			int n;
			out.type = HAWK_RTX_VALTOSTR_CPLDUP | HAWK_RTX_VALTOSTR_PRINT;
			if (hawk_rtx_valtostr(rtx, v, &out) <= -1) return -1;
			n = hawk_rtx_writeiostr(rtx, out_type, name, out.u.cpldup.ptr, out.u.cpldup.len);
			hawk_rtx_freemem(rtx, out.u.cpldup.ptr);
			return n;
		}
	}
}

struct write_io_data_t
{
	hawk_rio_arg_t* p;
	hawk_rio_impl_t handler;
};
typedef struct write_io_data_t write_io_data_t;

static int prepare_for_write_io_data (hawk_rtx_t* rtx, hawk_out_type_t out_type, const hawk_ooch_t* name, write_io_data_t* wid)
{
	hawk_rio_arg_t* p = rtx->rio.chain;
	hawk_rio_impl_t handler;
	int io_type, io_mode, io_mask, n;

	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_type_map));
	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_mode_map));
	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_mask_map));

	/* translate the out_type into the relevant io type and mode */
	io_type = out_type_map[out_type];
	io_mode = out_mode_map[out_type];
	io_mask = out_mask_map[out_type];

	handler = rtx->rio.handler[io_type];
	if (HAWK_UNLIKELY(!handler))
	{
		/* no I/O handler provided */
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIOUSER);
		return -1;
	}

	/* look for the corresponding rio for name */
	while (p)
	{
		/* the file "1.tmp", in the following code snippets,
		 * would be opened by the first print statement, but not by
		 * the second print statement. this is because
		 * both HAWK_OUT_FILE and HAWK_OUT_APFILE are
		 * translated to HAWK_RIO_FILE and it is used to
		 * keep track of file handles..
		 *
		 *    print "1111" >> "1.tmp"
		 *    print "1111" > "1.tmp"
		 */
		if (p->type == (io_type | io_mask) && hawk_comp_oocstr(p->name, name, 0) == 0) break;
		p = p->next;
	}

	/* if there is not corresponding rio for name, create one */
	if (p == HAWK_NULL)
	{
		p = (hawk_rio_arg_t*)hawk_rtx_allocmem(rtx, HAWK_SIZEOF(hawk_rio_arg_t));
		if (HAWK_UNLIKELY(!p)) return -1;

		HAWK_MEMSET (p, 0, HAWK_SIZEOF(*p));

		p->name = hawk_rtx_dupoocstr(rtx, name, HAWK_NULL);
		if (HAWK_UNLIKELY(!p->name))
		{
			hawk_rtx_freemem(rtx, p);
			return -1;
		}

		p->type = (io_type | io_mask);
		p->mode = io_mode;
		p->rwcmode = HAWK_RIO_CMD_CLOSE_FULL;
		/*
		p->handle = HAWK_NULL;
		p->next = HAWK_NULL;
		p->rwcstate = 0;

		p->out.eof = 0;
		p->out.eos = 0;
		*/

		n = handler(rtx, HAWK_RIO_CMD_OPEN, p, HAWK_NULL, 0);
		if (n <= -1)
		{
			hawk_rtx_freemem(rtx, p->name);
			hawk_rtx_freemem(rtx, p);
			return -1;
		}

		/* chain it */
		p->next = rtx->rio.chain;
		rtx->rio.chain = p;
	}

	if (p->out.eos) return 0; /* no more streams */
	if (p->out.eof) return 0; /* it has reached the end of the stream but this function has been recalled */

	wid->handler = handler;
	wid->p = p;
	return 1;
}

int hawk_rtx_writeiostr (hawk_rtx_t* rtx, hawk_out_type_t out_type, const hawk_ooch_t* name, hawk_ooch_t* str, hawk_oow_t len)
{
	int x;
	write_io_data_t wid;

	if ((x = prepare_for_write_io_data(rtx, out_type, name, &wid)) <= 0) return x;

	while (len > 0)
	{
		hawk_ooi_t n;

		n = wid.handler(rtx, HAWK_RIO_CMD_WRITE, wid.p, str, len);
		if (n <= -1) return -1;

		if (n == 0)
		{
			wid.p->out.eof = 1;
			return 0;
		}

		len -= n;
		str += n;
	}

	return 1;
}

int hawk_rtx_writeiobytes (hawk_rtx_t* rtx, hawk_out_type_t out_type, const hawk_ooch_t* name, hawk_bch_t* str, hawk_oow_t len)
{
	int x;
	write_io_data_t wid;

	if ((x = prepare_for_write_io_data(rtx, out_type, name, &wid)) <= 0) return x;

	while (len > 0)
	{
		hawk_ooi_t n;

		n = wid.handler(rtx, HAWK_RIO_CMD_WRITE_BYTES, wid.p, str, len);
		if (n <= -1) return -1;

		if (n == 0)
		{
			wid.p->out.eof = 1;
			return 0;
		}

		len -= n;
		str += n;
	}

	return 1;
}

int hawk_rtx_flushio (hawk_rtx_t* rtx, hawk_out_type_t out_type, const hawk_ooch_t* name)
{
	hawk_rio_arg_t* p = rtx->rio.chain;
	hawk_rio_impl_t handler;
	int io_type, io_mode, io_mask;
	hawk_ooi_t n;
	int ok = 0;

	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_type_map));
	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_mode_map));
	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_mask_map));

	/* translate the out_type into the relevant I/O type and mode */
	io_type = out_type_map[out_type];
	io_mode = out_mode_map[out_type];
	io_mask = out_mask_map[out_type];

	handler = rtx->rio.handler[io_type];
	if (!handler)
	{
		/* no I/O handler provided */
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIOUSER);
		return -1;
	}

	/* look for the corresponding rio for name */
	while (p)
	{
		/* without the check for io_mode and p->mode,
		 * HAWK_OUT_FILE and HAWK_OUT_APFILE matches the
		 * same entry since (io_type | io_mask) has the same value
		 * for both. */
		if (p->type == (io_type | io_mask) && p->mode == io_mode &&
		    (name == HAWK_NULL || hawk_comp_oocstr(p->name, name, 0) == 0))
		{
			n = handler(rtx, HAWK_RIO_CMD_FLUSH, p, HAWK_NULL, 0);
			if (n <= -1) return -1;
			ok = 1;
		}

		p = p->next;
	}

	if (ok) return 0;

	/* there is no corresponding rio for name */
	hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIONMNF);
	return -1;
}

int hawk_rtx_nextio_read (hawk_rtx_t* rtx, hawk_in_type_t in_type, const hawk_ooch_t* name)
{
	hawk_rio_arg_t* p = rtx->rio.chain;
	hawk_rio_impl_t handler;
	int io_type, /*io_mode,*/ io_mask;
	hawk_ooi_t n;

	HAWK_ASSERT(in_type >= 0 && in_type <= HAWK_COUNTOF(in_type_map));
	HAWK_ASSERT(in_type >= 0 && in_type <= HAWK_COUNTOF(in_mode_map));
	HAWK_ASSERT(in_type >= 0 && in_type <= HAWK_COUNTOF(in_mask_map));

	/* translate the in_type into the relevant I/O type and mode */
	io_type = in_type_map[in_type];
	/*io_mode = in_mode_map[in_type];*/
	io_mask = in_mask_map[in_type];

	handler = rtx->rio.handler[io_type];
	if (!handler)
	{
		/* no I/O handler provided */
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIOUSER);
		return -1;
	}

	while (p)
	{
		if (p->type == (io_type | io_mask) && hawk_comp_oocstr(p->name, name,  0) == 0) break;
		p = p->next;
	}

	if (!p)
	{
		/* something is totally wrong */
		HAWK_ASSERT(!"should never happen - cannot find the relevant rio entry");
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EINTERN);
		return -1;
	}

	if (p->in.eos)
	{
		/* no more streams. */
		return 0;
	}

	n = handler(rtx, HAWK_RIO_CMD_NEXT, p, HAWK_NULL, 0);
	if (n <= -1) return -1;

	if (n == 0)
	{
		/* the next stream cannot be opened.
		 * set the EOS flags so that the next call to nextio_read
		 * will return 0 without executing the handler */
		p->in.eos = 1;
		return 0;
	}
	else
	{
		/* as the next stream has been opened successfully,
		 * the EOF flag should be cleared if set */
		p->in.eof = 0;

		/* also the previous input buffer must be reset */
		p->in.pos = 0;
		p->in.len = 0;

		return 1;
	}
}

int hawk_rtx_nextio_write (hawk_rtx_t* rtx, hawk_out_type_t out_type, const hawk_ooch_t* name)
{
	hawk_rio_arg_t* p = rtx->rio.chain;
	hawk_rio_impl_t handler;
	int io_type, /*io_mode,*/ io_mask;
	hawk_ooi_t n;

	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_type_map));
	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_mode_map));
	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_mask_map));

	/* translate the out_type into the relevant I/O type and mode */
	io_type = out_type_map[out_type];
	/*io_mode = out_mode_map[out_type];*/
	io_mask = out_mask_map[out_type];

	handler = rtx->rio.handler[io_type];
	if (!handler)
	{
		/* no I/O handler provided */
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIOUSER);
		return -1;
	}

	while (p)
	{
		if (p->type == (io_type | io_mask) && hawk_comp_oocstr(p->name, name, 0) == 0) break;
		p = p->next;
	}

	if (!p)
	{
		/* something is totally wrong */
		HAWK_ASSERT(!"should never happen - cannot find the relevant rio entry");

		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EINTERN);
		return -1;
	}

	if (p->out.eos)
	{
		/* no more streams. */
		return 0;
	}

	n = handler(rtx, HAWK_RIO_CMD_NEXT, p, HAWK_NULL, 0);
	if (n <= -1) return -1;

	if (n == 0)
	{
		/* the next stream cannot be opened.
		 * set the EOS flags so that the next call to nextio_write
		 * will return 0 without executing the handler */
		p->out.eos = 1;
		return 0;
	}
	else
	{
		/* as the next stream has been opened successfully,
		 * the EOF flag should be cleared if set */
		p->out.eof = 0;
		return 1;
	}
}

int hawk_rtx_closio_read (hawk_rtx_t* rtx, hawk_in_type_t in_type, const hawk_ooch_t* name)
{
	hawk_rio_arg_t* p = rtx->rio.chain, * px = HAWK_NULL;
	hawk_rio_impl_t handler;
	int io_type, /*io_mode,*/ io_mask;

	HAWK_ASSERT(in_type >= 0 && in_type <= HAWK_COUNTOF(in_type_map));
	HAWK_ASSERT(in_type >= 0 && in_type <= HAWK_COUNTOF(in_mode_map));
	HAWK_ASSERT(in_type >= 0 && in_type <= HAWK_COUNTOF(in_mask_map));

	/* translate the in_type into the relevant I/O type and mode */
	io_type = in_type_map[in_type];
	/*io_mode = in_mode_map[in_type];*/
	io_mask = in_mask_map[in_type];

	handler = rtx->rio.handler[io_type];
	if (!handler)
	{
		/* no I/O handler provided */
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIOUSER);
		return -1;
	}

	while (p)
	{
		if (p->type == (io_type | io_mask) && hawk_comp_oocstr(p->name, name, 0) == 0)
		{
			hawk_rio_impl_t handler;

			handler = rtx->rio.handler[p->type & IO_MASK_CLEAR];
			if (handler)
			{
				if (handler (rtx, HAWK_RIO_CMD_CLOSE, p, HAWK_NULL, 0) <= -1)
				{
					/* this is not a rtx-time error.*/
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIOIMPL);
					return -1;
				}
			}

			if (px) px->next = p->next;
			else rtx->rio.chain = p->next;

			hawk_rtx_freemem(rtx, p->name);
			hawk_rtx_freemem(rtx, p);
			return 0;
		}

		px = p;
		p = p->next;
	}

	/* the name given is not found */
	hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIONMNF);
	return -1;
}

int hawk_rtx_closio_write (hawk_rtx_t* rtx, hawk_out_type_t out_type, const hawk_ooch_t* name)
{
	hawk_rio_arg_t* p = rtx->rio.chain, * px = HAWK_NULL;
	hawk_rio_impl_t handler;
	int io_type, /*io_mode,*/ io_mask;

	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_type_map));
	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_mode_map));
	HAWK_ASSERT(out_type >= 0 && out_type <= HAWK_COUNTOF(out_mask_map));

	/* translate the out_type into the relevant io type and mode */
	io_type = out_type_map[out_type];
	/*io_mode = out_mode_map[out_type];*/
	io_mask = out_mask_map[out_type];

	handler = rtx->rio.handler[io_type];
	if (HAWK_UNLIKELY(!handler))
	{
		/* no io handler provided */
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIOUSER);
		return -1;
	}

	while (p)
	{
		if (p->type == (io_type | io_mask) && hawk_comp_oocstr(p->name, name, 0) == 0)
		{
			hawk_rio_impl_t handler;

			handler = rtx->rio.handler[p->type & IO_MASK_CLEAR];
			if (handler && handler(rtx, HAWK_RIO_CMD_CLOSE, p, HAWK_NULL, 0) <= -1) return -1;

			if (px) px->next = p->next;
			else rtx->rio.chain = p->next;

			hawk_rtx_freemem(rtx, p->name);
			hawk_rtx_freemem(rtx, p);
			return 0;
		}

		px = p;
		p = p->next;
	}

	hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIONMNF);
	return -1;
}

int hawk_rtx_closeio (hawk_rtx_t* rtx, const hawk_ooch_t* name, const hawk_ooch_t* opt)
{
	hawk_rio_arg_t* p = rtx->rio.chain, * px = HAWK_NULL;

	while (p)
	{
		 /* it handles the first that matches the given name
		  * regardless of the io type */
		if (hawk_comp_oocstr(p->name, name, 0) == 0)
		{
			hawk_rio_impl_t handler;
			hawk_rio_rwcmode_t rwcmode = HAWK_RIO_CMD_CLOSE_FULL;

			if (opt)
			{
				if (opt[0] == HAWK_T('r'))
				{
					if (p->type & IO_MASK_RDWR)
					{
						if (p->rwcstate != HAWK_RIO_CMD_CLOSE_WRITE)
						{
							/* if the write end is not
							 * closed, let io handler close
							 * the read end only. */
							rwcmode = HAWK_RIO_CMD_CLOSE_READ;
						}
					}
					else if (!(p->type & IO_MASK_READ)) goto skip;
				}
				else
				{
					HAWK_ASSERT(opt[0] == HAWK_T('w'));
					if (p->type & IO_MASK_RDWR)
					{
						if (p->rwcstate != HAWK_RIO_CMD_CLOSE_READ)
						{
							/* if the read end is not
							 * closed, let io handler close
							 * the write end only. */
							rwcmode = HAWK_RIO_CMD_CLOSE_WRITE;
						}
					}
					else if (!(p->type & IO_MASK_WRITE)) goto skip;
				}
			}

			handler = rtx->rio.handler[p->type & IO_MASK_CLEAR];
			if (handler)
			{
				p->rwcmode = rwcmode;
				if (handler(rtx, HAWK_RIO_CMD_CLOSE, p, HAWK_NULL, 0) <= -1)
				{
					/* this is not a run-time error.*/
					return -1;
				}
			}

			if (p->type & IO_MASK_RDWR)
			{
				p->rwcmode = rwcmode;
				if (p->rwcstate == 0 && rwcmode != 0)
				{
					/* if either end has not been closed.
					 * return success without destroying
					 * the internal node. rwcstate keeps
					 * what has been successfully closed */
					p->rwcstate = rwcmode;
					return 0;
				}
			}

			if (px) px->next = p->next;
			else rtx->rio.chain = p->next;

			hawk_rtx_freemem(rtx, p->name);
			hawk_rtx_freemem(rtx, p);

			return 0;
		}

	skip:
		px = p;
		p = p->next;
	}

	hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EIONMNF);
	return -1;
}

void hawk_rtx_flushallios (hawk_rtx_t* rtx)
{
	hawk_rio_arg_t* rio;
	hawk_rio_impl_t handler;

	for (rio = rtx->rio.chain; rio; rio = rio->next)
	{
		handler = rtx->rio.handler[rio->type & IO_MASK_CLEAR];
		if (handler)
		{
			handler (rtx, HAWK_RIO_CMD_FLUSH, rio, HAWK_NULL, 0);
		}
	}
}

void hawk_rtx_clearallios (hawk_rtx_t* rtx)
{
	hawk_rio_arg_t* next;
	hawk_rio_impl_t handler;
	hawk_ooi_t n;

	while (rtx->rio.chain)
	{
		handler = rtx->rio.handler[rtx->rio.chain->type & IO_MASK_CLEAR];
		next = rtx->rio.chain->next;

		if (handler)
		{
			rtx->rio.chain->rwcmode = 0;
			n = handler(rtx, HAWK_RIO_CMD_CLOSE, rtx->rio.chain, HAWK_NULL, 0);
			if (n <= -1)
			{
				/* TODO: some warnings need to be shown??? */
			}
		}

		hawk_rtx_freemem(rtx, rtx->rio.chain->name);
		hawk_rtx_freemem(rtx, rtx->rio.chain);

		rtx->rio.chain = next;
	}
}
