// usbjoy: read from USB joystick and buttons and send to Vectrex.


// This is based on my earlier usbjoy.cpp but has been converted
// to C and cleaned up in order to remove any dependency on C++.

// A manual for this project is being written at https://www.gtoal.com/src/joystick/manual.html

// Compile by issuing:
// gcc -I. -o usbjoy usbjoy.c -lwiringPi `sdl2-config --cflags --libs` -lm

// To do: https://gist.github.com/uobikiemukot/457338b890e96babf60b - get the extra mouse parameters

// To do: request exclusive access when using mouse, so it is not also active
// on the desktop.  See https://forums.raspberrypi.com/viewtopic.php?t=213801
// for details.  May need to use different mouse device than currently.

// Need to only open mouse when no joystick, and close again when joystick
// replaced - otherwise won't ever be able to use desktop because mouse will
// always be suppressed.

// WiringPi is deprecated as it is no longer maintained by its author, however
// we'll continue to use a local copy because WiringPi does not require 'su'
// privileges.  The alternatives do.

#include "wiringPi.h"

typedef int byte;

#include <errno.h>
#include <fcntl.h>
#include <linux/joystick.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h> // for exit()
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h> // usleep

#ifdef DUAL

// There are instructions for creating a dual-input version of this circuit
// on a breadboard, at https://gtoal.com/vectrex/joystick/USB-to-Vectrex.html
// - that version of the circuit uses a slightly different GPIO pinout, even
// for the half of the circuit that is in common with the single-port version
// of the circuit used by the PCB.  You should "#define DUAL 1" (or compile
// with -DDUAL) to enable this version.

static int CS_LEFT; // left port, i.e. secondary controller
#endif
static int CS_RIGHT, CLK, MOSI;

  /**
   * These two procedures and a small amount of code below
   * is derived from the Arduino library
   * for using the MCP42010 digital potentiometer with SPI.
   *
   * Copyright (c) 2015 Stefan Mensink. All right reserved.
   * Find this on Github: https://github.com/mensink/arduino-lib-MCP42010
   *
   * MIT license, all text above must be included in any redistribution
   **/
  
static void transferSPI(byte data) {
  byte i;
  for (i=1; i<=8; i++) {
    digitalWrite(MOSI, (data >> (8-i)) & 1 ? HIGH : LOW);
    digitalWrite(CLK, HIGH); // There is no delay here unless digitalWrite has an implicit delay?
    digitalWrite(CLK, LOW);  // In any case, it works, for whatever reason.
  }
}

// Sets the given pot (1 or 2) on the given controller (left or right) to a given value (0-255)
void setPot(byte chip, byte pot, byte value) {
  digitalWrite(chip, LOW);
  transferSPI(pot);
  transferSPI(value);
  digitalWrite(chip, HIGH);
}


//---------
// This program uses code derived from the joystick hotplug monitor at
// https://github.com/carmiker/sdl2-hotplug-example/blob/master/sdl2hp.c
// which detects when joystick devices are added or removed:

/*
BSD Zero Clause License

Copyright (c) 2022 Rupert Carmichael

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/

//---------
int open_joystick(const char *devname) {
  if (devname == NULL) return -1;
  return open(devname, O_RDONLY | O_NONBLOCK);
}

static int mouse_fd, mouse_bytes;
static signed char mouse_data[3];
static int mouse_attached = 0;

void open_mouse(void) {
  const char *mouse_device = "/dev/input/mice"; // multiple mice supported.

  // Open Mouse:
  mouse_fd = open(mouse_device, O_RDWR /* | O_RDONLY */ | O_NONBLOCK);
  if (mouse_fd == -1) {
    mouse_attached = 0;
    printf("ERROR Opening %s - mouse not available.\n", mouse_device);
  } else mouse_attached = 1;
}

