/*
 * Jeffrey Friedl
 * Omron Corporation			¥ª¥à¥í¥ó¡Ê³ô¡Ë
 * Nagaokakyoshi, Japan			¢©617Ä¹²¬µþ²¼³¤°õ»û
 *
 * jfriedl@nff.ncl.omron.co.jp
 *
 * This work is placed under the terms of the GNU General Purpose License
 * (the "GNU Copyleft").
 */
#include "config.h"
#include "assert.h"
#include <stdarg.h>
#include <ctype.h>
#include "input.h"
#include "output.h"

#ifdef USE_LOCAL_OUTPUT

/*
 * Output routines.
 */
#define OUTBUF_SIZE    500
#define EXTRA_BUFFER	12
static unsigned char outbuf[OUTBUF_SIZE + EXTRA_BUFFER];
static unsigned char *nextout = &outbuf[0];
#define bufend (&outbuf[OUTBUF_SIZE])

#define THREE_BYTE_HI          143
#define HALF_WIDTH_KATA_HI     142

unsigned const char *jis_start_208   = (unsigned const char *)"\33$@";
unsigned const char *jis_start_212   = (unsigned const char *)"\33$(D";
unsigned const char *jis_start_ASCII = (unsigned const char *)"\33(B";
unsigned const char *jis_start_kana  = (unsigned const char *)"\33(I";

int flush_on_newline = 1;
static int output_fd = 1 /* stdout */;
static unsigned long output_style =
    JIS_OUTPUT | /* default output is JIS */
    JIS_ASCII |  /* use ASCII, not (JIS-ROMAN) when appropriate */
    SHOW_NONDISPLAYABLE_CODES |
    NO_0212_1990 |
    PASS_HW_KATANANA;

static int extra_fd = NO_OUTPUT_FILE;

static void nondisplayable3(unsigned char a, unsigned char b, unsigned char c)
{
    switch(output_style & _NONDISPLAYABLE)
    {
      default:
	soft_assert(0);
	break;

      case ELIDE_NONDISPLAYABLE:
	break;

      case OUTPUT_NONDISPLAYABLE:
	*nextout++ = a;
	*nextout++ = b;
	*nextout++ = c;
	break;

      case SHOW_NONDISPLAYABLE_CODES:
	outputf("\\%03\\%03\\%03", a, b, c);
	break;

      case MARK_NONDISPLAYABLE:
	output("¡ú");
	break;
    }
}

static void flush_raw_output(void)
{
    unsigned len = nextout - outbuf;
    if (len != 0)
    {
	kibishii_assert(output_fd_valid(output_fd)||output_fd==NO_OUTPUT_FILE);
	if (output_fd_valid(output_fd))
	    if (write(output_fd, outbuf, len) < 0)
	    {
		output_fd = OUTPUT_FILE_ERROR;
		kibishii_assert(0);
	    }
	kibishii_assert(output_fd_valid(extra_fd) || extra_fd==NO_OUTPUT_FILE);
	if (output_fd_valid(extra_fd))
	    if (write(extra_fd, outbuf, len) < 0)
	    {
		extra_fd = OUTPUT_FILE_ERROR; /* quietly shut it down */
		kibishii_assert(0);
	    }
	nextout = outbuf;
    }
}

#ifndef PROVIDE_PAGER
#  define PROVIDE_PAGER 1
#endif

#if PROVIDE_PAGER
static int column, line;
extern unsigned (*_real_output_char_function)(unsigned char);

static unsigned screen_height = 20;
static unsigned screen_width = 80;

#define SPECIAL_PRINT      0x01
#define REGULAR_PRINT      0x02
#define NO_STOP            0x04
#define TRANSPARENT        0x08

/* for watching out for ANSI escape sequences */
#define SEEN_ESCAPE        0x10
#define WAITING_FOR_M      0x20
#define isNORMAL           (!(pager_status & (SEEN_ESCAPE|WAITING_FOR_M)))
#define hasSEEN_ESCAPE     (pager_status & SEEN_ESCAPE)
#define isWAITING_FOR_M    (pager_status & WAITING_FOR_M)
#define setNORMAL          (pager_status &= ~(SEEN_ESCAPE|WAITING_FOR_M))
#define setSEEN_ESCAPE     ((pager_status &= ~WAITING_FOR_M),\
			    (pager_status |= SEEN_ESCAPE))
