/*
 *  acm : an aerial combat simulator for X
 *  Copyright (C) 1991-1998  Riley Rainey
 *
 *  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; version 2 dated June, 1991.
 *
 *  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.
 */


#define joystick_IMPORT
#include "joystick.h"

#ifdef WINNT

// No joystick support under Win32. Provides dummy functions instead.

void joystick_setPort(char * name){}

void joystick_calibrate(void){}

void joystick_getPosition(double *x1, double *y1,
	double *x2, double *y2, int *switches)
{
	*x1 = *y1 = *x2 = *y2 = 0.0;
	*switches = 0;
}

int joystick_processInput(void)
{
	return -1;
}

#else

#include <stdio.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>

static int min[4] = {999, 999, 999, 999};
static int max[4] = {-1, -1, -1, -1};
static int home[4];
static int value[5] = {128, 128, 128, 128, 0};

static char *joystick_port = NULL;

void
joystick_setPort (char * name)
{
    joystick_port = name;
}

/*
 *  ProcessJoystickInput()
 *
 *  This procedure should be called just before you call GetJoystickInput().
 *  In processes any pending input from the Workstation Gameport.
 */

static int init = 0, state;

int
joystick_processInput()
{
    static int fd;
    static int x1, y1, x2, y2, switches;
    int flags, n, i, updated = 0;
    unsigned char bytes[512], *p;

    if (init < 0) {
		return init;
    } else if (init == 0) {
	struct termios term;

	init = -1;

	if (!joystick_port) {
	    return init;
	}

	if ((fd = open(joystick_port, O_RDWR)) < 0) {
	    perror("joystick port open failed");
	    return -1;
	}

/*
 *  Set POSIX non-blocking I/O
 */

	if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
	    perror("F_GETFL failed");
	    return -1;
	}
	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) {
	    perror("F_SETFL failed");
	    return -1;
	}

/*
 *  Assert both DTR and RTS (these pins supply power to
 *  the Workstation Gameport).
 */

#ifdef TIOCM_RTS
	flags = TIOCM_RTS | TIOCM_DTR;
	if (ioctl(fd, TIOCMSET, &flags) == -1) {
	    perror("ioctl failed");
	    return -1;
	}
#endif

/*
 *  Condition the TTY line to talk to the converter
 */

	if (tcgetattr(fd, &term) != 0) {
	    perror("tcgetattr failed");
	    return -1;
	}
	term.c_iflag = IGNBRK | IGNPAR;
	term.c_oflag = 0;
	term.c_cflag = CLOCAL | CS8 | CSTOPB | CREAD;
	term.c_lflag &= ~(ECHO | ICANON);
	for (i = 0; i < NCCS; ++i) {
	    term.c_cc[i] = -1;
	}
	term.c_cc[VMIN] = 1;
	term.c_cc[VTIME] = 0;
	cfsetospeed(&term, B9600);
	cfsetispeed(&term, B9600);
	if (tcsetattr(fd, TCSAFLUSH, &term) != 0) {
	    perror("tcsetattr failed");
	    return -1;
	}
	state = 0;
	init = 1;
    }
/*
 *  Read a whole bunch of characters from the tty
 */

    n = read(fd, bytes, sizeof bytes);
	if (n != -1) {
		printf("%d ->", n);
		for (state = 0; state < n; state++) printf(" %d", bytes[state]);
		printf("\n");
	}

