/* ``The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved via the world wide web at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
 * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
 * AB. All Rights Reserved.''
 * 
 *     $Id$
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "erl_printf_term.h"
#include "sys.h"
#include "big.h"

#define PRINT_CHAR(CNT, FN, ARG, C)					\
do {									\
    int res__ = erts_printf_char((FN), (ARG), (C));			\
    if (res__ < 0)							\
	return res__;							\
    (CNT) += res__;							\
} while (0)

#define PRINT_STRING(CNT, FN, ARG, STR)					\
do {									\
    int res__ = erts_printf_string((FN), (ARG), (STR));			\
    if (res__ < 0)							\
	return res__;							\
    (CNT) += res__;							\
} while (0)

#define PRINT_BUF(CNT, FN, ARG, BUF, LEN)				\
do {									\
    int res__ = erts_printf_buf((FN), (ARG), (char*)(BUF), (LEN));	\
    if (res__ < 0)							\
	return res__;							\
    (CNT) += res__;							\
} while (0)

#define PRINT_POINTER(CNT, FN, ARG, PTR)				\
do {									\
    int res__ = erts_printf_pointer((FN), (ARG), (void *) (PTR));	\
    if (res__ < 0)							\
	return res__;							\
    (CNT) += res__;							\
} while (0)

#define PRINT_ULONG(CNT, FN, ARG, C, P, W, I)				\
do {									\
    int res__ = erts_printf_ulong((FN), (ARG), (C), (P), (W), (I));	\
    if (res__ < 0)							\
	return res__;							\
    (CNT) += res__;							\
} while (0)

#define PRINT_SLONG(CNT, FN, ARG, C, P, W, I)				\
do {									\
    int res__ = erts_printf_slong((FN), (ARG), (C), (P), (W), (I));	\
    if (res__ < 0)							\
	return res__;							\
    (CNT) += res__;							\
} while (0)

#define PRINT_DOUBLE(CNT, FN, ARG, C, P, W, I)				\
do {									\
    int res__ = erts_printf_double((FN), (ARG), (C), (P), (W), (I));	\
    if (res__ < 0)							\
	return res__;							\
    (CNT) += res__;							\
} while (0)

/* CTYPE macros */

#define LATIN1

#define IS_DIGIT(c)  ((c) >= '0' && (c) <= '9')
#ifdef LATIN1
#define IS_LOWER(c)  (((c) >= 'a' && (c) <= 'z') \
		      || ((c) >= 128+95 && (c) <= 255 && (c) != 247))
#define IS_UPPER(c)  (((c) >= 'A' && (c) <= 'Z') \
		      || ((c) >= 128+64 && (c) <= 128+94 && (c) != 247-32))
#else
#define IS_LOWER(c)  ((c) >= 'a' && (c) <= 'z')
#define IS_UPPER(c)  ((c) >= 'A' && (c) <= 'Z')
#endif

#define IS_ALNUM(c)  (IS_DIGIT(c) || IS_LOWER(c) || IS_UPPER(c))

/* We don't include 160 (non-breaking space). */
#define IS_SPACE(c)  (c == ' ' || c == '\n' || c == '\t' || c == '\r')

#ifdef LATIN1
#define IS_CNTRL(c)  ((c) < ' ' || (c) == 127 \
		      || ((c) >= 128 && (c) < 128+32))
#else
/* Treat all non-ASCII as control characters */
#define IS_CNTRL(c)  ((c) < ' ' || (c) >= 127)
#endif

#define IS_PRINT(c)  (!IS_CNTRL(c))

/* return 0 if list is not a non-empty flat list of printable characters */

static int
is_printable_string(Eterm list)
{
    int len = 0;
    int c;

    while(is_list(list)) {
	Eterm* consp = list_val(list);
	Eterm hd = CAR(consp);

	if (!is_byte(hd))
	    return 0;
	c = signed_val(hd);
	/* IS_PRINT || IS_SPACE would be another way to put it */
	if (IS_CNTRL(c) && !IS_SPACE(c))
	   return 0;
	len++;
	list = CDR(consp);
    }
    if (is_nil(list))
	return len;
    return 0;
}