#define setWAITING_FOR_M   ((pager_status |= WAITING_FOR_M), \
			    (pager_status &= ~SEEN_ESCAPE))

static unsigned pager_status = REGULAR_PRINT;

#define NOTE "--MORE [space=more, return=one more line, (c)ontinue, (q)uit]--"
#define SHORT_NOTE "--MORE [space,return,c,q]--"
#define VERYSHORT_NOTE "-MORE-"

static __inline__ void more(void)
{
    const char *note;
    unsigned len;
    int i;

    if (screen_width > sizeof(NOTE))
	note = NOTE;
    else if (screen_width > sizeof(SHORT_NOTE))
	note = SHORT_NOTE;
    else
	note = VERYSHORT_NOTE;
    len = strlen(note);

    for (i = 0; i < len; i++)
	_real_output_char_function(note[i]);

    flush_pending_input();
    flush_raw_output();

    for (;;)
    {
	extern volatile unsigned apply_regex_abort;/*kludge!*/
	switch (next_cooked_input_byte())
	{
	  default:
	    continue;
	  case '\r':
	  case '\n': line = screen_height - 1;               break;
	  case ' ' : line = 0;                               break;
	  case 'c' : line = 0; pager_status |= NO_STOP;      break;

	  case 0:
	  case 'q' : line = 0;
	             pager_status &= ~REGULAR_PRINT;
		     apply_regex_abort = 1;                  break;
	}
	break;
    }

    /* erase note */
    for (i = 0; i < len; i++)
    {
	_real_output_char_function('\b');
	_real_output_char_function(' ');
	_real_output_char_function('\b');
    }
}


static void pager_output_char_function(unsigned char c)
{
    unsigned column_increment;
    if (c != 0 && !(pager_status & (SPECIAL_PRINT|REGULAR_PRINT)))
	return;

    if (pager_status & NO_STOP)
    {
	(*_real_output_char_function)(c);
	return;
    }

    if (((line == screen_height) && (column > 0 || c >= '\33')) ||
	line > screen_height)
    {
	int logging_on = output_fd_valid(extra_fd);
	int old_extra_fd; /* "may be used uninitialized" warning OK here */

	if (logging_on)
	{
	    flush_raw_output();
	    old_extra_fd = extra_fd;
	    extra_fd = NO_OUTPUT_FILE;
	}

	more();

	if (logging_on)
	{
	    flush_raw_output();
	    extra_fd = old_extra_fd;
	}

	/* if they 'q', then we'll not continue */
	if (!(pager_status & (SPECIAL_PRINT|REGULAR_PRINT)))
	    return;
    }

    if ((c == '\n' || c == '\r') && column == screen_width)
    {
	/* don't want to output a newline at this point,
	   since we know we're already there */
	column = 0;
    } else 
	column_increment = (*_real_output_char_function)(c);

    while (column >= screen_width)
    {
	line++;
	column -= screen_width;
    }

    if (!(pager_status & TRANSPARENT))
    {
	static mcount = 0;

	if (c == '\33')
	    setSEEN_ESCAPE;
	else if (c == '[' && hasSEEN_ESCAPE) {
	    setWAITING_FOR_M;
	    mcount = 0;
	}
	else if (isWAITING_FOR_M && c != '\n' && c != '\r') {
	    if (mcount++ > /* some reasonably large number */20 || c == 'm')
		setNORMAL;
	}
	else switch(c)
	{
	  default:
	    column += column_increment;
	    break;

	  case '\n':
	    line++;
	    /* fallthrough */
	  case '\r':
	    setNORMAL;
	    column = 0;
	    break;
	  case '\b':
	    if (column > 0)
		column--;
	    break;
	}
    }
}

void output_pager_reset_more(void)
{
    line = 0;
    pager_status &= ~NO_STOP;
    pager_status |= REGULAR_PRINT;
}

int output_pager_transparent(int t)
{
    int was = (pager_status & TRANSPARENT) ? 1 : 0;
    if (t)
	pager_status |= TRANSPARENT;
    else
	pager_status &= ~TRANSPARENT;
    return was;
}

unsigned output_pager_lines(unsigned new)
{
    unsigned old = screen_height;
    if (new)
	screen_height = new;
    return old;
}

unsigned output_pager_columns(unsigned new)
{
    unsigned old = screen_width;
    if (new)
	screen_width = new;
    return old;
}

