/*
    Tucnak - VHF contest log
    Copyright (C) 2002-2023  Ladislav Vaiz <ok1zia@nagano.cz>
    and authors of web browser Links 0.96

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include "header.h"
#include "charsets.h"
#include "kbd.h"
#include "main.h"
#include "qsodb.h"
#include "rc.h"
#include "tsdl.h"
#include "session.h"
#include "terminal.h"
#include "zosk.h"

#define ALLOC_GR 0x100
struct terminal *term;
struct window *gseswin;

int hard_write(int fd, char *p, int l)
{
#ifdef Z_MSC_MINGW
	zinternal("hard_write unimplemented (and should not be called)");
	return -1;
#else
	int w, t;
    if (fd<0) return -1;
    
    w = 1;
    t = 0;
    while (l > 0 && w) {
        if ((w = write(fd, p, l)) < 0) {
            if (errno == EINTR) continue;
            return -1;
        }
        t += w;
        p += w;
        l -= w;
    }
    return t;
#endif
}


int hard_read(int fd, char *p, int l)
{
#ifdef Z_MSC_MINGW
	zinternal("hard_read unimplemented (and should not be called)");
	return -1;
#else
    int r = 1;
    int t = 0;
    while (l > 0 && r) {
        if ((r = read(fd, p, l)) < 0) {
            if (errno == EINTR) continue;
            return -1;
        }
        /*{int ww;for(ww=0;ww<r;ww++)fprintf(stderr," %02x",(int)p[ww]);fflush(stderr);}*/
        t += r;
        p += r;
        l -= r;
    }
    return t;
#endif
}

char *get_cwd()
{
    int bufsize = 128;
    char *buf;
    while (1) {
        if (!(buf = (char *)g_malloc(bufsize))) return NULL;
        if (getcwd(buf, bufsize)) return buf;
        g_free(buf);
        if (errno == EINTR) continue;
        if (errno != ERANGE) return NULL;
        bufsize += 128;
    }
    return NULL;
}

void set_cwd(char *path)
{
    if (path) while (chdir(path) && errno == EINTR) ;
}

/*struct list_head terminals = {&terminals, &terminals};*/

void alloc_term_screen(struct terminal *term, int x, int y)
{
	g_free(term->last_screen);
	g_free(term->screen);
	term->screen = g_new(unsigned, x * y);
	term->last_screen = g_new0(unsigned, x * y);
	memset(term->last_screen, -1, x * y * sizeof(unsigned));
    term->x = x;
    term->y = y;
	term->dirty = 1;
    term->lcx = -1;
    term->lcy = -1;
#ifdef FALL
    int i;
    if (term->fall != NULL) {
        term->fall = mem_realloc(term->fall, x * y * sizeof(int));
        for (i = 0; i < x * y; i++) term->fall[i] = i;
    }else{
        term->fall = g_malloc(x * y * sizeof(int));
        for (i = 0; i < x * y; i++) term->fall[i] = i;
    } 
#endif
        
}

void in_term(void *arg);
void check_if_no_terminal(void);

void clear_terminal(void)
{
    if (!term) return;
    fill_area(0, 0, term->x, term->y, ' ');
    set_cursor(0, 0, 0, 0);
}

void redraw_terminal_ev(struct terminal *term, int e)
{
    struct window *win;
    struct event ev = {0, 0, 0, 0};
    
    if (!term) return;
    ev.ev = e;
    ev.x = term->x;
    ev.y = term->y;
    clear_terminal();
    term->redrawing = 2;
    foreachback(win, term->windows) win->handler(win, &ev, 0);
    term->redrawing = 0;
#ifdef FALL
    if (term->fall){
        int i;
        for (i = term->x * term->y; i >= 0; i--){
            if (term->fall[i] < 0)
                term->screen[i] = ' ';
            else
                term->screen[i] = term->screen[term->fall[i]];
        }
    }
#endif
}

#define ISSPACE(idx) ((term->screen[idx] & 0xff) == ' ' || (term->screen[idx] & 0xff) == '\0')

#ifdef FALL
void fall_func(union cba_t cba){
    int s, d, j;
//    static iii = 0;

    if (term && term->fall){
        for (j = 0; j < 1000; j++){
            s = rand() % (term->x * term->y);
            if (ISSPACE(s)) continue;
            for (d = s + term->x; ISSPACE(d) && d < (term->x * term->y); d += term->x);
            d -= term->x;
            if (!ISSPACE(d)) continue;
            term->fall[d] = term->fall[s];
            term->fall[s] = -1;
            break;
        }

        
    }
    gses->fall_id = zselect_timer_new(zsel, 10, fall_func, NULL);
    redraw_later();
}
#endif

void redraw_terminal(void *arg)
{
    /*if (!term) return;*/
    //dbg("redraw_terminal()\n");
    redraw_terminal_ev(term, EV_REDRAW);
}

void redraw_terminal_all(void)
{
    if (!term) return;
    redraw_terminal_ev(term, EV_RESIZE);
}