/*
 *  And then process them ...
 */

    for (p = bytes; n > 0; --n) {

	switch (state) {

	case 0:
	  if ((*p & 0x80) != 0) {
	    switches = (((int) *p) >> 3) & 0x0F;
	    x1 = (((int) *p) & 0x07) << 5;
	    state = 1;
	  }
	  break;

	case 1:
	  if ((*p & 0x80) != 0) { state = 0; break; }
	  x1 += (((int) *p) >> 2) & 0x1F;
	  y1 = (((int) *p) & 0x03) << 6;
	  state = 2;
	  break;

	case 2:
	  if ((*p & 0x80) != 0) { state = 0; break; }
	  y1 += (((int) *p) >> 1) & 0x3F;
          x2 = (((int) *p) & 0x01) << 7;
	  state = 3;
	  break;

	case 3:
	  if ((*p & 0x80) != 0) { state = 0; break; }
	  x2 += ((int) *p) & 0x7F;
	  state = 4;
	  break;

	case 4:
	  if ((*p & 0x80) != 0) { state = 0; break; }
	  y2 = ((int) *p) << 1;
	  state = 5;
	  break;

	case 5:
	  if ((*p & 0x80) != 0) { state = 0; break; }
	  y2 += ((int) *p) & 0x01;
	  value[0] = 255 - x1;
	  value[1] = 255 - y1;
	  value[2] = x2;
	  value[3] = y2;
	  value[4] = switches;
	  state = 0;
	  updated = 1;
	  // printf("%d %d %d -> %d %d\n", switches, x1, y1, x2, y2);
	  break;
	}
	++p;
    }
    return updated;
}
void
joystick_calibrate()
{
	int i;

	printf("\n\nCenter the joystick, click when ready!\n\n");
	for (;;)
		if (joystick_processInput()) 
			if (value[4] != 0) break;
	for (i = 0; i < 4; i++) home[i] = value[i];
	for (;;) if (joystick_processInput()) if (value[4] == 0) break;

	printf("\n\nPosition stick in minimum and maximum values, then click\n\n");
	for (;;)
		{
			if (joystick_processInput())
				{
					for (i = 0; i < 4; i++)
						{
							if (value[i] < min[i]) min[i] = value[i];
							if (value[i] > max[i]) max[i] = value[i];
						}
					if (value[4] != 0) break;
				}
		}
	for (;;) if (joystick_processInput()) if (value[4] == 0) break;
	printf("\n\nJoystick Calibrated\n");
}

static double
fixJoy(int i)
{
  // Forward joysticks.
  if (min[i] < max[i])
  {      
    if (value[i] <= home[i])
      return (double) (value[i] - min[i]) / (double)(home[i] - min[i]) - 1.0;
    else
      return (double) (value[i] - home[i]) / (double)(max[i] - home[i]);
  }
  // Backwards joysticks.
  else 
    if (value[i] >= home[i])
      return ((double)(min[i] - value[i]) / (double)(min[i] - home[i]) - 1.0);
    else 
      return ((double)(home[i] - value[i]) / (double)(home[i] - max[i]));
}

void
joystick_getPosition(double *x1, double *y1,
		    double *x2, double *y2, int *switches)
{
  if (min[0] == 999)
  {
    *x1 = 0.0;  *y1 = 0.0;  *x2 = 0.0;  *y2 = 0.0; *switches = value[4];
    return;
  }
  
  *x1 = fixJoy(0);
  if (*x1 < -1.0) *x1 = -1.0;

  *y1 = fixJoy(1);
  if (*y1 < -1.0) *y1 = -1.0;

  *x2 = fixJoy(2);
  if (*x2 < -1.0) *x2 = -1.0;

  *y2 = fixJoy(3);
  if (*y2 < -1.0) *y2 = -1.0;
  *switches = value[4];
}

#endif


/*
 * 2017-03-20 Umberto Salsi:
 * The source code below for Colorado Spectrum Workstation Gameport(TM) was
 * in the "joystick-cms.c" but apparently not used. Save here just in case.
 */

#ifdef ___________NOT__USED______________

/*
 *  Colorado Spectrum Workstation Gameport(TM)
 *
 *  Interface Notes by Riley Rainey (rainey@netcom.com)
 *
 *  The Colorado Spectrum Workstation Gameport allows you to connect
 *  a PC joystick to a serial port on your Unix workstation.  It presents
 *  a DB-9 plug that can be attached directly into any system that has a
 *  PC-style 9 pin serial port.  I created a simple adapter cable to convert a
 *  Sparc IPX DIN-8 serial connector to the 9-pin that this unit requires.
 *
 *  The Workstation Gameport costs about $100.
 *  Colorado Spectrum accepts credit card phone orders: +1 (970) 225-6929
 *
 *  The workstation gameport emits asynchronous six byte sequences
 *  up to 30 times per second.  If the state of switches and joystick pots
 *  does not change, no packet is emitted.  The data rate is 9600 bps;
 *  eight data bits and two stop bits.
 *
 *  Byte  Contents
 *  ----  ----------------------------------------------
 *   0    Sync Byte (always zero)
 *   1    Switches  (J2s2, J2s1, J1s2, J1s1, 0, 0, 0, 0)
 *   2    J1x
 *   3    J1y
 *   4    J2x
 *   5    J2y
 *
 *  The driver (this module) must ensure that both DTR and RTS
 *  are raised to supply power to the joystick adapter.
 */