#endif /* PROVIDE_PAGER */


/************************************************************************/
/************************************************************************/


/*
 * Just pass it through.
 */
static unsigned output_euc_as_simple_euc(unsigned char c)
{
    unsigned retval = 0;

    if (c != 0)
    {
        #if PROVIDE_PAGER
	if (c != /*CodeSet3*/143 && c >= ' ')
	    retval = 1;
        #endif
        *nextout++ = c;
        if ((c == '\n' && flush_on_newline) || nextout >= bufend)
	    flush_raw_output();
    }
    return retval;
}


/*
 *
 */
static unsigned output_euc_as_jis(unsigned char c)
{
    static unsigned char hi = 0;
    static enum { ASCII, KATAKANA, _X_ , JIS208, JIS212 } mode = ASCII;
    unsigned retval = 0;

    if ((c & 0x80) == 0)
    {
	if (mode != ASCII)
	{
	    unsigned const char *ptr = jis_start_ASCII;
	    while (*ptr)
		*nextout++ = *ptr++;
	    mode = ASCII;
	}

	if (c == 0)
	    return 0;

	*nextout++ = c;
        #if PROVIDE_PAGER
	if (c >= ' ')
	    retval = 1;
        #endif

	if ((c == '\n' && flush_on_newline) ||  nextout >= bufend)
	    flush_raw_output();
	return retval;
    }

    /* we have a multibyte character */
    if (hi == 0)
    {
	/* the first byte of a 2- or 3-byte character */
	hi = c;
	return 0;
    }

    if (hi == THREE_BYTE_HI)
    {
	static unsigned char mid = 0;
	if (mid == 0)
	{
	    /* the 2nd byte of a three-byte character */
	    mid = c;
	    return 0;
	}

	/* We now have a full three-byte JIS X 0212-1990 character. */
	if (output_style & NO_0212_1990)
	{
	    unsigned char a = hi, b = mid;
	    hi = mid = 0;
	    nondisplayable3(a, b, c);
	} else {
	    if (mode != JIS212)
	    {
		unsigned const char *ptr = jis_start_212;
		while (*ptr)
		    *nextout++ = *ptr++;
		mode = JIS212;
	    }

	    *nextout++ = mid & 0x7f;
	    *nextout++ =   c & 0x7f;
	    hi = mid = 0;
	    #if PROVIDE_PAGER
		retval = 2;
	    #endif
	}
    } else if (hi == HALF_WIDTH_KATA_HI) {
	/* Have a half-width katakana */
	if (mode != KATAKANA)
	{
	    unsigned const char *ptr = jis_start_kana;
	    while (*ptr)
		*nextout++ = *ptr++;
	    mode = KATAKANA;
	}
	/* could put a HW -> FW kana converter here */
	*nextout++ = c & 0x7f;
	hi = 0;
	#if PROVIDE_PAGER
	    retval = 1;
	#endif
    } else {
	/* have "regular" JIS X 0208 two-byte kanji */
	if (mode != JIS208)
	{
	    unsigned const char *ptr = jis_start_208;
	    while (*ptr)
		*nextout++ = *ptr++;
	    mode = JIS208;
	}

	*nextout++ = hi & 0x7f;
	*nextout++ =  c & 0x7f;
	hi = 0;
	#if PROVIDE_PAGER
	    retval = 2;
	#endif
    }
    if (nextout >= bufend)
       flush_raw_output();
    return retval;
}

static unsigned output_euc_as_sjis(unsigned char c)
{
    static unsigned char hi = 0;
    unsigned retval = 0;

    if ((c & 0x80) == 0)
    {
	if (c == 0)
    	    return 0;
	*nextout++ = c;
	#if PROVIDE_PAGER
	if (c >= ' ')
	    retval = 1;
	#endif

	if ((c == '\n' && flush_on_newline) ||  nextout >= bufend)
	    flush_raw_output();
	return retval;
    }

    /* we have a multibyte character */
    if (hi == 0)
    {
	/* the first byte of a 2- or 3-byte character */
	hi = c;
	return 0;
    }

    if (hi == THREE_BYTE_HI)
    {
	static unsigned char mid = 0;
	if (mid == 0)
	{
	    /* the 2nd byte of a three-byte character */
	    mid = c;
	    return 0;
	}
	/*
	 * We now have a full three-byte JIS X 0212-1990 character, which
	 * is undisplayable in SJIS
	 */
	{
	    unsigned char a = hi, b = mid;
	    hi = mid = 0;
	    nondisplayable3(a, b, c);
	}
    } else if (hi == HALF_WIDTH_KATA_HI) {
	/* Have a half-width katakana */
	/* could put a HW -> FW kana converter here */
	*nextout++ = c;
	hi = 0;
	#if PROVIDE_PAGER
	    retval = 1;
	#endif
    } else {
	/* have "regular" JIS X 0208 two-byte kanji */
	hi &= 0x7f;
	c &= 0x7f;
	*nextout++ = ((hi + 1) >> 1) + (hi < 95 ? 112 : 176);
	*nextout++ = c + ((hi & 1) ? (c > 95 ? 32 : 31) : 126);
	hi = 0;
	#if PROVIDE_PAGER
	    retval = 2;
	#endif
    }
    if (nextout >= bufend)
       flush_raw_output();
    return retval;
}