void redraw_later(void)
{       
    if (!term) return;
    //dbg("redraw_later()\n");
    zselect_bh_new(zsel, redraw_terminal, NULL);

    /*if (!gses){
        zselect_bh_new(zsel, redraw_terminal, NULL);
        return;
    }
    
    if (!gses->redraw_timer_id)
        gses->redraw_timer_id = zselect_timer_new(zsel, 100, timer_redraw, NULL);*/
}

void erase_screen(void)
{
    if (!term) return;
    if (!term->master || !is_blocked()) {
        if (term->fdout >= 0) hard_write(term->fdout, "\033[0;37;40m\033[2J\033[1;1H", 20);
    }
}

void redraw_terminal_cls()
{
    if (!term) return;
    erase_screen();
    alloc_term_screen(term, term->x, term->y);
    redraw_terminal_all();
}

/*void cls_redraw_all_terminals()
{
    struct terminal *term;
    foreach(term, terminals) redraw_terminal_cls(term);
} */

void redraw_from_window(struct window *win)
{
    struct terminal *term = win->term;
    struct window *end = (void *)&term->windows;
    struct event ev = {EV_REDRAW, 0, 0, 0};
    ev.x = term->x;
    ev.y = term->y;
    if (term->redrawing) return;
    term->redrawing = 1;
    for (win = win->prev; win != end; win = win->prev) {
        win->handler(win, &ev, 0);
    }
    term->redrawing = 0;
}

void redraw_below_window(struct window *win)
{
    int tr;
    struct terminal *term = win->term;
    struct window *end = win;
    struct event ev = {EV_REDRAW, 0, 0, 0};
    ev.x = term->x;
    ev.y = term->y;
    if (term->redrawing >= 2) return;
    tr = term->redrawing;
    win->term->redrawing = 2;
    for (win = term->windows.prev; win != end; win = win->prev) {
        win->handler(win, &ev, 0);
    }
    term->redrawing = tr;
}

void add_window_at_pos(void (*handler)(struct window *, struct event *, int), void *data, struct window *at)
{
    struct event ev = {EV_INIT, 0, 0, 0};
    struct window *win;
    ev.x = term->x;
    ev.y = term->y;
    if (!(win = g_malloc(sizeof (struct window)))) {
        g_free(data);
        return;
    }
    win->handler = handler;
    win->data = data;
    win->term = term;
    win->xp = win->yp = 0;
    add_at_pos(at, win);
    win->handler(win, &ev, 0);
}

void add_window(void (*handler)(struct window *, struct event *, int), void *data)
{
    add_window_at_pos(handler, data, (struct window *)&term->windows);
}

void delete_window(struct window *win)
{
    struct event ev = {EV_ABORT, 0, 0, 0};
    win->handler(win, &ev, 1);
    del_from_list(win);
    if (win->data) g_free(win->data);
    redraw_terminal(NULL);
    g_free(win);
}

void delete_window_ev(struct window *win, struct event *ev)
{
    struct window *w = win->next;
    if ((void *)w == &win->term->windows) w = NULL;
    delete_window(win);
    if (ev && w && w->next != w) w->handler(w, ev, 1);
}

void set_window_ptr(struct window *win, int x, int y)
{
    win->xp = x;
    win->yp = y;
}

void get_parent_ptr(struct window *win, int *x, int *y)
{
    if ((void *)win->next != &win->term->windows) {
        *x = win->next->xp;
        *y = win->next->yp;
    } else {
        *x = *y = 0;
    }
}

struct window *get_root_window()
{
    if (list_empty(term->windows)) {
        zinternal("terminal has no windows");
        return NULL;
    }
    return (struct window *)term->windows.prev;
}

struct ewd {
    void (*fn)(void *);
    void *data;
    int b;
};

void empty_window_handler(struct window *win, struct event *ev, int fwd)
{
    struct window *n;
    struct ewd *ewd = (struct ewd*)win->data;
    int x, y;
    void (*fn)(void *) = ewd->fn;
    void *data = ewd->data;
    if (ewd->b) return;
    switch (ev->ev) {
        case EV_INIT:
        case EV_RESIZE:
        case EV_REDRAW:
            get_parent_ptr(win, &x, &y);
            set_window_ptr(win, x, y);
            return;
        case EV_ABORT:
            fn(data);
            return;
    }
    ewd->b = 1;
    n = win->next;
    delete_window(win);
    fn(data);
    if (n->next != n) n->handler(n, ev, fwd);
}

/*void add_empty_window(void (*fn)(void *), void *data)
{
    struct ewd *ewd;
    if (!(ewd = g_malloc(sizeof(struct ewd)))) return;
    ewd->fn = fn;
    ewd->data = data;
    ewd->b = 0;
    add_window(empty_window_handler, ewd);
} */

void free_term_specs()
{
/*    free_list(term_specs);*/
    while (term_specs.next != &(term_specs)) {
        struct term_spec *a=term_specs.next; 
        del_from_list(a); 
        g_free(a); 
    }
}

struct term_spec term_specs = {&term_specs, &term_specs};

struct term_spec dumb_term = { NULL, NULL, "", 0, 0, 0, 0, 0, 0 };

