/*
 * gpm-xterm.c - pseudo client for non-Linux xterm only mouse support.
 *               This code has been extracted from libgpm-0.18 and then
 *               took its own way.
 *
 * Copyright (C) 1994   rubini@ipvvis.unipv.it (Alessandro Rubini)
 * Copyright (C) 1994   Janne Kukonlehto
 *
 *   This program is free software; you can redistribute 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.
 *
 *   This program 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 received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 ********/


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>		/* select(); */
#include <sys/time.h>		/* timeval */
#include <sys/types.h>		/* socket() */

#ifdef HAVE_NCURSES_H
#include <ncurses.h>
#else
#include <curses.h>
#endif

#include "gpm-xterm.h"
#define RELEASE "0.97"

/******* This from liblow.c(libgpm) */

int gpm_flag = 0, gpm_tried = 0, gpm_fd = -1, gpm_hflag = 0;
int gpm_zerobased = 0, gpm_visiblepointer = 0, gpm_morekeys = 0;
struct timeval gpm_timeout =
{10, 0};

Gpm_Handler *gpm_handler = NULL;
void *gpm_data = NULL;

static int gpm_convert_event (char *mdata, Gpm_Event * ePtr);

/******* This from libcurses.c(libgpm) */
int
Gpm_Wgetch (WINDOW * win)
{
    int flag, result;
    int fd = STDIN_FILENO;
    static Gpm_Event ev;
    static struct timeval to =
    {0, 0}, tv1 =
    {0, 0}, tv2;
    static fd_set selSet;
    static int prevchar = EOF, clicks = 0;
    static char mdata[4];
    int c;

#define GET(win) ((win) ? wgetch(win) : getch())

#define GET_TIME(tv) (gettimeofday(&tv, (struct timezone *)NULL))
#define DIF_TIME(t1,t2) ((t2.tv_sec -t1.tv_sec) *1000+ \
                         (t2.tv_usec-t1.tv_usec)/1000)

    if (!gpm_flag)
	return GET (win);
    if (gpm_morekeys && gpm_handler)
	return (*gpm_handler) (&ev, gpm_data);

    gpm_hflag = 0;		/* not generated by handler (default) */

    if ((c = prevchar) != EOF) {	/* if ungetc() didn't suffice... */
	prevchar = EOF;
	return c;
    }
    while (1) {
	do {
	    FD_ZERO (&selSet);
	    FD_SET (fd, &selSet);
	    gpm_timeout.tv_sec = SELECT_TIME;
	    flag = select (fd + 1, &selSet, (fd_set *) NULL, (fd_set *) NULL, &to);
	}
	while (!flag);

	if ((c = GET (win)) != 0x1b)
	    return c;

	/* escape: go on */
	FD_ZERO (&selSet);
	FD_SET (fd, &selSet);
	if ((flag = select (fd + 1, &selSet, (fd_set *) NULL, (fd_set *) NULL, &to)) == 0)
	    return c;
	if ((c = GET (win)) != '[') {
	    ungetc (c, stdin);
	    return 0x1B;
	}
	/* '[': go on */
	FD_ZERO (&selSet);
	FD_SET (fd, &selSet);
	if ((flag = select (fd + 1, &selSet, (fd_set *) NULL, (fd_set *) NULL, &to)) == 0) {
	    ungetc (c, stdin);
	    return 0x1B;
	}
	if ((c = GET (win)) != 'M') {
	    ungetc (c, stdin);
	    prevchar = '[';
	    return 0x1B;
	}
	/* now, it surely is a mouse event */
	for (c = 0; c < 3; c++)
	    mdata[c] = GET (win);
	gpm_convert_event (mdata, &ev);

	if (gpm_handler && (result = (*gpm_handler) (&ev, gpm_data))) {
	    gpm_hflag = 1;
	    return result;
	}
    }				/* while(1) */
}

/******* This from liblow.c(libgpm) */
int
Gpm_Open (Gpm_Connect * conn, int flag)
{
    char *tty;
    int i;

    if ((tty = (char *) getenv ("TERM")) && !strncmp (tty, "xterm", 5)) {
	if (gpm_tried)
	    return gpm_fd;	/* already open */
	gpm_fd = -2;
	GPM_XTERM_ON;
	gpm_flag = 1;
	return gpm_fd;
    }
    return -1;
}

int
Gpm_Close (void)
{
    if (gpm_fd == -2)		/* xterm */
	GPM_XTERM_OFF;
    gpm_tried = 0;
    gpm_fd = -1;
    return 0;
}

