/*      $NetBSD: inout.c,v 1.3 2018/02/06 17:58:19 christos Exp $       */
/*      $OpenBSD: inout.c,v 1.20 2017/02/26 11:29:55 otto Exp $ */

/*
* Copyright (c) 2003, Otto Moerbeek <[email protected]>
*
* 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>
__RCSID("$NetBSD: inout.c,v 1.3 2018/02/06 17:58:19 christos Exp $");

#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 void     src_freestream(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,
       src_freestream
};

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)
{
       (void)ungetc(src->lastchar, src->u.stream);
}

/* ARGSUSED */
static void
src_freestream(struct source *src)
{
}

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 ch, i;

       i = 0;
       while (i < BUFSIZ-1) {
               ch = src_getcharstring(src);
               if (ch == EOF)
                       break;
               buf[i++] = (char)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)
               (void)putc(lastchar, f);
}

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

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

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

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

       n = new_number();
       bn_check(BN_set_word(n->number, 0));

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

               if ('0' <= ch && ch <= '9')
                       v = (BN_ULONG)(ch - '0');
               else if ('A' <= ch && ch <= 'F')
                       v = (BN_ULONG)(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)
                       n->scale++;

               bn_check(BN_mul_word(n->number, base));

#if 0
               /* work around a bug in BN_add_word: 0 += 0 is buggy.... */
               if (v > 0)
#endif
                       bn_check(BN_add_word(n->number, v));
       }
       if (base != 10) {
               scale_number(n->number, (int)n->scale);
               for (i = 0; i < n->scale; i++)
                       (void)BN_div_word(n->number, base);
       }
       if (sign)
               negate(n);
       return n;
}

char *
read_string(struct source *src)
{
       size_t count, i, sz, new_sz;
       int ch;
       char *p;
       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++] = (char)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] = (char)(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   *int_part, *fract_part;
       int             digits;
       char            buf[11];
       size_t          sz, i;
       struct stack    stack;
       char            *p;

       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();
               stop = BN_new();
               if (mult == NULL || stop == NULL)
                       err(1, NULL);
               bn_check(BN_one(mult));
               bn_check(BN_one(stop));
               scale_number(stop, (int)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)
{
       (void)fputs(prefix, f);
       switch (value->type) {
       case BCODE_NONE:
               if (value->array != NULL)
                       (void)fputs("<array>", f);
               break;
       case BCODE_NUMBER:
               printnumber(f, value->u.num, base);
               break;
       case BCODE_STRING:
               (void)fputs(value->u.string, f);
               break;
       }
}

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

       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);
               (void)putc(ch, f);
               numbits -= 8;
       }
       BN_free(v);
}