/***********************************************
 *  buff.c 
 *                                            
 *  Copyright 2001 Eric Kratzer.               
 *
 *  This file is part of imp3sh.              
 *  Includes Xaudio software Copyright (C)
 *           1996-2000 Xaudio Corporation
 *           All Rights Reserved.
 *                                            
 *  imp3sh is free software; you can distribute
 *  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.
 *
 *  imp3sh 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 recieved a copy of the GNU
 *  General Public License along with imp3sh; if
 *  not, write to the Free Software Foundation,
 *  Inc., 59 Temple Place, Suite 330, Boston MA
 *  02111-1307 USA
 ************************************************/

/************************************
 *          Include Files           *
 ************************************/

#include "imp3sh.h"

/************************************
 *       External References        *
 ************************************/

extern CommandDef com[];

extern DCharBuf *out_buffer, *decoder_log, *console_buf;

extern int tty_height, tty_width, tab_length;

extern flag_t prompt_now, ignore_char_input, ignore_hist_input, piped_io_flag;

/************************************
 *         Local Variables          *
 ************************************/

static char mess_msg[INFO_SIZE], tmpstr_bf[COMM_SIZE], tmpstr_bu[COMM_SIZE];

/************************************
 *   DChar buffer manipulation      * 
 ************************************/

unsigned int g_dbuf_bytec(DCharBuf * dcb)
{				/* get byte count */
    unsigned int bytec;

    bytec = 0;

    if (dcb) {
	bytec += dcb->bytec;
	bytec += dcb->size * sizeof(DCBNode);
	bytec += sizeof(DCharBuf);
	bytec += get_strlen(dcb->name);
    }

    return bytec;
}

char g_dbuf_info(DCharBuf * dcb, int pos)
{
    return (dcb && pos >= 0 && pos < dcb->len) ? dcb->buf[pos].info : 0;
}

short g_dbuf_len(DCharBuf * dcb, int pos)
{
    return (dcb && pos >= 0 && pos < dcb->len) ? dcb->buf[pos].len : 0;
}

char *g_dbuf(DCharBuf * dcb, int pos)
{				/* get */
    return (dcb && pos >= 0 && pos < dcb->len) ? dcb->buf[pos].str : NULL;
}

void c_dbuf(DCharBuf * dcb)
{				/* clear */
    if (dcb) dcb->pos = dcb->len = dcb->var1 = 0;
}

int compare_dbuf(const void *vel1, const void *vel2)
{
    return strcmp(((DCBNode *) (vel1))->str, ((DCBNode *) (vel2))->str);
}

void sort_dbuf(DCharBuf * dcb)
{
    if (dcb && dcb->len > 1)
	qsort((void *) dcb->buf, dcb->len, sizeof(DCBNode), compare_dbuf);
}

void sp_dbuf(DCharBuf * dcb, int index, int val)
{
    if (dcb && index >= 0 && index < dcb->len)
	dcb->buf[index].info = val;
}

void verify_dbuf(DCharBuf * dcb)
{
    if (!dcb)
	return;
 
    if (dcb->len > dcb->size)
	dcb->len = dcb->size;
    if (dcb->pos >= dcb->len)
	dcb->pos = (dcb->len - 1);
    if (dcb->pos < 0)
	dcb->pos = 0;
}

void f_dbuf(DCharBuf * dcb)
{				/* free */
    int i;

    if (!dcb)
	return;

    for (i = 0; i < dcb->size; i++)
	free(dcb->buf[i].str);

    free(dcb->name);
    free(dcb->buf);
    free(dcb);
}

DCharBuf *i_dbuf(char *desc, int size, int mode)
{				/* init */
    DCharBuf *dcb; 

    dcb = malloc(sizeof(DCharBuf));
    dcb->buf = malloc(sizeof(DCBNode) * size);
    dcb->size = size;
    dcb->mode = mode;
    dcb->bytec = 0;
    dcb->pos = 0;
    dcb->len = 0;
    dcb->var1 = 0;

    memset(dcb->buf, ZCHAR, sizeof(DCBNode) * size);

    if (desc)
	dcb->name = strdup(desc);
    else
	dcb->name = NULL;

    return dcb;
}