/* print a atom doing what quoting is necessary */
static int print_atom_name(fmtfn_t fn, void* arg, Eterm atom, long *dcount)
{
    int n, i;
    int res;
    int need_quote;
    int pos;
    byte *s;
    byte *cpos;
    int c;

    res = 0;
    i = atom_val(atom);

    if ((i < 0) || (i >= atom_table_size()) ||  (atom_tab(i) == NULL)) {
	PRINT_STRING(res, fn, arg, "<bad atom index: ");
	PRINT_SLONG(res, fn, arg, 'd', 0, 1, (signed long) i);
	PRINT_CHAR(res, fn, arg, '>');
	return res;
    }

    s = atom_tab(i)->name;
    n = atom_tab(i)->len;

    *dcount -= atom_tab(i)->len;

    if (n == 0) {
	PRINT_STRING(res, fn, arg, "''");
	return res;
    }


    need_quote = 0;
    cpos = s;
    pos = n - 1;

    c = *cpos++;
    if (!IS_LOWER(c))
	need_quote++;
    else {
	while (pos--) {
	    c = *cpos++;
	    if (!IS_ALNUM(c) && (c != '_')) {
		need_quote++;
		break;
	    }
	}
    }
    cpos = s;
    pos = n;
    if (need_quote)
	PRINT_CHAR(res, fn, arg, '\'');
    while(pos--) {
	c = *cpos++;
	switch(c) {
	case '\'': PRINT_STRING(res, fn, arg, "\\'"); break;
	case '\\': PRINT_STRING(res, fn, arg, "\\\\"); break;
	case '\n': PRINT_STRING(res, fn, arg, "\\n"); break;
	case '\f': PRINT_STRING(res, fn, arg, "\\f"); break;
	case '\t': PRINT_STRING(res, fn, arg, "\\t"); break;
	case '\r': PRINT_STRING(res, fn, arg, "\\r"); break;
	case '\b': PRINT_STRING(res, fn, arg, "\\b"); break;
	case '\v': PRINT_STRING(res, fn, arg, "\\v"); break;
	default:
	    if (IS_CNTRL(c)) {
		PRINT_CHAR(res, fn, arg, '\\');
		PRINT_ULONG(res, fn, arg, 'o', 1, 3, (unsigned long) c);
	    }
	    else
		PRINT_CHAR(res, fn, arg, (char) c);
	    break;
	}
    }
    if (need_quote)
	PRINT_CHAR(res, fn, arg, '\'');
    return res;
}