/***************************************************************/

unsigned (*_output_char_function)(unsigned char) = output_euc_as_jis;

int output(const char *string)
{
    unsigned char *ptr = (unsigned char *)string;
    while (*ptr)
	outchar(*ptr++);
    return 0;
}


void output_buffer(const unsigned char *start, const unsigned char *end)
{
    while (start < end)
	outchar(*start++);
}


void flush_output(void)
{
    outchar(0); /* flush to ASCII mode, if it matters */
    flush_raw_output();
}

#ifdef USE_VSNPRINTF
    int outputf(const char *fmt, ...)
    {
	va_list ap;
	unsigned char buffer[500];
	unsigned char *start = buffer, *end;
	unsigned count;

	va_start(ap, fmt);
	count = vsnprintf((void*)buffer, sizeof(buffer), (void *)fmt, ap);
	end = start + count;
	va_end(ap);
	while (start < end)
	    outchar(*start++);
	return count;
    }


    void warn(const char *fmt, ...)
    {
	va_list ap;
	unsigned char buffer[500];
	unsigned char *start = buffer, *end;
	unsigned count;
	unsigned fd = output_fd;

	flush_output();
	va_start(ap, fmt);
	count = vsnprintf((void*)buffer, sizeof(buffer), (void*)fmt, ap);
	end = start + count;
	va_end(ap);

	{
	    #if PROVIDE_PAGER
		unsigned old_status = pager_status;
		pager_status |= SPECIAL_PRINT;
	    #endif
	    output_fd = 2;
	    while (start < end)
		outchar(*start++);
	    flush_output();
	    output_fd = fd;
	    #if PROVIDE_PAGER
		pager_status = old_status;
	    #endif
	}
    }

    __volatile__ void die(const char *fmt, ...)
    {
	va_list ap;
	unsigned char buffer[500];
	unsigned char *start = buffer, *end;
	unsigned count;

	flush_output();
	va_start(ap, fmt);
	count = vsnprintf((void*)buffer, sizeof(buffer), (void *)fmt, ap);
	end = start + count;
	va_end(ap);
	#if PROVIDE_PAGER
	    pager_status |= SPECIAL_PRINT | NO_STOP;
	#endif
	output_fd = 2;
	while (start < end)
	    outchar(*start++);
	flush_output();
	exit(1);
    }

#else  /* USE_VSNPRINTF */

/*
 * vfcnprintf -- Vector Function Print Formatted.
 *
 * Given a format string, a vector of arguments, and an output-this-byte
 * function, format and print a'la printf.
 * In the stdio world, it's about the same as (untested!)
 *
 *    printf(const char *fmt, ...)
 *    {
 *       va_list ap;
 *       va_start(ap, fmt);
 *       #undef putchar
 *       vfcnprintf(putchar, fmt, ap)
 *    }
 *
 * The number of bytes sent to FUNCTION is returned. If FUNCTION is null,
 * the number of bytes that *would be* sent is returned.
 *
 * It's simple in that for non-%s formats, it just does an internal
 * sprintf of whatever. For %s, it does it itself so that it doesn't have
 * to worry about buffer limits.
 *
 * Jeffrey Friedl (jfriedl@omron.co.jp)
 * Feb 1994
 */