int resize_dbuf(DCharBuf * dcb, int new_size)
{
    DCBNode *tmp;
    int i;

    if (!dcb || new_size <= 0)
	return FALSE;

    tmp = dcb->buf;

    if (new_size < dcb->size)
	for (i = new_size; i < dcb->size - 1; i++) {
	    dcb->bytec -= dcb->buf[i].len;
	    free(dcb->buf[i].str);
	}

    dcb->buf = realloc(tmp, new_size * sizeof(DCBNode));

    if (!dcb->buf) {
	dcb->buf = tmp;
	dcb->mode = DCHAR_NORM;
	return FALSE;
    }

    if (new_size > dcb->size)
	memset(&dcb->buf[dcb->size], ZCHAR,
	       (new_size - dcb->size) * sizeof(DCBNode));

    dcb->size = new_size;
    verify_dbuf(dcb);

    return TRUE;
}

int s_dbuf(DCharBuf * dcb, int ind1, int ind2)
{				/* swap */
    DCBNode tmp;

    if (!dcb || ind1 < 0 || ind1 >= dcb->len || ind2 < 0 || ind2 >= dcb->len)
	return FALSE;

    tmp = dcb->buf[ind1];

    dcb->buf[ind1] = dcb->buf[ind2];
    dcb->buf[ind2] = tmp;

    return TRUE;
}

int r_dbuf(DCharBuf * dcb, int ndx)
{				/* remove */
    DCBNode *buf, tmp;
    int len;

    if (!dcb)
	return FALSE;

    buf = dcb->buf;
    len = dcb->len;

    if (ndx == (len - 1))
	dcb->len--;
    else if (ndx >= 0 && ndx < len) {
	tmp = buf[ndx];
	memmove(&buf[ndx], &buf[ndx + 1], sizeof(DCBNode) * (len - ndx));
	tmp.info = 0;
	buf[len - 1] = tmp;
	dcb->len--;
    } else
	return FALSE;

    if (dcb->pos >= ndx)
	dcb->pos--;
    
    verify_dbuf(dcb);
    return TRUE;
}

int a_dbuf(DCharBuf * dcb, char *s)
{				/* add */
    DCBNode *buf, tmpbuf;
    int len, pos;

    if (!dcb || !s)
	return FALSE;
    
    buf = dcb->buf;
    len = get_strlen(s) + 1;

    if (dcb->len < dcb->size)
	pos = dcb->len++;
    else
	switch (dcb->mode) {
	case DCHAR_SHIFT:
	    tmpbuf = *buf;
	    memmove(buf, (buf + 1), sizeof(DCBNode) * (dcb->size - 1));
	    pos = dcb->size - 1;
	    buf[pos] = tmpbuf;
	    break;
	case DCHAR_GROW:
	    if (resize_dbuf(dcb, dcb->size + LIST_SIZE)) {
		pos = dcb->len++;
		buf = dcb->buf;
	    } else
		return FALSE;
	    break;
	default:
	    return FALSE;
	}

    buf = &buf[pos];
    buf->info = 0;

    if (buf->len == 0) {
	dcb->bytec += len;
	buf->len = len;
	buf->str = malloc(len);
    } else if (len > buf->len) {
	dcb->bytec += len - buf->len;
	buf->len = len;
	buf->str = realloc(buf->str, len);
    }

    memcpy(buf->str, s, len);
    return TRUE;
}

/************************************
 *     Buffered Output Functions    *
 ************************************/

int outb_empty()
{
    if (out_buffer->pos == out_buffer->len)
	return TRUE;
    else
	return FALSE;
}

void clear_outbuffer()
{
    c_dbuf(out_buffer);
    out_buffer->var1 = tty_height;

    /* keep output buffer size decent... */
    if (out_buffer->size > (LIST_SIZE * 4))
	resize_dbuf(out_buffer, LIST_SIZE);
}

void sort_outbuffer()
{
    sort_dbuf(out_buffer);
}

void add_outbuffer(const char *frmt, ...)
{
    va_list ap;

    va_start(ap, frmt);
    build_string(ap, frmt, tmpstr_bf);
    va_end(ap);

    if (piped_io_flag)
	outstrnl(tmpstr_bf);
    else
	a_dbuf(out_buffer, tmpstr_bf);
}