struct term_spec *get_term_spec(char *term)
{
    struct term_spec *t;
    foreach(t, term_specs) if (!strcasecmp(t->term, term)) return t;
    return &dumb_term;
}

struct term_spec *new_term_spec(char *term)
{
    struct term_spec *t;
    foreach(t, term_specs) if (!strcasecmp(t->term, term)) return t;
    if (!(t = g_new(struct term_spec, 1))) return NULL;
    memcpy(t, &dumb_term, sizeof(struct term_spec));
    if (strlen(term) < MAX_TERM_LEN) strcpy(t->term, term);
    else memcpy(t->term, term, MAX_TERM_LEN - 1), t->term[MAX_TERM_LEN - 1] = 0;
    add_to_list(term_specs, t);
    sync_term_specs();
    return t;
}

void sync_term_specs()
{
    if (!term) return;
    term->spec = get_term_spec(term->term);
    //dbg("sync_term_specs: %p\n", term->spec);
} 

struct terminal *init_term(int fdin, int fdout, 
        void (*root_window)(struct window *, struct event *, int))
{
    struct terminal *term;
    struct window *win;
    if (!(term = g_new0(struct terminal, 1))) {
        zselect_terminate(zsel);
        return NULL;
    }
    term->fdin = fdin;
    term->fdout = fdout;
    term->master = term->fdout == get_output_handle();
    term->lcx = -1;
    term->lcy = -1;
    term->dirty = 1;
    term->redrawing = 0;
    term->blocked = -1;
    term->screen = NULL;
    term->last_screen = NULL;
    term->spec = &dumb_term;
    term->term[0] = 0;
    term->input_queue = NULL;
    term->qlen = 0;
    init_list(term->windows);
    if (!(win = g_new(struct window, 1))){
        g_free(term);
        zselect_terminate(zsel);
        return NULL;
    }
    win->handler = root_window;
    win->data = NULL;
    win->term = term;
    add_to_list(term->windows, win);
    if (fdin!=-1){
        zselect_set(zsel, fdin, in_term, NULL, destroy_terminal, NULL);
    }
    gseswin = win;
    return term;
}

void term_send_event(struct terminal *term, struct event *ev)
{
	struct window *win;

#ifdef Z_HAVE_SDL
	if (gses && gses->osk){
		switch (ev->ev){
			case EV_MOUSE:
				//if (ev->b & (B_MOVE | B_DRAG)) goto to;
				if (ev->b == (B_LEFT | B_DOWN)) {
					if (zosk_click(gses->osk, ev->mx - gses->osk->parent_x, ev->my - gses->osk->parent_y, 1)) {
						if (cfg->portrait) goto toterm;
					}
					redraw_later();
					return;
				}
				if ((ev->b & ~B_CLICK) == (B_LEFT | B_UP))   {
					if (zosk_click(gses->osk, ev->mx - gses->osk->parent_x, ev->my - gses->osk->parent_y, 0)) {
						if (cfg->portrait) goto toterm;
					}
					redraw_later();
					return;
				}
				if (cfg->portrait) goto toterm;
				return;
			case EV_KBD:
				if ((ev->x == KBD_ESC || ev->x == KBD_ENTER) && (gses->osk->flags & ZOSK_ENTERCLOSE)){
                    int flg = gses->osk->flags;
					zosk_free(gses->osk);
					gses->osk = NULL;
					if (cfg->portrait){
						resize_terminal(NULL);
					}else{
						redraw_later();
						sdl_force_redraw();
						SDL_FillRect(sdl->screen, NULL, z_makecol(0, 0, 0));
						SDL_UpdateRect(sdl->screen, 0, 0, 0, 0);
					}
					if (flg & ZOSK_IGNOREESC) return;
                    if (flg & ZOSK_SENDENTER) break;
                    return;
				}
				break;
		}
	}
toterm:;
#endif

/*	assert(ev && term && !list_empty(term->windows));*/
/*	if_assert_failed return;*/
	win = term->windows.next;
	win->handler(win, ev, 0);
}

static void term_send_ucs(struct terminal *term, struct event *ev, unicode_val u)
{
	char *recoded;

	recoded = u2cp(u, term->spec->charset);
	if (!recoded) recoded = "*";
	while (*recoded) {
		ev->x = *recoded;
		term_send_event(term, ev);
		recoded++;
	}
}

#define get_bool_opt(s, opt) (s->opt == 1 ? 1 : 0)