static int
vfcnprintf(unsigned (*function)(unsigned char), const char *fmt,va_list ap)
{
    unsigned char C; /* current character from the format string */
    unsigned bytes_out = 0; /* number of bytes output */
    const unsigned char *format = (const unsigned char *)fmt;

    #define NEXT_ARG(type) (va_arg(ap, type))

    /* Send the given BYTE to the output function (if it exists) */
    #define out(BYTE)                                                        \
    macro_start {                                                            \
	unsigned char c = (BYTE); /* BYTE gets evaluated exactly once */     \
	if (function)                                                        \
	    (void)(*function)(c);                                            \
	bytes_out++;                                                         \
    } macro_end

    /*
     * Run though the format string, either dumping each character, or
     * interpreting a %-format.
     */
    while (C = *format++, C != '\0') if (C != '%') {
	out(C);
    } else	{
	const unsigned char *fstart = format;
	unsigned argsneeded = 0;
	int islong = 0;
	int arg1, arg2; /* "may be used uninitialized" warning OK here */

	if (*format == '-')	/* allow an optional leading '-' */
	    format++;

	if (*format == '*') {   /* allow an optional field size */
	    format++;
	    argsneeded++;
	} else while (isascii(*format) && isdigit(*format))
	    format++;

	if (*format == '.')     /* allow a minimum width */
	{
	    format++;
	    if (*format == '*') {
		format++;
		argsneeded++;
	    } else while (isascii(*format) && isdigit(*format))
		format++;
	}

	if (*format == 'l') {	/* allow an optional 'l' */
	    islong = 1;
	    format++;
	}

	/*
	 * If there were '*' arg markers, nab them from the argument list.
	 */
	if (argsneeded)
	{
	    arg1 = NEXT_ARG(int);
	    if (argsneeded == 2)
		arg2 = NEXT_ARG(int);
	}

	#define do_type(TYPE)                                                \
	macro_start {                                                        \
	    unsigned char f[128];                                            \
	    unsigned char b[128];                                            \
	    unsigned char *ptr = f;                                          \
	    TYPE value = NEXT_ARG(TYPE);                                     \
                                                                             \
	    for (*ptr++ = '%'; fstart < format; fstart++)                    \
		*ptr++ = *fstart;                                            \
	    *ptr = 0;                                                        \
	    switch(argsneeded)                                               \
	    {                                                                \
	      case 0: sprintf(b, f,             value); break;               \
	      case 1: sprintf(b, f, arg1,       value); break;               \
	      case 2: sprintf(b, f, arg1, arg2, value); break;               \
	    }                                                                \
	    for (ptr = b; *ptr; ptr++)                                       \
		out(*ptr);                                                   \
	} macro_end



	switch(*format++)
	{
	  case '%': out('%'); break;
	  case 'c': out(NEXT_ARG(unsigned int)); break;

	  default: /* unknown */
	    out('?');
	    while (fstart < format)
		out(*fstart++);
	    out('?');
	    break;

	  /* 'int' sized things */
	  case 'd': case 'x': case 'o': case 'u':
	    if (!islong)
	    {
		do_type(int);
		break;
	    }
	    /* else FALLTHROUGH */

	  /* 'long int' sized things */
	  case 'D': case 'X': case 'O': case 'U':
	    do_type(long int);
	    break;

	  case 'F':	  case 'f':
	  case 'E':	  case 'e':
	  case 'G':	  case 'g':
	    do_type(double);
	    break;


	  /* string */
	  case 's':
	  case 'n': /* errno string */
	  case 'N': /* errno string */
	    {
		extern char *sys_errlist[];
		extern int errno, sys_nerr;
		char temp_error_buff[20];
		const char *str;

		if (format[-1] == 's')
		    str = NEXT_ARG(const char *);
		else
		{
		    int errval;
		    if (format[-1] == 'N')
			errval = NEXT_ARG(int);
		    else
			errval = errno;

		    if (errval >= 0 && errval < sys_nerr)
			str = sys_errlist[errval];
		    else
		    {
			sprintf(temp_error_buff, "#%d", errval);
			str = temp_error_buff;
		    }
		}

		if (format[-2] == '%')
		{
		    /* easy case... just dump the string */
		    while (*str)
			out(*str++);
		}
		else
		{
		    /* shit, have to go and interpret the format myself */
		    int leftadjust = 0;
		    int minfieldwidth = 0;
		    const char *strmax = &str[/*BIG*/0x7fff];
		    const char *ptr;

		    if (*fstart == '-')
			leftadjust = *fstart++;
		    if (*fstart == '*')
		    {
			fstart++;
			if (minfieldwidth = arg1, minfieldwidth < 0) {
			    leftadjust = !leftadjust;
			    minfieldwidth = -minfieldwidth;
			}
		    } else {
			while (isascii(*fstart) && isdigit(*fstart))
			    minfieldwidth = minfieldwidth*10 + (*fstart++-'0');
		    }
		    if (*fstart == '.')
		    {
			fstart++;
			if (*fstart == '*')
			{
			    strmax = &str[argsneeded == 1 ? arg1 : arg2];
			    fstart++;
			} else {
			    int num = 0;
			    while (isascii(*fstart) && isdigit(*fstart))
				num = num * 10 + (*fstart++ - '0');
			    strmax = &str[num];
			}
		    }

		    kibishii_assert(*fstart == 's' || *fstart == 'N');

		    if (!leftadjust)
		    {
			int len;
			for (ptr=  str; *ptr && ptr < strmax; ptr++)
			    ;
			len = ptr - str;
			while (len < minfieldwidth)
			{
			    out(' ');
			    minfieldwidth--;
			}
		    }

		    ptr = str;
		    while (*ptr && ptr < strmax)
			out(*ptr++);
		    minfieldwidth -= (ptr - str);
		    while (minfieldwidth-- > 0)
			out(' ');
		}
		break;
	    }
	}
    }
    return bytes_out;
}


