#include <dlfcn.h>
#include<stdarg.h>
#include <iostream>
#include <linux/joystick.h>

class Libc
{
public:
  static int (*open)(const char*, int);
  static int (*ioctl)(int d, int request, ...);
  static int (*close)(int fd);
  static ssize_t (*read)(int fd, void *buf, size_t count);
  static ssize_t (*write)(int fd, const void *buf, size_t count);
  static bool ready;

  static void init()
  {
    if (!ready)
      {
        ready = true;

        void* handle = dlopen("/lib/libc.so.6", RTLD_LAZY);
        if (!handle)
          {
            std::cerr << dlerror() << std::endl;
            exit(EXIT_FAILURE);
          }
  
        *(void**)(&open)  = sym(handle, "open");
        *(void**)(&close) = sym(handle, "close");
        *(void**)(&read)  = sym(handle, "read");
        *(void**)(&write) = sym(handle, "write");
        *(void**)(&ioctl) = sym(handle, "ioctl");
      }
  }

  static void* sym(void* handle, const char* sym)
  {
    char* error;
    void* s = dlsym(handle, sym);
    if ((error = dlerror()) != NULL)
      {
        std::cerr << error << std::endl;
        exit(EXIT_FAILURE);
      }
    return s;
  }

  static void deinit()
  {
  }
};

int (*Libc::open)(const char*, int);
int (*Libc::ioctl)(int d, int request, ...);
int (*Libc::close)(int fd);
ssize_t (*Libc::read)(int fd, void *buf, size_t count);
ssize_t (*Libc::write)(int fd, const void *buf, size_t count);
bool Libc::ready = false;

extern "C" {
  int open(const char* pathname, int flags)
  {
    Libc::init();
    return Libc::open(pathname, flags);
  }

  ssize_t write(int fd, const void *buf, size_t count)
  {
    Libc::init();
    return Libc::write(fd, buf, count);
  }

  ssize_t read(int fd, void *buf, size_t count)
  {
    Libc::init();

    // fixme: check fd, so that we only use true joystick fds
    ssize_t ret = Libc::read(fd, buf, count);
    
    struct js_event* js = (struct js_event*)buf;

    if (js->type & JS_EVENT_BUTTON)
      {
        if (js->number == 1)
          js->number = 0;
        else if (js->number == 0)
          js->number = 1;        
      }

    return ret;
  }

  int ioctl(int d, long unsigned int request, ...)
  {
    Libc::init();

    if (request == JSIOCGAXES)
      {
        unsigned char axes;
        Libc::ioctl(d, request, &axes);

        va_list ap;
        va_start(ap, request);
        *va_arg(ap, unsigned char*) = axes/2;
        va_end(ap);

        return 0;
      }

    if (request == JSIOCGBUTTONS)
      {
        unsigned char buttons;
        Libc::ioctl(d, request, &buttons);

        va_list ap;
        va_start(ap, request);
        *va_arg(ap, unsigned char*) = buttons/2;
        va_end(ap);

        return 0;
      }
    else if ((request & ~(_IOC_SIZEMASK << _IOC_SIZESHIFT)) == JSIOCGNAME(0))
      {
        va_list ap;
        va_start(ap, request);
        snprintf(va_arg(ap, char*), _IOC_SIZE(request), "Virtual Joystick Driver");
        va_end(ap);

        return 0;
      }
    else
      {
        va_list ap;
        va_start(ap, request);
        int ret = Libc::ioctl(d, request, va_arg(ap, unsigned long));
        va_end(ap);
        return ret;
      }
  }
}

/* EOF */