void in_term(void *arg)
{
    struct event *ev;
    int r, err;
    char *iq, *c;

    if (!(iq = (char*)g_realloc(term->input_queue, term->qlen + ALLOC_GR))) {
        destroy_terminal(NULL);
		zselect_hint(zsel, "a");
        return;
    }
    term->input_queue = iq;
	r = z_pipe_read(term->fdin, iq + term->qlen, ALLOC_GR);
    err = z_sock_errno;
	
	//zselect_hint(zsel, "fdin=%d qlen=%d   r=%d err=%d", term->fdin, term->qlen, r, err);

    if (r <= 0) {
        if (r == -1/* && errno != ECONNRESET*/) zinternal("ERROR: error %d on terminal: could not read event", err);
        destroy_terminal(NULL);
		zselect_hint(zsel, "b");
        return;
    }
#ifdef Z_HAVE_SDL    
    if (sdl){
        MUTEX_LOCK(sdl->eventpipestate);
        sdl->eventpipestate-=r;
/*        dbg("r %d\n", sdl->eventpipestate);*/
        MUTEX_UNLOCK(sdl->eventpipestate);
    }
#endif

    term->qlen += r;
test_queue:
	//dbg("next event\n");
    if (term->qlen < sizeof(struct event)) {
		//zselect_hint(zsel, "%d < %d", term->qlen, sizeof(struct event));
		return;
	}
    ev = (struct event *)iq;
    
//    dbg("in_term [%d,%d,%d,%d]\n",ev->ev,ev->x,ev->y,ev->b);
/*    dbg("in_term qlen=%d\n", term->qlen);*/

    if (term->qlen > sizeof(struct event) &&    /* more than one event */
        ev->ev==EV_KBD && ev->x==KBD_TAB){       /* processing TAB keypress */
        for (c=iq+sizeof(struct event); c<iq+term->qlen;c+=sizeof(struct event)){
            ev = (struct event *)c;
            /*dbg("in_term c=%d [%d,%d,%d,%d]\n",(c-iq)/sizeof(struct event),ev->ev,ev->x,ev->y,ev->b);*/
            if (ev->ev!=EV_KBD || ev->x!=KBD_TAB) continue;
            ev->ev=EV_SKIP;
            
        }
    }
    
    
    ev = (struct event *)iq;

	if (zselect_profiling(zsel)){
		switch (ev->ev){
			case EV_INIT:
				zselect_hint(zsel, "EV_INIT");
				break;
			case EV_RESIZE:
				zselect_hint(zsel, "EV_RESIZE %dx%d", ev->x, ev->y);
				break;
			case EV_REDRAW:
				zselect_hint(zsel, "EV_REDRAW");
				break;
			case EV_KBD:
				if (ev->x < 0x100)
					zselect_hint(zsel, "EV_KBD '%c' %s %s %s", ev->x, ev->y & KBD_CTRL ? "CTRL" : "", ev->y & KBD_ALT ? "ALT" : "", ev->y & KBD_SHIFT ? "SHIFT" : "");
				else
					zselect_hint(zsel, "EV_KBD 0x%x %s %s %s", ev->x, ev->y & KBD_CTRL ? "CTRL" : "", ev->y & KBD_ALT ? "ALT" : "", ev->y & KBD_SHIFT ? "SHIFT" : "");
				break;
			case EV_MOUSE:
				zselect_hint(zsel, "EV_MOUSE 0x%x %x %y", ev->b, ev->mx, ev->my);
				break;
			case EV_ABORT:
				zselect_hint(zsel, "EV_ABORT");
				break;
			case EV_SDLRESIZE:
				zselect_hint(zsel, "EV_SDLRESIZE");
				break;
			case EV_SKIP:
				zselect_hint(zsel, "EV_SKIP");
				break;
			default:
				zselect_hint(zsel, "unknown %d", ev->ev);
				break;
		}

	}
    r = sizeof(struct event);
    if (ev->ev != EV_INIT && ev->ev != EV_RESIZE && ev->ev != EV_REDRAW && ev->ev != EV_KBD && ev->ev != EV_MOUSE && ev->ev != EV_ABORT && ev->ev!=EV_SDLRESIZE && ev->ev!=EV_SKIP) {
        error("ERROR: error on terminal: bad event %d", ev->ev);
        goto mm;
    }
    if (ev->ev == EV_INIT) {
        char *ts;
        int xwin;
        
        xwin = is_xterm() * ENV_XWIN + can_twterm() * ENV_TWIN + (!!getenv("STY")) * ENV_SCREEN;
        if (!(ts = getenv("TERM"))) ts = "";
//        dbg("in_term ts=%s\n", ts);
        g_strlcpy(term->term, ts, MAX_TERM_LEN);
        term->environment=xwin;
        sync_term_specs();
    }
    if (ev->ev == EV_SDLRESIZE){
#ifdef Z_HAVE_SDL        
        //dbg("EV_SDLRESIZE(%d,%d) (%d,%d)\n", ev->x, ev->y, sdl->resize_w, sdl->resize_h/*ev->mx, ev->my*/);
		MUTEX_LOCK(sdl->eventpipestate);
		sdl->resizeevents--;
		MUTEX_UNLOCK(sdl->eventpipestate);
        if (sdl) {
			cfg->gfx_x=sdl->resize_w;//ev->mx;
			cfg->gfx_y=sdl->resize_h;//ev->my;
			sdl_setvideomode(sdl->resize_w, sdl->resize_h/*ev->mx, ev->my*/, 0);
		}
#endif
        resize_terminal(NULL);
    }
    if (ev->ev == EV_REDRAW || ev->ev == EV_RESIZE || ev->ev == EV_INIT) {
        struct window *win;
        send_redraw:
        if (ev->x < 0 || ev->y < 0) {
            error("ERROR: bad terminal size: %d, %d", (int)ev->x, (int)ev->y);
            goto mm;
        }
        alloc_term_screen(term, ev->x, ev->y);
        clear_terminal();
        erase_screen();
        term->redrawing = 1;
        foreachback(win, term->windows) win->handler(win, ev, 0);
        term->redrawing = 0;
    }
	if (ev->ev == EV_MOUSE && preferred_func(ev)){
		term_send_event(term, ev);
	}
	if (ev->ev == EV_KBD) {
		if (
#ifdef Z_HAVE_SDL
            !sdl &&     
#endif
            z_char_uc(ev->x) == (int)'L' && ev->y == KBD_CTRL) {

			ev->ev = EV_REDRAW;
			ev->x = term->x;
			ev->y = term->y;
			goto send_redraw;
		} else if (ev->x == KBD_CTRL_C) 
			((struct window *)&term->windows)->prev->handler(term->windows.prev, ev, 0);
		else if (preferred_func(ev)) {
/*			((struct window *)&term->windows)->next->handler(term->windows.next, ev, 0);*/
			int utf8_io = -1;

			if (term->utf_8.len) {
				utf8_io = get_bool_opt(term->spec, utf_8_io);
				if ((ev->x & 0xC0) == 0x80 && utf8_io) {
					term->utf_8.ucs <<= 6;
					term->utf_8.ucs |= ev->x & 0x3F;
					if (! --term->utf_8.len) {
						unicode_val u = term->utf_8.ucs;
						if (u < term->utf_8.min)
							u = UCS_NO_CHAR;
						term_send_ucs(term, ev, u);
					}
					goto mm;
				} else {
					term->utf_8.len = 0;
					term_send_ucs(term, ev, UCS_NO_CHAR);
				}
			}

			if (ev->x < 0x80 || ev->x > 0xFF || (utf8_io == -1 ?
				!get_bool_opt(term->spec, utf_8_io) : !utf8_io)) {
				term_send_event(term, ev);
				goto mm;
			} else if ((ev->x & 0xC0) == 0xC0 && (ev->x & 0xFE) != 0xFE) {
				register unsigned int mask, cov = 0x80;
				int len = 0;

				for (mask = 0x80; ev->x & mask; mask >>= 1) {
					len++;
					term->utf_8.min = cov;
					cov = 1 << (1 + 5 * len);
				}
				term->utf_8.len = len - 1;
				term->utf_8.ucs = ev->x & (mask - 1);
				goto mm;
			}
			term_send_ucs(term, ev, UCS_NO_CHAR);
		}
    }
    if (ev->ev == EV_ABORT) zselect_terminate(zsel);//destroy_terminal(NULL);
    /*redraw_screen(term);*/
    mm:
    if (term->qlen == r) term->qlen = 0;
    else memmove(iq, iq + r, term->qlen -= r);
	//return;
    goto test_queue;
}