int outputf(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    return vfcnprintf(*_output_char_function, fmt, ap);
}

void warn(const char *fmt, ...)
{
    unsigned fd = output_fd;
    va_list ap;
    #if PROVIDE_PAGER
	unsigned old_status = pager_status;
	pager_status |= SPECIAL_PRINT;
    #endif

    va_start(ap, fmt);
    flush_output();
    output_fd = 2;

    (void)vfcnprintf(*_output_char_function, fmt, ap);

    flush_output();
    output_fd = fd;

    #if PROVIDE_PAGER
	pager_status = old_status;
    #endif
}

__volatile__ void die(const char *fmt, ...)
{
    va_list ap;
    #if PROVIDE_PAGER
	pager_status |= SPECIAL_PRINT;
    #endif

    va_start(ap, fmt);
    flush_output();
    output_fd = 2;

    (void)vfcnprintf(*_output_char_function, fmt, ap);
    flush_output();
    exit(1);
}
#endif /* USE_VSNPRINTF */


#if PROVIDE_PAGER
static unsigned (*_real_output_char_function)(unsigned char) = 0;

/*
 * output_pager_status(0) turns off and reports previous state.
 * output_pager_status(1) turns on and reports previous state.
 * output_pager_status(-1) reports current state.
 */
int output_pager_status(int want)
{
    int was = _real_output_char_function != 0;
    if (want >= 0 && was != want)
    {
	if (want) {
	    _real_output_char_function = _output_char_function;
	    _output_char_function = (unsigned (*)(unsigned char))
		pager_output_char_function;
	    line = column = 0;
	} else {
	    _output_char_function = _real_output_char_function;
	    _real_output_char_function = 0;
	}
    }
    return was;
}
#endif

unsigned long select_output_style(unsigned long mods)
{
    unsigned (**function_pointer)(unsigned char) =
    #if PROVIDE_PAGER
	_real_output_char_function ? &_real_output_char_function :
    #endif
	                             &_output_char_function;
    unsigned long old_output_style = output_style;

    if (mods == INQUIRE_ONLY)
	return output_style;

    #define update(RANGE)                                                    \
	if (mods & (RANGE))                                                  \
	    output_style = (output_style & ~(RANGE)) | (mods & (RANGE));

    update(_BASIC_OUTPUT_TYPE);
    update(_JIS_KANJI_STYLE);
    update(_JIS_ENGLISH_STYLE);
    update(_NONDISPLAYABLE);
    update(_0212_1990);
    update(_KATAKANA);

    switch(output_style & _BASIC_OUTPUT_TYPE) {
      default: soft_assert(0); break;
      case SJIS_OUTPUT:
	*function_pointer = output_euc_as_sjis;
	break;
      case EUC_OUTPUT:
	*function_pointer = output_euc_as_simple_euc;
	break;
      case JIS_OUTPUT:
      	*function_pointer = output_euc_as_jis;
	switch(output_style & _JIS_KANJI_STYLE)
	{
	  default:
	    kibishii_assert(0);
	    break;
	  case JIS_1978_OUTPUT:
	    jis_start_208 = (unsigned const char *)"\33$@";
	    break;
	  case JIS_1983_OUTPUT:
	    jis_start_208 = (unsigned const char *)"\33$B";
	    break;
	  case JIS_1990_OUTPUT:
	    jis_start_208 = (unsigned const char *)"\33$2\33$B";
	    break;
	}
	switch(output_style & _JIS_ENGLISH_STYLE)
	{
	  default:
	    kibishii_assert(0);
	    break;
	  case JIS_ROMAN:
	    jis_start_ASCII = (unsigned const char *)"\33(J";
	    break;
	  case JIS_ASCII:
	    jis_start_ASCII = (unsigned const char *)"\33(B";
	    break;
	}
	break;
    }

    if ((output_style & _KATAKANA) != PASS_HW_KATANANA)
	warn("[half-width katakana support not implemented]\n");

    return old_output_style;
}