int axes = 0, buttons = 0;
void print_device_info(int fd) {
  char name[128];

  ioctl(fd, JSIOCGAXES, &axes);
  ioctl(fd, JSIOCGBUTTONS, &buttons);
  ioctl(fd, JSIOCGNAME(sizeof(name)), &name);

  printf("Your joystick is a \"%s\"\nIt supports %d Axes and %d Buttons\n",
         name, axes, buttons); // Not that I use these yet :-/
}

const char *Pressed[2] = {"released", "pressed"};

#ifdef DUAL
static int LeftJoyX = 0, LeftJoyY = 0;
#endif
static int RightJoyX = 0, RightJoyY = 0, LinButtons = 0;

#define ONE_SECOND 1000000

#ifdef DUAL
// The dual controller on a breadboard has moved some of the pins around:
// 13 was a pwm pin which we'll reserve for generating sound later.
const int gpio_a[4] = {17, 27, 22, 23}; // Vectrex buttons 1 2 3 4 on secondary controller
// primary controller on breadboard:
const int gpio_b[4] = {25, 5, 6, 16};   // Vectrex buttons 1 2 3 4 on primary controller
#else
// primary controller on pcb:
const int gpio_b[4] = {6, 13, 26, 19};   // Vectrex buttons 1 2 3 4 on primary controller
#endif

#define PRESSED 1
#define RELEASED 0

void cleanup(void) {
  int i;
  for (i = 0; i <= 3; i++) {
#ifdef DUAL
    digitalWrite(gpio_a[i], RELEASED);
#endif
    digitalWrite(gpio_b[i], RELEASED);
  }
}

void process_event(struct js_event jse) {
  if (jse.type == 2) {
    //printf("Axis %d Value %d\n", jse.number, jse.value);
    if (jse.number == 0)
      RightJoyX = jse.value >> 8;
    else if (jse.number == 1)
      RightJoyY = jse.value >> 8;
#ifdef DUAL
    else if (jse.number == 4)
      LeftJoyY = jse.value >> 8;
    else if (jse.number == 5)
      LeftJoyX = jse.value >> 8;
#endif
  } else if (jse.type == 1) {
    //printf("Button %d %s\n", jse.number, Pressed[jse.value]);
    if (jse.value == 0) {
      if ((0 <= jse.number) && (jse.number <= 3)) {
        LinButtons &= ~(1 << jse.number);
        digitalWrite(gpio_b[jse.number], RELEASED);
      }
#ifdef DUAL
      else if ((4 <= jse.number) && (jse.number <= 7)) {
        LinButtons &= ~(1 << jse.number);
        digitalWrite(gpio_a[jse.number-4], RELEASED);
      }
#endif
    } else if (jse.value == 1) {
      if ((0 <= jse.number) && (jse.number <= 3)) {
        LinButtons |= 1 << jse.number;
        digitalWrite(gpio_b[jse.number], PRESSED);
      }
#ifdef DUAL
      else if ((4 <= jse.number) && (jse.number <= 7)) {
        LinButtons |= 1 << jse.number;
        digitalWrite(gpio_a[jse.number-4], PRESSED);
      }
#endif
    } else {
    }
  } else if (jse.type == 130) {
    printf("Axis %d Supported\n", jse.number);
  } else if (jse.type == 129) {
    printf("Button %d Supported\n", jse.number);
  } else {
    printf("Type=%d Value=%d\n", jse.type, jse.number);
  }
}

#include <SDL.h>

#define MAXPORTS 16

static SDL_Joystick *joystick[MAXPORTS];
static SDL_Haptic *haptic[MAXPORTS];
static int jsports[MAXPORTS];
static int jsiid[MAXPORTS];


int main(int argc, char **argv) {
  int tick = 0;
  int i;
  int x, y;
  int fd;
  char device_name[128];
  struct js_event jse;
  SDL_Event event;
  int joystick_attached = 0;
  int running = 1;

  SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC);

  if (strrchr(argv[0], '/'))
    argv[0] = strrchr(argv[0], '/') + 1;
  if (argc == 2)
    sprintf(device_name, "%s", argv[1]);
  if (argc > 2) {
    printf("syntax: %s {optional joystick device such as /dev/input/js0}\n",
           argv[0]);
    exit(1);
  }
  wiringPiSetupGpio();

  // These are the pins used to connect to the two digipots.
  // Originally I intended to use daisy-chain mode, but finding
  // accurate documentation on that proved impossible, so I
  // used 2 GPIO pins to address each pot independently, which
  // works just fine.