/*inline int getcompcode(int c) // probably bug in gcc 4.6
{
    return (c<<1 | (c&4)>>2) & 7;
}*/
#define getcompcode(c) (int)( ((c)<<1 | ((c)&4)>>2) & 7 )


char frame_dumb[49] =  "   ||||++||++++++--|-+||++--|-+----++++++++     ";
char frame_vt100[49] = "aaaxuuukkuxkjjjkmvwtqnttmlvwtqnvvwwmmllnnjla    ";

/* For UTF8 I/O */
static char frame_vt100_u[48] = {
	177, 177, 177, 179, 180, 180, 180, 191,
	191, 180, 179, 191, 217, 217, 217, 191,
	192, 193, 194, 195, 196, 197, 195, 195,
	192, 218, 193, 194, 195, 196, 197, 193,
	193, 194, 194, 192, 192, 218, 218, 197,
	197, 217, 218, 177,  32, 32,  32,  32
};

char frame_koi[48] = {
    144,145,146,129,135,178,180,167,
    166,181,161,168,174,173,172,131,
    132,137,136,134,128,138,175,176,
    171,165,187,184,177,160,190,185,
    186,182,183,170,169,162,164,189,
    188,133,130,141,140,142,143,139,
};

char frame_restrict[48] = {
    0, 0, 0, 0, 0, 179, 186, 186,
    205, 0, 0, 0, 0, 186, 205, 0,
    0, 0, 0, 0, 0, 0, 179, 186,
    0, 0, 0, 0, 0, 0, 0, 205,
    196, 205, 196, 186, 205, 205, 186, 186,
    179, 0, 0, 0, 0, 0, 0, 0,
};

/* characters 128-159 are special for xterm unless
 * *VT100*allowC1Printable property is not set */

#define MODE(ch) (((ch) >> 15) & MODE_MASK)

