/* $Id$
 *
 * This file is part of Rho, a project to refactor the R interpreter
 * into C++.  It may consist in whole or in part of program code and
 * documentation taken from the R project itself, incorporated into
 * Rho (and possibly MODIFIED) under the terms of the GNU General Public
 * Licence.
 * 
 * Rho is Copyright (C) 2008-14 Andrew R. Runnalls, subject to such other
 * copyrights and copyright restrictions as may be stated below.
 * 
 * Rho is not part of the R project, and bugs and other issues should
 * not be reported via r-bugs or other R project channels; instead refer
 * to the Rho website.
 * */

/*
 *  R : A Computer Language for Statistical Data Analysis
 *  Copyright (C) 1995, 1996, 1997,  Robert Gentleman and Ross Ihaka
 *                2007-2014 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"

#include "rho/RAllocStack.hpp"


/* 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 = static_cast<BufferListItem*>(malloc(sizeof(BufferListItem)))))
	    return 0;
	_new->next = nullptr;
	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 == nullptr || iob->start_buf == nullptr)
	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 == nullptr || iob->start_buf == nullptr)
	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 == nullptr) return 0;
    iob->start_buf = static_cast<BufferListItem*>(malloc(sizeof(BufferListItem)));
    if (iob->start_buf == nullptr) return 0;
    iob->start_buf->next = nullptr;
    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 == nullptr || iob->start_buf == nullptr)
	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';
}

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(translateChar(STRING_ELT(text, i))));
		if (k > l)
		    l = k;
	    }
	}
	vmaxset(vmax);
	txtb->vmax = vmax;
	txtb->buf = static_cast<unsigned char *>(RHO_alloc(l+2, sizeof(char))); /* '\n' and '\0' */
	txtb->bufp = txtb->buf;
	txtb->text = text;
	txtb->ntext = n;
	txtb->offset = 0;
	transferChars(txtb->buf,
		      translateChar(STRING_ELT(txtb->text, txtb->offset)));
	txtb->offset++;
	return 1;
    }
    else {
	txtb->vmax = vmaxget();
	txtb->buf = nullptr;
	txtb->bufp = nullptr;
	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 == nullptr)
	return EOF;
    if (*(txtb->bufp) == '\0') {
	if (txtb->offset == txtb->ntext) {
	    txtb->buf = nullptr;
	    return EOF;
	} else {
	    const void *vmax = vmaxget();
	    transferChars(txtb->buf,
			  translateChar(STRING_ELT(txtb->text, txtb->offset)));
	    txtb->bufp = txtb->buf;
	    txtb->offset++;
	    vmaxset(vmax);
	}
    }
    return *txtb->bufp++;
}