void flush_outbuffer()
{
    int len, tabl, cut;
    float perc;

    if (ignore_hist_input) {
	clear_outbuffer();
	show_user(DEBUG_MSG " %s flush denied...", out_buffer->name);
	return;
    }

    if (ignore_char_input) {
	add_console(DEBUG_MSG " %s flush delayed...", out_buffer->name);
	return;
    }

    while (out_buffer->pos < out_buffer->len) {	
	len = tabl = 0;
	strncpy(tmpstr_bf, g_dbuf(out_buffer, out_buffer->pos), tty_width + 2);
	tmpstr_bf[tty_width+1] = ZCHAR;

	while (tmpstr_bf[len] != ZCHAR)
	    if (tmpstr_bf[len++] == TAB)
		tabl += tab_length - len%tab_length;

	if ((len+tabl) > tty_width) {
	    cut = tty_width - tabl;
	    if (cut > 3) strcpy(&tmpstr_bf[cut - 3], "...\0");
	}

	if ((out_buffer->pos >= (out_buffer->var1 - 1))) {
	    perc = 100.0 * ((float) out_buffer->pos / (float) out_buffer->len);

	    print_ctrl_str(1, TC_CR);
	    print_ctrl_str(1, TC_CLR_TOEOL);

	    sprintf(mess_msg, "--Mess--(%d%%)--['\\n','q','b','any']--",
		    (int) perc);
	    outstr(mess_msg);
	    outflush();
	    break;
	} else {
	    outstrnl(tmpstr_bf);
	    out_buffer->pos++;
	}
    }

    if (outb_empty()) {
	clear_outbuffer();
	flush_console_buf();
	if (!prompt_now)
	    update_prompt(FALSE);
    } else
	prompt_now = FALSE;
}

int check_outbuffer(char in)
{
    if (outb_empty())
	return FALSE;

    print_ctrl_str(1, TC_CR);
    print_ctrl_str(1, TC_CLR_TOEOL);

    switch (in) {
    case '\n':
	out_buffer->var1++;
	break;
    case 'q':
	clear_outbuffer();
	flush_console_buf();
	update_prompt(FALSE);
	return TRUE;
    case 'b':
	out_buffer->pos -= (tty_height - 1) * 2;
	out_buffer->var1 -= (tty_height - 1);

	if (out_buffer->pos < 0)
	    out_buffer->pos = 0;
	if (out_buffer->var1 < tty_height)
	    out_buffer->var1 = tty_height;
	break;
    default:
	out_buffer->var1 += (tty_height - 1);
	break;
    }

    flush_outbuffer();
    return TRUE;
}

/************************************
 *      Console Buffer Functions    *
 ************************************/

void flush_console_buf()
{
    int i;

    if (!have_tty() || !console_buf->len)
	return;

    for (i = 0; i < console_buf->len; i++)
	outstrnl(g_dbuf(console_buf, i));
    
    c_dbuf(console_buf);
}

void add_console(const char *frmt, ...)
{
    va_list ap;

    va_start(ap, frmt);
    build_string(ap, frmt, tmpstr_bf);
    va_end(ap);

    if (piped_io_flag)
	outstrnl(tmpstr_bf);
    else
	a_dbuf(console_buf, tmpstr_bf);
}

void show_user(const char *frmt, ...)
{
    va_list ap;

    va_start(ap, frmt);
    build_string(ap, frmt, tmpstr_bf);
    va_end(ap);

    if (have_tty() || piped_io_flag)
	outstrnl(tmpstr_bf);
    else
	a_dbuf(console_buf, tmpstr_bf);
}

void show_user_n(char *str)
{
    if (have_tty() || piped_io_flag)
	outstr(str);
    else
	a_dbuf(console_buf, str);
}

/************************************
 *      Decoder Log functions       *
 ************************************/

void add_dlog(char *s)
{
    time_t tim;
    char *time_ascii;

    tim = time(NULL);
    time_ascii = ctime(&tim);
    remove_newline(time_ascii);

    a_dbuf(decoder_log, s);
    sprintf(tmpstr_bu, "\t...logged %s", time_ascii);
    a_dbuf(decoder_log, tmpstr_bu);
}

void /* user */ clear_dlog()
{
    if (!decoder_log->len)
	show_user(DEBUG_MSG " %s is empty", decoder_log->name);
    else {
	c_dbuf(decoder_log);
	show_user(STATUS_MSG " %s cleared", decoder_log->name);
    }
}

void /* user */ show_dlog()
{
    int i;

    if (!decoder_log->len)
	add_outbuffer(DEBUG_MSG " %s is empty", decoder_log->name);
    else
	for (i = 0; i < decoder_log->len; i++)
	    add_outbuffer("%s", g_dbuf(decoder_log, i));

    flush_outbuffer();
}