#define PRINT_CHAR(p)                                                       \
{                                                                           \
	unsigned ch = term->screen[p];                                          \
	unsigned char c = ch & 0xff;                                            \
	unsigned char A = ch >> 8 & 0x7f;                                       \
	int charset = s->charset;                                               \
	if (s->mode == TERM_LINUX) {                                            \
		if (s->m11_hack) {                                                  \
			if (MODE(ch) != mode) {         	                            \
				if (!(mode = MODE(ch)))                                     \
					g_string_append(a, "\033[10m");                         \
                else                                                        \
					g_string_append(a, "\033[11m");	                        \
			}                                                               \
		}                                                                   \
		if ((mode = MODE(ch)) && c >= 176 && c < 224) {                     \
			charset = get_cp_index("cp437");                                \
			if (s->restrict_852 && frame_restrict[c - 176])                 \
				c = frame_restrict[c - 176];                                \
		}                                                                   \
	} else if (s->mode == TERM_VT100) {                                     \
		if (MODE(ch) != mode) {                                             \
			if (!(mode = MODE(ch)))                                         \
				g_string_append(a, "\x0f");                                 \
			else                                                            \
				g_string_append(a, "\x0e");                                 \
		}                                                                   \
		if (mode && c >= 176 && c < 224) {                                  \
			charset = get_cp_index("cp437");                                \
			if (get_bool_opt(s, utf_8_io)                                   \
				&& strcasecmp(term->term, "xterm"))                         \
				c = frame_vt100_u[c - 176];                                 \
			else                                                            \
				c = frame_vt100[c - 176];                                   \
		}                                                                   \
	} else if (s->mode == TERM_KOI8) {                                      \
		if (MODE(ch) != mode && (mode = MODE(ch)))                          \
			charset = get_cp_index("koi8-r");                               \
		if (mode && c >= 176 && c < 224)                                    \
			c = frame_koi[c - 176];                                         \
	} else if (s->mode == TERM_DUMB && MODE(ch) && c >= 176 && c < 224)   \
		c = frame_dumb[c - 176];                                            \
	if (!(A & 0100) && (A >> 3) == (A & 7))                                 \
		A = (A & 070) | 7 * !(A & 020);                                     \
	if (A != attrib) {                                                      \
		attrib = A;                                                         \
		g_string_append(a, "\033[0");                                       \
		if (s->col) {                                                       \
			char m[4];                                                      \
			m[0] = ';'; m[1] = '3'; m[3] = 0;                               \
			m[2] = (attrib & 7) + '0';                                      \
			g_string_append(a, m);                                          \
			m[1] = '4';                                                     \
			m[2] = (attrib >> 3 & 7) + '0';                                 \
			g_string_append(a, m);                                          \
		} else if (getcompcode(attrib & 7) < getcompcode(attrib >> 3 & 7))  \
			g_string_append(a, ";7");                                       \
		if (attrib & 0100)                                                  \
			g_string_append(a, ";1");                                       \
		g_string_append(a, "m");                                            \
	}                                                                       \
	if (c>=' ' && (c<127 || c>159) && c!=255) {                             \
		if (get_bool_opt(s, utf_8_io))                                      \
			g_string_append(a, cp2utf_8(charset, c));                       \
		else                                                                \
			g_string_append_c(a, c);                                        \
	}                                                                       \
	else if (!c || c == 1) g_string_append_c(a, ' ');                       \
	else g_string_append_c(a, '.');                                         \
	cx++;                                                                   \
}                                                                           \


void term_redraw_screen()
{
    int x, y, p = 0;
    int cx = -1, cy = -1;
    GString *a;
    int attrib = -1;
    int mode = -1;
    struct term_spec *s;
    if (!term->dirty || (term->master && is_blocked())) return;
    a = g_string_new("");
    s = term->spec;
    for (y = 0; y < term->y; y++)
        for (x = 0; x < term->x; x++, p++) {
            if (y == term->y - 1 && x == term->x - 1) break;
            if (term->screen[p] == term->last_screen[p]) continue;
            if ((term->screen[p] & 0x3800) == (term->last_screen[p] & 0x3800) && 
                ((term->screen[p] & 0xff) == 0 || 
                 (term->screen[p] & 0xff) == 1 || 
                 (term->screen[p] & 0xff) == ' ') &&
                ((term->last_screen[p] & 0xff) == 0 || 
                 (term->last_screen[p] & 0xff) == 1 || 
                 (term->last_screen[p] & 0xff) == ' ')) continue;
            if (cx == x && cy == y) goto pc;/*PRINT_CHAR(p)*/
            else if (cy == y && x - cx < 10) {
                int i;
                for (i = x - cx; i >= 0; i--) PRINT_CHAR(p - i);
            } else {
                g_string_append_printf(a, "\033[%d;%dH", y + 1, x + 1);
                cx = x; cy = y;
                pc:
                PRINT_CHAR(p);
            }
        }
    if (a->len) {
        if (s->col) g_string_append(a, "\033[37;40m");
        g_string_append(a, "\033[0m");
        if (s->mode == TERM_LINUX && s->m11_hack) g_string_append(a, "\033[10m");
        if (s->mode == TERM_VT100) g_string_append(a, "\x0f");
    }
    if (a->len || term->cx != term->lcx || term->cy != term->lcy) {
        term->lcx = term->cx;
        term->lcy = term->cy;
        g_string_append_printf(a, "\033[%d;%dH", term->cy + 1, term->cx + 1);
    }
    hard_write(term->fdout, a->str, a->len);
	
    g_string_free(a, TRUE);
    memcpy(term->last_screen, term->screen, term->x * term->y * sizeof(int));
    term->dirty = 0;
}