static int
print_term(fmtfn_t fn, void* arg, Eterm obj, long *dcount)
{
    int res;
    int i;
    Uint32 *ref_num;
    Eterm* nobj;

    res = 0;

    if ((*dcount)-- <= 0)
	return res;

#ifdef HYBRID___NOT_ACTIVE
    /* Color coded output based on memory location */
    if(ptr_val(obj) >= global_heap && ptr_val(obj) < global_hend)
        PRINT_STRING(res, fn, arg, "\033[32m");
#ifdef INCREMENTAL
    else if(ptr_val(obj) >= inc_fromspc && ptr_val(obj) < inc_fromend)
        PRINT_STRING(res, fn, arg, "\033[33m");
#endif
    else if(IS_CONST(obj))
        PRINT_STRING(res, fn, arg, "\033[34m");
    else
        PRINT_STRING(res, fn, arg, "\033[31m");
#endif

    if (is_CP(obj)) {
	PRINT_STRING(res, fn, arg, "<cp/header:");
	PRINT_POINTER(res, fn, arg, obj);
	PRINT_CHAR(res, fn, arg, '>');
	return res;
    }

    switch (tag_val_def(obj)) {
    case NIL_DEF:
	PRINT_STRING(res, fn, arg, "[]");
	break;
    case ATOM_DEF: {
	int tres = print_atom_name(fn, arg, obj, dcount);
	if (tres < 0)
	    return tres;
	res += tres;
	if (*dcount <= 0)
	    return res;
	break;
    }
    case SMALL_DEF:
	PRINT_SLONG(res, fn, arg, 'd', 0, 1, (signed long) signed_val(obj));
	break;
    case BIG_DEF: {
	int print_res;
	char def_buf[64];
	char *buf, *big_str;
	Uint sz = (Uint) big_decimal_estimate(obj);
	sz++;
	if (sz <= 64)
	    buf = &def_buf[0];
	else
	    buf = erts_alloc(ERTS_ALC_T_TMP, sz);
	big_str = erts_big_to_string(obj, buf, sz);
	print_res = erts_printf_string(fn, arg, big_str);
	if (buf != &def_buf[0])
	    erts_free(ERTS_ALC_T_TMP, (void *) buf);
	if (print_res < 0)
	    return print_res;
	res += print_res;
	break;
    }
    case REF_DEF:
    case EXTERNAL_REF_DEF:
	PRINT_STRING(res, fn, arg, "#Ref<");
	PRINT_ULONG(res, fn, arg, 'u', 0, 1,
		    (unsigned long) ref_channel_no(obj));
	ref_num = ref_numbers(obj);
	for (i = ref_no_of_numbers(obj)-1; i >= 0; i--) {
	    PRINT_CHAR(res, fn, arg, '.');
	    PRINT_ULONG(res, fn, arg, 'u', 0, 1, (unsigned long) ref_num[i]);
	}
	PRINT_CHAR(res, fn, arg, '>');
	break;
    case PID_DEF:
    case EXTERNAL_PID_DEF:
	PRINT_CHAR(res, fn, arg, '<');
	PRINT_ULONG(res, fn, arg, 'u', 0, 1,
		    (unsigned long) pid_channel_no(obj));
	PRINT_CHAR(res, fn, arg, '.');
	PRINT_ULONG(res, fn, arg, 'u', 0, 1,
		    (unsigned long) pid_number(obj));
	PRINT_CHAR(res, fn, arg, '.');
	PRINT_ULONG(res, fn, arg, 'u', 0, 1,
		    (unsigned long) pid_serial(obj));
	PRINT_CHAR(res, fn, arg, '>');
	break;
    case PORT_DEF:
    case EXTERNAL_PORT_DEF:
	PRINT_STRING(res, fn, arg, "#Port<");
	PRINT_ULONG(res, fn, arg, 'u', 0, 1,
		    (unsigned long) port_channel_no(obj));
	PRINT_CHAR(res, fn, arg, '.');
	PRINT_ULONG(res, fn, arg, 'u', 0, 1,
		    (unsigned long) port_number(obj));
	PRINT_CHAR(res, fn, arg, '>');
	break;
    case LIST_DEF:
	if (is_printable_string(obj)) {
	    int c;
	    PRINT_CHAR(res, fn, arg, '"');
	    nobj = list_val(obj);
	    while (1) {
		if ((*dcount)-- <= 0)
		    return res;
		c = signed_val(*nobj++);
		if (c == '\n')
		    PRINT_STRING(res, fn, arg, "\\n");
		else {
		    if (c == '"')
			PRINT_CHAR(res, fn, arg, '\\');
		    PRINT_CHAR(res, fn, arg, (char) c);
		}
		if (is_not_list(*nobj))
		    break;
		nobj = list_val(*nobj);
	    }
	    PRINT_CHAR(res, fn, arg, '"');
	} else {
	    PRINT_CHAR(res, fn, arg, '[');
	    nobj = list_val(obj);
	    while (1) {
		int tres = print_term(fn, arg, *nobj++, dcount);
		if (tres < 0)
		    return tres;
		res += tres;
		if (*dcount <= 0)
		    return res;
		if (is_not_list(*nobj))
		    break;
		PRINT_CHAR(res, fn, arg, ',');
		nobj = list_val(*nobj);
	    }
	    if (is_not_nil(*nobj)) {
		int tres;
		PRINT_CHAR(res, fn, arg, '|');
		tres = print_term(fn, arg, *nobj, dcount);
		if (tres < 0)
		    return tres;
		res += tres;
		if (*dcount <= 0)
		    return res;
	    }
	    PRINT_CHAR(res, fn, arg, ']');
	}
	break;
    case TUPLE_DEF:
	nobj = tuple_val(obj);	/* pointer to arity */
	i = arityval(*nobj);	/* arity */
	PRINT_CHAR(res, fn, arg, '{');
	while (i--) {
	    int tres = print_term(fn, arg, *++nobj, dcount);
	    if (tres < 0)
		return tres;
	    res += tres;
	    if (*dcount <= 0)
		return res;
	    if (i >= 1)
		PRINT_CHAR(res, fn, arg, ',');
	}
	PRINT_CHAR(res, fn, arg, '}');
	break;
    case FLOAT_DEF: {
	    FloatDef ff;
	    GET_DOUBLE(obj, ff);
	    PRINT_DOUBLE(res, fn, arg, 'e', 6, 0, ff.fd);
	}
	break;
    case BINARY_DEF:
	{
	    ProcBin* pb = (ProcBin *) binary_val(obj);
	    if (pb->size == 1)
		PRINT_STRING(res, fn, arg, "<<1 byte>>");
	    else {
		PRINT_STRING(res, fn, arg, "<<");
		PRINT_ULONG(res, fn, arg, 'u', 0, 1, (unsigned long) pb->size);
		PRINT_STRING(res, fn, arg, " bytes>>");
	    }
	}
	break;
    case EXPORT_DEF:
	{
	    Export* ep = (Export *) (export_val(obj))[1];
	    Atom* module = atom_tab(atom_val(ep->code[0]));
	    Atom* name = atom_tab(atom_val(ep->code[1]));

	    PRINT_STRING(res, fn, arg, "#Fun<");
	    PRINT_BUF(res, fn, arg, module->name, module->len);
	    PRINT_CHAR(res, fn, arg, '.');
	    PRINT_BUF(res, fn, arg, name->name, name->len);
	    PRINT_CHAR(res, fn, arg, '.');
	    PRINT_SLONG(res, fn, arg, 'd', 0, 1,
			(signed long) ep->code[2]);
	    PRINT_CHAR(res, fn, arg, '>');
	}
	break;
    case FUN_DEF:
	{
	    ErlFunThing *funp = (ErlFunThing *) fun_val(obj);
	    Atom *ap = atom_tab(atom_val(funp->fe->module));

	    PRINT_STRING(res, fn, arg, "#Fun<");
	    PRINT_BUF(res, fn, arg, ap->name, ap->len);
	    PRINT_CHAR(res, fn, arg, '.');
	    PRINT_SLONG(res, fn, arg, 'd', 0, 1,
			(signed long) funp->fe->old_index);
	    PRINT_CHAR(res, fn, arg, '.');
	    PRINT_SLONG(res, fn, arg, 'd', 0, 1,
			(signed long) funp->fe->old_uniq);
	    PRINT_CHAR(res, fn, arg, '>');
	}
	break;
    default:
	PRINT_STRING(res, fn, arg, "<unknown:");
	PRINT_POINTER(res, fn, arg, obj);
	PRINT_CHAR(res, fn, arg, '>');
	break;
    }

    return res;
}

int
erts_printf_term(fmtfn_t fn, void* arg, unsigned long term, long precision)
{
    int res = print_term(fn, arg, (Uint) term, &precision);
    if (res < 0)
	return res;
    if (precision <= 0)
	PRINT_STRING(res, fn, arg, "... ");
    return res;
}
