/*	$OpenBSD: inout.c,v 1.18 2014/12/01 13:13:00 deraadt Exp $	*/

/*
 * Copyright (c) 2003, Otto Moerbeek <otto@drijf.net>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, 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.
 */

#include <sys/cdefs.h>
#include <openssl/ssl.h>
#include <ctype.h>
#include <err.h>
#include <string.h>

#include "extern.h"

#define MAX_CHARS_PER_LINE 68

static int	 lastchar;
static int	 charcount;

static int	 src_getcharstream(struct source *);
static void	 src_ungetcharstream(struct source *);
static char	*src_getlinestream(struct source *);
static int	 src_getcharstring(struct source *);
static void	 src_ungetcharstring(struct source *);
static char	*src_getlinestring(struct source *);
static void	 src_freestring(struct source *);
static void	 flushwrap(FILE *);
static void	 putcharwrap(FILE *, int);
static void	 printwrap(FILE *, const char *);
static char	*get_digit(u_long, int, u_int);

static struct vtable stream_vtable = {
	src_getcharstream,
	src_ungetcharstream,
	src_getlinestream,
	NULL
};

static struct vtable string_vtable = {
	src_getcharstring,
	src_ungetcharstring,
	src_getlinestring,
	src_freestring
};

void
src_setstream(struct source *src, FILE *stream)
{

	src->u.stream = stream;
	src->vtable = &stream_vtable;
}

void
src_setstring(struct source *src, char *p)
{

	src->u.string.buf = (u_char *)p;
	src->u.string.pos = 0;
	src->vtable = &string_vtable;
}

static int
src_getcharstream(struct source *src)
{

	return (src->lastchar = getc(src->u.stream));
}

static void
src_ungetcharstream(struct source *src)
{

	ungetc(src->lastchar, src->u.stream);
}

static char *
src_getlinestream(struct source *src)
{
	char buf[BUFSIZ];

	if (fgets(buf, BUFSIZ, src->u.stream) == NULL)
		return (bstrdup(""));
	return bstrdup(buf);
}

static int
src_getcharstring(struct source *src)
{

	src->lastchar = src->u.string.buf[src->u.string.pos];
	if (src->lastchar == '\0')
		return (EOF);
	else {
		src->u.string.pos++;
		return (src->lastchar);
	}
}

static void
src_ungetcharstring(struct source *src)
{

	if (src->u.string.pos > 0) {
		if (src->lastchar != '\0')
			--src->u.string.pos;
	}
}

static char *
src_getlinestring(struct source *src)
{
	char buf[BUFSIZ];
	int i, ch;

	i = 0;
	while (i < BUFSIZ-1) {
		ch = src_getcharstring(src);
		if (ch == EOF)
			break;
		buf[i++] = ch;
		if (ch == '\n')
			break;
	}
	buf[i] = '\0';
	return (bstrdup(buf));
}

static void
src_freestring(struct source *src)
{

	free(src->u.string.buf);
}

static void
flushwrap(FILE *f)
{

	if (lastchar != -1)
		putc(lastchar, f);
}

static void
putcharwrap(FILE *f, int ch)
{

	if (charcount >= MAX_CHARS_PER_LINE) {
		charcount = 0;
		fputs("\\\n", f);
	}
	if (lastchar != -1) {
		charcount++;
		putc(lastchar, f);
	}
	lastchar = ch;
}

static void
printwrap(FILE *f, const char *p)
{
	char *q;
	char buf[12];

	q = buf;
	strlcpy(buf, p, sizeof(buf));
	while (*q)
		putcharwrap(f, *q++);
}

struct number *
readnumber(struct source *src, u_int base, u_int bscale)
{
	struct number *n;
	BN_ULONG v;
	int ch;
	u_int iscale = 0;
	bool dot = false, sign = false;

	n = new_number();
	BN_zero(n->number);

	while ((ch = (*src->vtable->readchar)(src)) != EOF) {

		if ('0' <= ch && ch <= '9')
			v = ch - '0';
		else if ('A' <= ch && ch <= 'F')
			v = ch - 'A' + 10;
		else if (ch == '_') {
			sign = true;
			continue;
		} else if (ch == '.') {
			if (dot)
				break;
			dot = true;
			continue;
		} else {
			(*src->vtable->unreadchar)(src);
			break;
		}
		if (dot)
			iscale++;

		bn_check(BN_mul_word(n->number, base));
		bn_check(BN_add_word(n->number, v));
	}
	if (base == 10) {
		n->scale = iscale;
	} else {
		/* At this point, the desired result is n->number / base^iscale*/
		struct number *quotient, *divisor, *_n;
		BIGNUM *base_n, *exponent;
		BN_CTX *ctx;

		ctx = BN_CTX_new();
		base_n = BN_new();
		exponent = BN_new();
		divisor = new_number();
		BN_zero(base_n);
		BN_zero(exponent);

		bn_check(BN_add_word(base_n, base));
		bn_check(BN_add_word(exponent, iscale));
		bn_check(BN_exp(divisor->number, base_n, exponent, ctx));
		divisor->scale = 0;
		quotient = div_number(n, divisor, bscale);
		_n = n;
		n = quotient;

		/* 
		 * Trim off trailing zeros to yield the smallest scale without
		 * loss of accuracy
		 */
		while ( n->scale > 0 &&
			BN_mod_word(n->number, 10) == 0) {
			normalize(n, n->scale - 1);
		}

		free_number(_n);
		free_number(divisor);
		BN_CTX_free(ctx);
		BN_free(base_n);
		BN_free(exponent);
	}
	if (sign)
		negate(n);
	return (n);
}