void destroy_terminal(void *arg)
{
    if (!term) return;
	//zselect_terminate(zsel);
	while ((term->windows.next) != &term->windows) delete_window(term->windows.next);
    /*if (term->cwd) g_free(term->cwd);*/
    if (term->title) g_free(term->title);
    g_free(term->screen);
    term->screen=NULL;
    g_free(term->last_screen);
    term->last_screen=NULL;
    zselect_set(zsel, term->fdin, NULL, NULL, NULL, NULL);
    g_free(term->input_queue);
    if (term->blocked != -1) {
#ifdef Z_MSC_MINGW
		zinternal("term->blocked unimplemented (and should not be called)");
#else
        close(term->blocked);
        zselect_set(zsel, term->blocked, NULL, NULL, NULL, NULL);
#endif
    }
    z_pipe_close(term->fdin);
    if (term->fdout != 1 && term->fdout >= 0) {
#ifdef Z_MSC_MINGW
		zinternal("term->fdout unimplemented (and should not be called)");
#else
        if (term->fdout != term->fdin) close(term->fdout);
#endif

    } else {
        unhandle_terminal_signals();
        free_all_itrms();
/*#ifndef NO_FORK_ON_EXIT
        if (!list_empty(terminals)) {
            if (fork()) exit(0);
        }
#endif */
    }

    g_free(term);
    term=NULL;
}

/*void destroy_all_terminals()
{
    struct terminal *term;
    while ((void *)(term = terminals.next) != &terminals) destroy_terminal(term);
}

void check_if_no_terminal(void)
{
    if (!list_empty(terminals)) return;
    terminate = 1;
} */

void set_char(int x, int y, unsigned c)
{
    term->dirty = 1;
    if (x >= 0 && x < term->x && y >= 0 && y < term->y) term->screen[x + term->x * y] = c;
}

void set_last_char(int x, int y, unsigned c)
{
    if (x >= 0 && x < term->x && y >= 0 && y < term->y) term->last_screen[x + term->x * y] = c;
}

unsigned get_char(int x, int y)
{
    if (x >= term->x) x = term->x - 1;
    if (x < 0) x = 0;
    if (y >= term->y) y = term->y - 1;
    if (y < 0) y = 0;
    return term->screen[x + term->x * y];
}

void set_color(int x, int y, unsigned c)
{
    term->dirty = 1;
    if (x >= 0 && x < term->x && y >= 0 && y < term->y) term->screen[x + term->x * y] = (term->screen[x + term->x * y] & 0x80ff) | (c & ~0x80ff);
}

void set_only_char(int x, int y, unsigned c)
{
    term->dirty = 1;
    if (x >= 0 && x < term->x && y >= 0 && y < term->y) term->screen[x + term->x * y] = (term->screen[x + term->x * y] & ~0x80ff) | (c & 0x80ff);
}

void set_line(int x, int y, int l, chr *line)
{
    int i;
    term->dirty = 1;
    for (i = x >= 0 ? 0 : -x; i < (x+l <= term->x ? l : term->x-x); i++)
        term->screen[x+i + term->x * y] = line[i];
}

void set_line_color(int x, int y, int l, unsigned c)
{
    int i;
    term->dirty = 1;
    for (i = x >= 0 ? 0 : -x; i < (x+l <= term->x ? l : term->x-x); i++)
        term->screen[x+i + term->x * y] = (term->screen[x+i + term->x * y] & 0x80ff) | (c & ~0x80ff);
}

void fill_area(int x, int y, int xw, int yw, unsigned c)
{
    int i,j;
    term->dirty = 1;
    for (j = y >= 0 ? 0 : -y; j < yw && y+j < term->y; j++)
        for (i = x >= 0 ? 0 : -x; i < xw && x+i < term->x; i++) 
            term->screen[x+i + term->x*(y+j)] = c;
}

void fill_lastarea(int x, int y, int xw, int yw, unsigned c)
{
    int i,j;
    for (j = y >= 0 ? 0 : -y; j < yw && y+j < term->y; j++)
        for (i = x >= 0 ? 0 : -x; i < xw && x+i < term->x; i++) 
            term->last_screen[x+i + term->x*(y+j)] = c;
}

int p1[] = { 218, 191, 192, 217, 179, 196 };
int p2[] = { 201, 187, 200, 188, 186, 205 };

void draw_frame(int x, int y, int xw, int yw, unsigned c, int w)
{
    int *p = w > 1 ? p2 : p1;
    c |= ATTR_FRAME;
    set_char(x, y, c+p[0]);
    set_char(x+xw-1, y, c+p[1]);
    set_char(x, y+yw-1, c+p[2]);
    set_char(x+xw-1, y+yw-1, c+p[3]);
    fill_area(x, y+1, 1, yw-2, c+p[4]);
    fill_area(x+xw-1, y+1, 1, yw-2, c+p[4]);
    fill_area(x+1, y, xw-2, 1, c+p[5]);
    fill_area(x+1, y+yw-1, xw-2, 1, c+p[5]);
}