#include <stdio.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>

static int max[4] =
{255, 255, 255, 255};
static int home[4] =
{128, 128, 128, 128};
static int value[5] =
{128, 128, 128, 128, 0};

static char *joystick_port = NULL;

void
joystick_setPort(char *name)
{
	joystick_port = name;
}

void
joystick_calibrate(void)
{
	int       i;

	for (i = 0; i < 4; ++i) {
		home[i] = value[i];
	}
}

void
joystick_getPosition(double *x1, double *y1,
					double *x2, double *y2, int *switches)
{
	*x1 = (double) (value[0] - home[0]) / (max[0] - home[0]);
	if (*x1 < -1.0) {
		*x1 = -1.0;
	}
	*y1 = (double) (value[1] - home[1]) / (max[1] - home[1]);
	if (*y1 < -1.0) {
		*y1 = -1.0;
	}
	*x2 = (double) (value[2] - home[2]) / (max[2] - home[2]);
	if (*x2 < -1.0) {
		*x2 = -1.0;
	}
	*y2 = (double) (value[3] - home[3]) / (max[3] - home[3]);
	if (*y2 < -1.0) {
		*y2 = -1.0;
	}
	*switches = value[4];
}

/*
 *  ProcessJoystickInput()
 *
 *  This procedure should be called just before you call GetJoystickInput().
 *  In processes any pending input from the Workstation Gameport.
 */

static int init = 0, state;

int
joystick_processInput(void)
{
	static int fd;
	static int x1, y1, x2, switches;
	int       flags, n, i, updated = 0;
	unsigned char bytes[512], *p;

	if (init < 0) {
		return init;
	}
	else if (init == 0) {
		struct termios term;

		init = -1;

		if (!joystick_port) {
			return init;
		}

		if ((fd = open(joystick_port, O_RDWR)) < 0) {
			perror("joystick port open failed");
			return -1;
		}

/*
 *  Set POSIX non-blocking I/O
 */

		if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
			perror("F_GETFL failed");
			return -1;
		}
		if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) {
			perror("F_SETFL failed");
			return -1;
		}

/*
 *  Assert both DTR and RTS (these pins supply power to
 *  the Workstation Gameport).
 */

#ifdef TIOCM_RTS
		flags = TIOCM_RTS | TIOCM_DTR;
		if (ioctl(fd, TIOCMSET, &flags) == -1) {
			perror("ioctl failed");
			return -1;
		}
#endif

/*
 *  Condition the TTY line to talk to the converter
 */

		if (tcgetattr(fd, &term) != 0) {
			perror("tcgetattr failed");
			return -1;
		}
		term.c_iflag = IGNBRK | IGNPAR;
		term.c_oflag = 0;
		term.c_cflag = CLOCAL | CS8 | CSTOPB | CREAD;
		term.c_lflag &= ~(ECHO | ICANON);
		for (i = 0; i < NCCS; ++i) {
			term.c_cc[i] = -1;
		}
		term.c_cc[VMIN] = 1;
		term.c_cc[VTIME] = 0;
		cfsetospeed(&term, B9600);
		cfsetispeed(&term, B9600);
		if (tcsetattr(fd, TCSAFLUSH, &term) != 0) {
			perror("tcsetattr failed");
			return -1;
		}
		state = 0;
		init = 1;
	}
/*
 *  Read a whole bunch of characters from the tty
 */

	n = read(fd, bytes, sizeof bytes);

/*
 *  And then process them ...
 */

	for (p = bytes; n > 0; --n) {

		switch (state) {

		case 0:
			if (*p == 0) {
				state = 1;
			}
			break;

		case 1:
			if ((*p & 0x0f) == 0 || 1) {
				switches = *p >> 4;
				state = 2;
			}
			else {
				state = 0;
			}
			break;

		case 2:
			x1 = *p;
			state = 3;
			break;

		case 3:
			y1 = *p;
			state = 4;
			break;

		case 4:
			x2 = *p;
			state = 5;
			break;

		case 5:
			state = 0;
			value[0] = x1;
			value[1] = y1;
			value[2] = x2;
			value[3] = *p;
			value[4] = switches;
			updated = 1;
			break;
		}
		++p;
	}
	return updated;
}


#endif /* ___NOT__USED___ */