char *
read_string(struct source *src)
{
	char *p;
	int count, ch, i, new_sz, sz;
	bool escape;

	escape = false;
	count = 1;
	i = 0;
	sz = 15;
	p = bmalloc(sz + 1);

	while ((ch = (*src->vtable->readchar)(src)) != EOF) {
		if (!escape) {
			if (ch == '[')
				count++;
			else if (ch == ']')
				count--;
			if (count == 0)
				break;
		}
		if (ch == '\\' && !escape)
			escape = true;
		else {
			escape = false;
			if (i == sz) {
				new_sz = sz * 2;
				p = breallocarray(p, 1, new_sz + 1);
				sz = new_sz;
			}
			p[i++] = ch;
		}
	}
	p[i] = '\0';
	return (p);
}

static char *
get_digit(u_long num, int digits, u_int base)
{
	char *p;

	if (base <= 16) {
		p = bmalloc(2);
		p[0] = num >= 10 ? num + 'A' - 10 : num + '0';
		p[1] = '\0';
	} else {
		if (asprintf(&p, "%0*lu", digits, num) == -1)
			err(1, NULL);
	}
	return (p);
}

void
printnumber(FILE *f, const struct number *b, u_int base)
{
	struct number *fract_part, *int_part;
	struct stack stack;
	char *p;
	char buf[11];
	size_t sz;
	unsigned int i;
	int digits;

	charcount = 0;
	lastchar = -1;
	if (BN_is_zero(b->number))
		putcharwrap(f, '0');

	int_part = new_number();
	fract_part = new_number();
	fract_part->scale = b->scale;

	if (base <= 16)
		digits = 1;
	else {
		digits = snprintf(buf, sizeof(buf), "%u", base-1);
	}
	split_number(b, int_part->number, fract_part->number);

	i = 0;
	stack_init(&stack);
	while (!BN_is_zero(int_part->number)) {
		BN_ULONG rem = BN_div_word(int_part->number, base);
		stack_pushstring(&stack, get_digit(rem, digits, base));
		i++;
	}
	sz = i;
	if (BN_is_negative(b->number))
		putcharwrap(f, '-');
	for (i = 0; i < sz; i++) {
		p = stack_popstring(&stack);
		if (base > 16)
			putcharwrap(f, ' ');
		printwrap(f, p);
		free(p);
	}
	stack_clear(&stack);
	if (b->scale > 0) {
		struct number *num_base;
		BIGNUM *mult, *stop;

		putcharwrap(f, '.');
		num_base = new_number();
		bn_check(BN_set_word(num_base->number, base));
		mult = BN_new();
		bn_checkp(mult);
		bn_check(BN_one(mult));
		stop = BN_new();
		bn_checkp(stop);
		bn_check(BN_one(stop));
		scale_number(stop, b->scale);

		i = 0;
		while (BN_cmp(mult, stop) < 0) {
			u_long rem;

			if (i && base > 16)
				putcharwrap(f, ' ');
			i = 1;

			bmul_number(fract_part, fract_part, num_base,
			    bmachine_scale());
			split_number(fract_part, int_part->number, NULL);
			rem = BN_get_word(int_part->number);
			p = get_digit(rem, digits, base);
			int_part->scale = 0;
			normalize(int_part, fract_part->scale);
			bn_check(BN_sub(fract_part->number, fract_part->number,
			    int_part->number));
			printwrap(f, p);
			free(p);
			bn_check(BN_mul_word(mult, base));
		}
		free_number(num_base);
		BN_free(mult);
		BN_free(stop);
	}
	flushwrap(f);
	free_number(int_part);
	free_number(fract_part);
}

void
print_value(FILE *f, const struct value *value, const char *prefix, u_int base)
{

	fputs(prefix, f);
	switch (value->type) {
	case BCODE_NONE:
		if (value->array != NULL)
			fputs("<array>", f);
		break;
	case BCODE_NUMBER:
		printnumber(f, value->u.num, base);
		break;
	case BCODE_STRING:
		fputs(value->u.string, f);
		break;
	}
}

void
print_ascii(FILE *f, const struct number *n)
{
	BIGNUM *v;
	int ch, i, numbits;

	v = BN_dup(n->number);
	bn_checkp(v);

	if (BN_is_negative(v))
		BN_set_negative(v, 0);

	numbits = BN_num_bytes(v) * 8;
	while (numbits > 0) {
		ch = 0;
		for (i = 0; i < 8; i++)
			ch |= BN_is_bit_set(v, numbits-i-1) << (7 - i);
		putc(ch, f);
		numbits -= 8;
	}
	BN_free(v);
}