void print_text(int x, int y, int l, char *text, unsigned c)
{
    for (; l-- && *text; text++, x++) set_char(x, y,((unsigned char)*text) + c);
}

void set_cursor(int x, int y, int altx, int alty)
{
//    dbg("set_cursor(%d,%d,  %d,%d)\n", x, y, altx, alty);
    term->dirty = 1;
    if (term->spec->block_cursor) x = altx, y = alty;
    if (x >= term->x) x = term->x - 1;
    if (y >= term->y) y = term->y - 1;
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    term->cx = x;
    term->cy = y;
}

void exec_thread(char *path, int p)
{
#ifdef Z_MSC_MINGW
	zinternal("exec_thread unimplemented (and should not be called)");
#else

#if defined(HAVE_SETPGID) && !defined(BEOS) && !defined(HAVE_BEGINTHREAD)
    if (path[0] == 2) setpgid(0, 0);
#endif
    exe(path + 1);
    close(p);
    if (path[1 + strlen(path + 1) + 1]) unlink(path + 1 + strlen(path + 1) + 1);
#endif
}

void close_handle(void *arg)
{
#ifdef Z_MSC_MINGW
	zinternal("close_handle unimplemented");
#else
    int h = GPOINTER_TO_INT(arg);
    close(h);
    zselect_set(zsel, h, NULL, NULL, NULL, NULL);
#endif
}

#ifndef Z_MSC_MINGW
void unblock_terminal(void *arg)
{
    close_handle(GINT_TO_POINTER(term->blocked));
    term->blocked = -1;
    zselect_set(zsel, term->fdin, in_term, NULL, destroy_terminal, NULL);
    unblock_itrm(term->fdin);
    redraw_terminal_cls();
}

void exec_on_terminal(char *path, char *delete1, int fg)
{
    if (path && !*path) return;
    if (!path) path="";
#ifdef NO_FG_EXEC
    fg = 0;
#endif
    if (term->master) {
        if (!*path) dispatch_special(delete1);
        else {
            int blockh;
            char *param;
            if (is_blocked() && fg) {
                unlink(delete1);
                return;
            }
            if (!(param = g_malloc(strlen(path) + strlen(delete1) + 3))) return;
            param[0] = fg;
            strcpy(param + 1, path);
            strcpy(param + 1 + strlen(path) + 1, delete1);
            if (fg == 1) block_itrm(term->fdin);
            if ((blockh = start_thread((void (*)(void *, int))exec_thread, param, strlen(path) + strlen(delete1) + 3)) == -1) {
                if (fg == 1) unblock_itrm(term->fdin);
                g_free(param);
                return;
            }
            g_free(param);
            if (fg == 1) {
                term->blocked = blockh;
                zselect_set(zsel, blockh, unblock_terminal, NULL, unblock_terminal, NULL);
                zselect_set(zsel, term->fdin, NULL, NULL, destroy_terminal, NULL);
                /*block_itrm(term->fdin);*/
            } else {
                zselect_set(zsel, blockh, close_handle, NULL, close_handle, GINT_TO_POINTER(blockh));
            }
        }
    } else {
        char *data;
        if ((data = g_malloc(strlen(path) + strlen(delete1) + 4))) {
            data[0] = 0;
            data[1] = fg;
            strcpy(data + 2, path);
            strcpy(data + 3 + strlen(path), delete1);
            hard_write(term->fdout, data, strlen(path) + strlen(delete1) + 4);
            g_free(data);
        }
        /*char x = 0;
        hard_write(term->fdout, &x, 1);
        x = fg;
        hard_write(term->fdout, &x, 1);
        hard_write(term->fdout, path, strlen(path) + 1);
        hard_write(term->fdout, delete, strlen(delete) + 1);*/
    }
}

void do_terminal_function(char code, char *data)
{
    char *x_data;
    if (!(x_data = (char *)g_malloc(strlen(data) + 2))) return;
    x_data[0] = code;
    strcpy(x_data + 1, data);
    exec_on_terminal(NULL, x_data, 0);
    g_free(x_data);
}
#endif

void set_terminal_title(char *title)
{
    if (term->title && !strcmp(title, term->title)) return;
    if (term->title) g_free(term->title);
    term->title = g_strdup(title);
#ifdef Z_HAVE_SDL    
    if (!sdl) 
#endif 
	{
#ifndef Z_MSC_MINGW
        do_terminal_function(TERM_FN_TITLE, title);
#endif
	}
}
    

void set_ctest_title(){
    gchar *c;

    if (ctest){
        c = g_strdup_printf(" %s %s - %s-%s %s", ctest->pcall, ctest->tname, PACKAGE_NAME, Z_PLATFORM, PACKAGE_VERSION);
        set_terminal_title(c);
        g_free(c);
    }else{
        c = g_strdup_printf(" %s-%s %s", PACKAGE_NAME, Z_PLATFORM, PACKAGE_VERSION);
        set_terminal_title(c);
        g_free(c);
    }
}
