// Read from USB joystick and buttons, send to Vectrex.

// This was originally usbjoy.cpp but has been cleaned up to remove any dependency on C++.

// This is very preliminary software, although it will output data from
// a joystick such as the Microsoft Sidewinder Pro to both Vectrex joystick
// ports, it has not yet been customised to handle two separate joystick
// devices simultaneously. (The sidewinder has > 8 buttons and both an
// analogue stick and a digital thumbstick, so can behave as if it were
// two independent Vectrex controllers)

// 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.

// WII: although it would be possible to build the Wii software into this
// program, it will be cleaner to make it a joystick device in its own
// right - see vec2linux.c and wiitest.c for code that could be modified
// to implement that with.

#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

static int CS_LEFT, CS_RIGHT, CLK, MOSI;

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 the joystick hotplug monitor 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";

    // 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"};

static int LeftJoyX = 0, LeftJoyY = 0, RightJoyX = 0, RightJoyY = 0, LinButtons = 0;


#define ONE_SECOND 1000000

// 13 is 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
const int gpio_b[4] = {25, 5, 6, 16};   // Vectrex buttons 1 2 3 4 on primary controller

#define PRESSED 1
#define RELEASED 0

void cleanup(void) {
  int i;
  for (i = 0; i <= 3; i++) {
    digitalWrite(gpio_a[i], RELEASED);
    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;
    else if (jse.number == 4)
      LeftJoyY = jse.value >> 8;
    else if (jse.number == 5)
      LeftJoyX = jse.value >> 8;
  } 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);
      } else if ((4 <= jse.number) && (jse.number <= 7)) {
        LinButtons &= ~(1 << jse.number);
        digitalWrite(gpio_a[jse.number-4], RELEASED);
      }
    } else if (jse.value == 1) {
      if ((0 <= jse.number) && (jse.number <= 3)) {
        LinButtons |= 1 << jse.number;
        digitalWrite(gpio_b[jse.number], PRESSED);
      } else if ((4 <= jse.number) && (jse.number <= 7)) {
        LinButtons |= 1 << jse.number;
        digitalWrite(gpio_a[jse.number-4], PRESSED);
      }
    } 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];

#ifdef NEVER
typedef short int fp14; // 16 bits on Vectrex  (fp2.14 not fp18.14)
typedef unsigned char uint8_t;
typedef   signed char  int8_t;

//----------------------------------------------
//
// 2.14 fixed point sine and cosine tables.
//
// These tables assume that there are 256 angles
// in a circle.
//

#define int2fp(x) ((x) << 14)
#define fp2int(x) ((x) >> 14)

const fp14 qsine[65] = {
  0,    402,    803,   1205,   1605,   2005,   2404,   2801,
  3196,   3589,   3980,   4369,   4756,   5139,   5519,   5896,
  6269,   6639,   7005,   7366,   7723,   8075,   8423,   8765,
  9102,   9434,   9759,  10079,  10393,  10701,  11002,  11297,
  11585,  11866,  12139,  12406,  12665,  12916,  13159,  13395,
  13622,  13842,  14053,  14255,  14449,  14634,  14810,  14978,
  15136,  15286,  15426,  15557,  15678,  15790,  15892,  15985,
  16069,  16142,  16206,  16260,  16305,  16339,  16364,  16379,
  16384,
};

static inline fp14 hsine(uint8_t x) {
  return x>=64 ? qsine[128-x] : qsine[x];
}

static inline fp14 sine(uint8_t x) {
  return x&0x80 ? -hsine(x&0x7f) : hsine(x);
}

static inline fp14 fpSin(uint8_t x) {
  return sine((uint8_t)(x));
}

static inline fp14 fpCos(uint8_t x) {
  return sine((uint8_t)((x)+64));
}

static inline int8_t sin127(int8_t x) {
  return 63-(int8_t)(sine((uint8_t)x&255U)>>8);
}

static inline int8_t cos127(int8_t x) {
  return 63-(int8_t)(sine(((uint8_t)x+64U)&255U)>>8);
}
#endif // NEVER

int main(int argc, char **argv) {
  int tick = 0;
  int x, y;
  int fd;
  char device_name[128];
  // Sidewinder Pro for now on my machine. Unfortunately
  // button numbers are different from Vectrex controller
  // unless remapped
  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.
  
  CS_RIGHT = 8; CS_LEFT = 26; CLK = 11; MOSI = 10;
  
  pinMode(CS_LEFT, OUTPUT); pinMode(CS_RIGHT, OUTPUT); pinMode(CLK, OUTPUT); 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 */
  {
    int i;
    for (i = 0; i <= 3; i++) {
      // Init state is no buttons pressed.
      pinMode(gpio_a[i], OUTPUT);
      digitalWrite(gpio_a[i], RELEASED);
      pinMode(gpio_b[i], OUTPUT);
      digitalWrite(gpio_b[i], RELEASED);
    }
  }

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

  // These are the pins used to connect to the daisy-chained digipots:
  CS_RIGHT = 8; CS_LEFT = 26; CLK = 11; MOSI = 10;
  
  pinMode(CS_LEFT, OUTPUT); pinMode(CS_RIGHT, OUTPUT); pinMode(CLK, OUTPUT); pinMode(MOSI, OUTPUT);
  
  digitalWrite(CLK, LOW);

  open_mouse();

  //circle(); // maybe want some sort of feedback to indicate that USB converter is active now.
  
  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
      // read joysticks, buttons ...
      while (read(fd, &jse, sizeof(jse)) > 0) {
        process_event(jse); // sets JoyXY values
      }

      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);

      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;
}