/*
 *  R : A Computer Language for Statistical Data Analysis
 *  Copyright (C) 1995, 1996, 1997,  Robert Gentleman and Ross Ihaka
 *                2007-2020 The R Core Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, a copy is available at
 *  https://www.R-project.org/Licenses/
 *
 *
 *  I/O Support Code
 *
 *  This is a general IO support package to provide R with a uniform
 *  interface to reading data from the console, files and internal
 *  text strings.
 *
 *  This is probably overkill, but it works much better than the
 *  previous anarchy.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "IOStuff.h"


/* Move the iob->write_buf pointer to the next */
/* BufferListItem in the chain. If there no next */
/* buffer item, then one is added. */

static int NextWriteBufferListItem(IoBuffer *iob)
{
    if (iob->write_buf->next) {
	iob->write_buf = iob->write_buf->next;
    }
    else {
	BufferListItem *_new;
	if (!(_new = (BufferListItem*)malloc(sizeof(BufferListItem))))
	    return 0;
	_new->next = NULL;
	iob->write_buf->next = _new;
	iob->write_buf = iob->write_buf->next;
    }
    iob->write_ptr = iob->write_buf->buf;
    iob->write_offset = 0;
    return 1;
}

/* Move the iob->read_buf pointer to the next */
/* BufferListItem in the chain. */

static int NextReadBufferListItem(IoBuffer *iob)
{
    iob->read_buf = iob->read_buf->next;
    iob->read_ptr = iob->read_buf->buf;
    iob->read_offset = 0;
    return 1;
}


/* Reset the read/write pointers of an IoBuffer */

int attribute_hidden R_IoBufferWriteReset(IoBuffer *iob)
{
    if (iob == NULL || iob->start_buf == NULL)
	return 0;
    iob->write_buf = iob->start_buf;
    iob->write_ptr = iob->write_buf->buf;
    iob->write_offset = 0;
    iob->read_buf = iob->start_buf;
    iob->read_ptr = iob->read_buf->buf;
    iob->read_offset = 0;
    return 1;
}

/* Reset the read pointer of an IoBuffer */

int attribute_hidden R_IoBufferReadReset(IoBuffer *iob)
{
    if (iob == NULL || iob->start_buf == NULL)
	return 0;
    iob->read_buf = iob->start_buf;
    iob->read_ptr = iob->read_buf->buf;
    iob->read_offset = 0;
    return 1;
}

/* Allocate an initial BufferListItem for IoBuffer */
/* Initialize the counts and pointers. */

int attribute_hidden R_IoBufferInit(IoBuffer *iob)
{
    if (iob == NULL) return 0;
    iob->start_buf = (BufferListItem*)malloc(sizeof(BufferListItem));
    if (iob->start_buf == NULL) return 0;
    iob->start_buf->next = NULL;
    return R_IoBufferWriteReset(iob);
}

/* Free any BufferListItem's associated with an IoBuffer */
/* This resets pointers to NULL, which could be detected */
/* in other calls. */

int attribute_hidden R_IoBufferFree(IoBuffer *iob)
{
    BufferListItem *thisItem, *nextItem;
    if (iob == NULL || iob->start_buf == NULL)
	return 0;
    thisItem = iob->start_buf;
    while (thisItem) {
	nextItem = thisItem->next;
	free(thisItem);
	thisItem = nextItem;
    }
    return 1;
}

/* Add a character to an IoBuffer */

int attribute_hidden R_IoBufferPutc(int c, IoBuffer *iob)
{
    if (iob->write_offset == IOBSIZE)
	NextWriteBufferListItem(iob);
    *(iob->write_ptr)++ = (char) c;
    iob->write_offset++;
    return 0;/*not used*/
}

/* Add a (null terminated) string to an IoBuffer */

int attribute_hidden R_IoBufferPuts(char *s, IoBuffer *iob)
{
    char *p;
    int n = 0;
    for (p = s; *p; p++) {
	R_IoBufferPutc(*p, iob);
	n++;
    }
    return n;
}

/* Read a character from an IoBuffer */

int attribute_hidden R_IoBufferGetc(IoBuffer *iob)
{
    if (iob->read_buf == iob->write_buf &&
       iob->read_offset >= iob->write_offset)
	return EOF;
    if (iob->read_offset == IOBSIZE) NextReadBufferListItem(iob);
    iob->read_offset++;
    return *(iob->read_ptr)++;
}

/* What is our current offset, taking all blocks into account? */

int attribute_hidden R_IoBufferReadOffset(IoBuffer *iob)
{
    int result = iob->read_offset;
    BufferListItem* buf = iob->start_buf;
    while(buf && buf != iob->read_buf) {
    	result += IOBSIZE;
    	buf = buf->next;
    }
    return result;
}
    
/* Initialization code for text buffers */

static void transferChars(unsigned char *p, const char *q)
{
    while (*q) *p++ = *q++;
    *p++ = '\n';
    *p++ = '\0';
}

/* respect encoding override from parser invocation - do_parse */
static const char *translateCharWithOverride(SEXP x)
{
    if (!IS_LATIN1(x) && !mbcslocale && known_to_be_utf8)
	/* A hack to allow UTF-8 string literals, comments when
	   parsing on Windows. Note that the parser cannot handle
	   invalid characters when running in UTF-8 locale. */
	return CHAR(x);
    else
	return translateChar(x);
}

int attribute_hidden R_TextBufferInit(TextBuffer *txtb, SEXP text)
{
    int i, k, l, n;
    if (isString(text)) {
	// translateChar might allocate
	void *vmax = vmaxget();
	n = length(text);
	l = 0;
	for (i = 0; i < n; i++) {
	    if (STRING_ELT(text, i) != R_NilValue) {
		k = (int) strlen(translateCharWithOverride(STRING_ELT(text, i)));
		if (k > l)
		    l = k;
	    }
	}
	vmaxset(vmax);
	txtb->vmax = vmax;
	txtb->buf = (unsigned char *)R_alloc(l+2, sizeof(char)); /* '\n' and '\0' */
	txtb->bufp = txtb->buf;
	txtb->text = text;
	txtb->ntext = n;
	txtb->offset = 0;
	transferChars(txtb->buf,
		      translateCharWithOverride(STRING_ELT(txtb->text, txtb->offset)));
	txtb->offset++;
	return 1;
    }
    else {
	txtb->vmax = vmaxget();
	txtb->buf = NULL;
	txtb->bufp = NULL;
	txtb->text = R_NilValue;
	txtb->ntext = 0;
	txtb->offset = 1;
	return 0;
    }
}

/* Finalization code for text buffers */

int attribute_hidden R_TextBufferFree(TextBuffer *txtb)
{
    vmaxset(txtb->vmax);
    return 0;/* not used */
}

/* Getc for text buffers */

int attribute_hidden R_TextBufferGetc(TextBuffer *txtb)
{
    if (txtb->buf == NULL)
	return EOF;
    if (*(txtb->bufp) == '\0') {
	if (txtb->offset == txtb->ntext) {
	    txtb->buf = NULL;
	    return EOF;
	} else {
	    const void *vmax = vmaxget();
	    transferChars(txtb->buf,
			  translateCharWithOverride(STRING_ELT(txtb->text,
			                                       txtb->offset)));
	    txtb->bufp = txtb->buf;
	    txtb->offset++;
	    vmaxset(vmax);
	}
    }
    return *txtb->bufp++;
}