void show_output_style(void)
{
    int is_sjis = 0;
    int has_212_support = 0;

    switch(output_style & _BASIC_OUTPUT_TYPE) {
      default: soft_assert(0); break;
      case SJIS_OUTPUT: output("Shift JIS"); is_sjis = 1; break;
      case EUC_OUTPUT: output("EUC"); break;

      case JIS_OUTPUT:
	switch(output_style & _JIS_KANJI_STYLE)
	{
	  default: soft_assert(0); break;
	  case JIS_1978_OUTPUT: output("JIS (1978"); break;
	  case JIS_1983_OUTPUT: output("JIS (1983"); break;
	  case JIS_1990_OUTPUT: output("JIS (1990"); break;
	}
	switch(output_style & _JIS_ENGLISH_STYLE)
	{
	  default: soft_assert(0); break;
	  case JIS_ROMAN: output(", roman)"); break;
	  case JIS_ASCII: output(", ASCII)"); break;
	}
	break;
    }

    switch (output_style & _0212_1990)
    {
      default: soft_assert(0); break;
      case SUPPORT_0212_1990: outputf(" (X212"); has_212_support = 1; break;
      case NO_0212_1990:      outputf(" (!X212");                     break;
    }

    switch (output_style & _KATAKANA)
    {
      default: soft_assert(0); break;
      case PASS_HW_KATANANA: 	     output(", hwk");        break;
      case ELIDE_HW_KATAKANA: 	     output(", !hwk");  break;
      case FOLD_HW_KATAKANA_TO_FULL: output(", hwk converted"); break;
    }

    if (is_sjis || !has_212_support) {
	output(", unsupported ");
	switch(output_style & _NONDISPLAYABLE) {
	  default: soft_assert(0); break;
	  case ELIDE_NONDISPLAYABLE: output("stripped"); break;
	  case OUTPUT_NONDISPLAYABLE: output("passed"); break;
	  case SHOW_NONDISPLAYABLE_CODES: output("as codes"); break;
	  case MARK_NONDISPLAYABLE: output("marked");
	}
    }
    outchar(')');
}

int set_extra_output_file(int fd)
{
    int old = extra_fd;
    if (fd != JUST_CHECKING_OUTPUT_FILE)
	extra_fd = fd;
    return old;
}

int set_normal_output_file(int fd)
{
    int old = output_fd;
    if (fd != JUST_CHECKING_OUTPUT_FILE)
	output_fd = fd;
    return old;
}

#undef outchar
void outchar(unsigned char c)
{
    (*_output_char_function)(c);
}

#else /* not USE_LOCAL_OUTPUT */

volatile void die(const char *fmt, ...)
{
    va_list ap;
    unsigned char buffer[500];
    va_start(ap, fmt);
    vsnprintf(buffer, sizeof(buffer), (unsigned char *)fmt, ap);
    va_end(ap);
    fputs(buffer, stderr);
    exit(1);
}

void warn(const char *fmt, ...)
{
    va_list ap;
    unsigned char buffer[500];
    va_start(ap, fmt);
    vsnprintf(buffer, sizeof(buffer), (unsigned char *)fmt, ap);
    va_end(ap);
    fputs(buffer, stderr);
}

void output_buffer(const unsigned char *start, const unsigned char *end)
{
    while (start < end)
	putchar(*start++);
}

#endif /* not USE_LOCAL_OUTPUT */