int
Gpm_Getc (FILE * f)
{
#define DELAY_MS 500		/* stolen form mc */
    int flag, result;
    static Gpm_Event ev;
    int fd = fileno (f);
    static int count;
    static struct timeval to =
    {0, DELAY_MS * 1000}, tv1 =
    {0, 0}, tv2;
    static fd_set selSet;
    static int prevchar = EOF, clicks = 0;
    static char mdata[4];
    int c;

    /* Hmm... I must be sure it is unbuffered */
    if (!(count++))
	setvbuf (f, NULL, _IONBF, 0);

    if (!gpm_flag)
	return fgetc (f);

    /* If the handler asked to provide more keys, give them back */
    if (gpm_morekeys && gpm_handler)
	return (*gpm_handler) (&ev, gpm_data);
    gpm_hflag = 0;

    if ((c = prevchar) != EOF) {	/* if ungetc() didn't suffice... */
	prevchar = EOF;
	return c;
    }
    while (1) {
	do {
	    FD_ZERO (&selSet);
	    FD_SET (fd, &selSet);
	    gpm_timeout.tv_sec = SELECT_TIME;
	    flag = select (fd + 1, &selSet, (fd_set *) NULL, (fd_set *) NULL, &to);
	}
	while (!flag);

	if ((c = fgetc (f)) != 0x1b)
	    return c;

	/* escape: go on */
	FD_ZERO (&selSet);
	FD_SET (fd, &selSet);
	to.tv_usec = DELAY_MS * 1000;
	if ((flag = select (fd + 1, &selSet, (fd_set *) NULL, (fd_set *) NULL, &to)) == 0)
	    return c;
	if ((c = fgetc (f)) != '[') {
	    ungetc (c, stdin);
	    return 0x1B;
	}
	/* '[': go on */
	FD_ZERO (&selSet);
	FD_SET (fd, &selSet);
	to.tv_usec = DELAY_MS * 1000;
	if ((flag = select (fd + 1, &selSet, (fd_set *) NULL, (fd_set *) NULL, &to)) == 0) {
	    ungetc (c, f);
	    return 0x1B;
	}
	if ((c = fgetc (f)) != 'M') {
	    ungetc (c, f);
	    prevchar = '[';
	    return 0x1B;
	}
	/* now, it surely is a mouse event */
	for (c = 0; c < 3; c++)
	    mdata[c] = fgetc (f);
	gpm_convert_event (mdata, &ev);

	if (gpm_handler && (result = (*gpm_handler) (&ev, gpm_data))) {
	    gpm_hflag = 1;
	    return result;
	}
    }				/* while(1) */
}

int
Gpm_Repeat (int msec)
{
    struct timeval to =
    {0, 0};
    fd_set selSet;
    int fd = STDIN_FILENO;

    to.tv_usec = msec * 1000;
    FD_ZERO (&selSet);
    FD_SET (fd, &selSet);
    return (select (fd + 1, &selSet, (fd_set *) NULL, (fd_set *) NULL, &to) == 0);
}

/*-------------------------------------------------------------------*/
/* this is the real protocol conversion */
static int
gpm_convert_event (char *mdata, Gpm_Event * ePtr)
{
    static struct timeval tv1 =
    {0, 0}, tv2;
    static int clicks = 0;
    int c;

#define GET_TIME(tv) (gettimeofday(&tv, (struct timezone *)NULL))
#define DIF_TIME(t1,t2) ((t2.tv_sec -t1.tv_sec) *1000+ \
                         (t2.tv_usec-t1.tv_usec)/1000)


    /* Variable btn has following meaning: */
    c = mdata[0] - 32;		/* 0="1-down", 1="2-down", 2="3-down", 3="up" */

    if (c == 3) {
	ePtr->type = GPM_UP | (GPM_SINGLE << clicks);
	ePtr->buttons = 0;
	GET_TIME (tv1);
	clicks = 0;
    } else {
	ePtr->type = GPM_DOWN;
	GET_TIME (tv2);
	if (tv1.tv_sec && (DIF_TIME (tv1, tv2) < 250)) {	/* 250ms for double click */
	    clicks++;
	    clicks %= 3;
	} else
	    clicks = 0;

	switch (c) {
	case 0:
	    ePtr->buttons |= GPM_B_LEFT;
	    break;
	case 1:
	    ePtr->buttons |= GPM_B_MIDDLE;
	    break;
	case 2:
	    ePtr->buttons |= GPM_B_RIGHT;
	    break;
	default:		/* Nothing */
	    break;
	}
    }
    /* Coordinates are 33-based */
    /* Transform them to 1-based */
    ePtr->x = mdata[1] - 32;
    ePtr->y = mdata[2] - 32;
    return 0;
}