#ifdef DUAL
  CS_LEFT = 26;  pinMode(CS_LEFT, OUTPUT);
#endif
  CS_RIGHT = 8;  pinMode(CS_RIGHT, OUTPUT);
  
  CLK = 11;      pinMode(CLK, OUTPUT);
  MOSI = 10;     pinMode(MOSI, OUTPUT);
  
  digitalWrite(CLK, LOW);

  // We must still clean up even if the user exits with ^C
  atexit(cleanup); /* Release all buttons, stop DMA, release resources */

  /* Set GPIO modes to OUTPUT for all 4 buttons on each controller */
  for (i = 0; i <= 3; i++) {      // Init state is 'no buttons pressed.'
#ifdef DUAL
    pinMode(gpio_a[i], OUTPUT);   digitalWrite(gpio_a[i], RELEASED);
#endif
    pinMode(gpio_b[i], OUTPUT);   digitalWrite(gpio_b[i], RELEASED);
  }

  int mouse_x = 127, mouse_y = 127;
  open_mouse();

  for (;;) {

    while (SDL_PollEvent(&event)) {
      
      switch (event.type) {
	
      case SDL_QUIT: {
        //fprintf(stderr, "SDL_QUIT\n");
        running = 0;
        break;
      } // case SDL_QUIT

      case SDL_JOYDEVICEADDED: {
        int port = 0;
        joystick_attached += 1;
        fprintf(stderr, "SDL_JOYDEVICEADDED\n");

        // Choose next unplugged port
        for (int i = 0; i < MAXPORTS; ++i) {
          if (!jsports[i]) {
            joystick[i] = SDL_JoystickOpen(event.jdevice.which);
            SDL_JoystickSetPlayerIndex(joystick[i], i);
            jsports[i] = 1;
            jsiid[i] = SDL_JoystickInstanceID(joystick[i]);
            port = i;
            break;
          }
        }

        fprintf(stderr,
                "Joystick Connected: %s\n"
                "\tInstance ID: %d, Player: %d\n"
                "\tPort /dev/input/js%0d\n",
                SDL_JoystickName(joystick[port]), jsiid[port],
                SDL_JoystickGetPlayerIndex(joystick[port]), port);

        sprintf(device_name, "/dev/input/js%0d", port);

        if (SDL_JoystickIsHaptic(joystick[port])) {
          haptic[port] = SDL_HapticOpenFromJoystick(joystick[port]);
          SDL_HapticRumbleInit(haptic[port]) < 0
              ? fprintf(stderr, "Force Feedback Enable Failed: %s\n", SDL_GetError())
              : fprintf(stderr, "Force Feedback Enabled\n");
        } else {
          fprintf(stderr, "Device is not haptic\n");
        }
        fprintf(stderr, "Currently connected joystick devices: %s%s%s%s\n\n",
                jsports[0] ? "/dev/input/js0 " : "",
                jsports[1] ? "/dev/input/js1 " : "",
                jsports[2] ? "/dev/input/js2 " : "",
                jsports[3] ? "/dev/input/js3 " : "");

        fd = open_joystick(device_name);
        if (fd < 0) {
          printf("Could not locate joystick device %s\n", device_name);
          exit(1);
        }

        print_device_info(fd);

        break;
      } // case SDL_JOYDEVICEADDED

      case SDL_JOYDEVICEREMOVED: {
        int id = event.jdevice.which;
        joystick_attached -= 1;
        fprintf(stderr, "SDL_JOYDEVICEREMOVED\n");
        fprintf(stderr, "Instance ID: %d\n", id);
        for (int i = 0; i < MAXPORTS; ++i) {
          // If it's the one that got disconnected...
          if (jsiid[i] == id) {
            if (SDL_JoystickIsHaptic(joystick[i]))
              SDL_HapticClose(haptic[i]);
            jsports[i] = 0; // Notify that this is unplugged
            fprintf(stderr, "Joystick /dev/input/js%0d Disconnected\n", i);
            SDL_JoystickClose(joystick[i]);
            break;
          }
        }
	if (!jsports[0] && !jsports[1] && !jsports[2] && !jsports[3]) {
	  fprintf(stderr, "Waiting for a USB Joystick to be attached.\n");
	  joystick_attached = 0;
	} else {
          fprintf(stderr, "Currently connected joystick devices: %s%s%s%s\n\n",
                  jsports[0] ? "/dev/input/js0 " : "",
                  jsports[1] ? "/dev/input/js1 " : "",
                  jsports[2] ? "/dev/input/js2 " : "",
                  jsports[3] ? "/dev/input/js3 " : "");
	}
        // go quiescent until another joystick is attached.
        break;
      } // case SDL_JOYDEVICEREMOVED

      } // switch (event.type)

    } // loop while (SDL_PollEvent(&event))
    
    if (!running)
      break;
    
    if (joystick_attached) {
      int vx, vy;
      usleep(1000000 / 120); // 120 polls per second. (2 * 60Hz, for safety.  Nyquist.)
      
      // read joysticks, buttons ...
      while (read(fd, &jse, sizeof(jse)) > 0) {
        process_event(jse); // sets JoyXY values
      }

#ifdef DUAL
      x = (LeftJoyX ^ 128) & 255; // currentJoy1X;
      y = (LeftJoyY ^ 128) & 255; // currentJoy1Y;
      // whether these are inverted or not depends on order of +5V 0V -5V pins on PCB.
      // First voltage divider has +5V <-> 0V inputs, second has 0V <-> -5V.
      vx = x;
      vy = 255-y;
      setPot(CS_LEFT, 0B00010001, vx);
      setPot(CS_LEFT, 0B00010010, vy);
      //fprintf(stderr, "Setting left  potentiometer values  vx=%d vy=%d       \r", vx, vy);
#endif

      x = (RightJoyX ^ 128) & 255; // currentJoy1X;
      y = (RightJoyY ^ 128) & 255; // currentJoy1Y;
      vx = 255-x;
      vy = y;
      setPot(CS_RIGHT, 0B00010001, vx);
      setPot(CS_RIGHT, 0B00010010, vy);
      //fprintf(stderr, "Setting right potentiometer values  vx=%d vy=%d       \r", vx, vy);

    } else if (mouse_attached) {
      int left, middle, right;

        // Read Mouse     
        mouse_bytes = read(mouse_fd, mouse_data, sizeof(mouse_data));

        if (mouse_bytes > 0) {
            left = mouse_data[0] & 0x1;
            right = mouse_data[0] & 0x2;
            middle = mouse_data[0] & 0x4;

            mouse_x -= mouse_data[1]; if (mouse_x > 255) mouse_x = 255; if (mouse_x < 0) mouse_x = 0;
            mouse_y -= mouse_data[2]; if (mouse_y > 255) mouse_y = 255; if (mouse_y < 0) mouse_y = 0;
            setPot(CS_RIGHT, 0B00010001, mouse_x);
            setPot(CS_RIGHT, 0B00010010, mouse_y);
            fprintf(stderr, "dx=%d, dy=%d, x=%d, y=%d, left=%d, middle=%d, right=%d\n",
		    mouse_data[1], mouse_data[2],
		    mouse_x, mouse_y,
		    left, middle, right);
	    digitalWrite(gpio_b[0], (left != 0)&1);
	    digitalWrite(gpio_b[2], (middle != 0)&1);
	    digitalWrite(gpio_b[3], (right != 0)&1);
        }   

    } else {
      usleep(1000000 / 120); // set rate at which the joysticks are polled.
    }
  } // while running

  exit(0);
  return 0;
}
