#include <pspkernel.h>
#include <pspdebug.h>
#include <pspdisplay.h>
#include <pspctrl.h>
#include <pspsircs.h>
#include <stdlib.h>
#include <string.h>

// Types
typedef unsigned short UInt16;
typedef short Int16;
typedef unsigned char UInt8;
typedef char Int8;

// States
#define MODE_WAIT_FRAME 0
#define MODE_STORE      1

int frame_decode (UInt8 c);
int frame_analyze (int fd);

// Global variables that handle paddle values and button states
UInt16 paddleA; // 10bits right aligned
UInt16 paddleB; // 10bits right aligned
UInt8 buttonA; // Corresponds to atari joystick button too
UInt8 buttonB;
UInt8 atariJ; // contains all atari joystick bits. Use ATARI_*_MASK to get corresponding info

#define ATARI_UP_MASK 0x10
#define ATARI_DOWN_MASK 0x08
#define ATARI_LEFT_MASK 0x04
#define ATARI_RIGHT_MASK 0x02
#define ATARI_BUTTON_MASK 0x01

#define MODE_WAIT_FRAME 0
#define MODE_STORE      1

#define FRAME_SIZE   5
UInt8 mode=MODE_WAIT_FRAME;
UInt8 cnt=0;
UInt8 frame_buf[6];


/* Define the module info section */
PSP_MODULE_INFO("IRDAJoy", 0, 1, 1);

/* Define the main thread's attribute value (optional) */
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);

/* Define printf, just to make typing easier */
#define printf	pspDebugScreenPrintf

/* Exit callback */
int exit_callback(int arg1, int arg2, void *common) {
  sceKernelExitGame();

  return 0;
}

/* Callback thread */
int CallbackThread(SceSize args, void *argp) {
  int cbid;

  cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
  sceKernelRegisterExitCallback(cbid);
  sceKernelSleepThreadCB();

  return 0;
}

/* Sets up the callback thread and returns its thread id */
int SetupCallbacks(void) {
  int thid = 0;

  thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0);
  if(thid >= 0) {
    sceKernelStartThread(thid, 0, 0);
  }

  return thid;
}

void clearScreen(void) {
  pspDebugScreenInit();
  printf("    IrDAJoy Simple IRDA frame receiver\n");
  printf("    CROSS: insert newline, SQUARE: clear screen, CIRCLE: quit\n");
  printf("--------------------------------------------------------------------");
}

int main(void) {
  SceCtrlData pad;
  u32 buttonold = 0;
  SetupCallbacks();

  sceCtrlSetSamplingCycle(0);
  sceCtrlSetSamplingMode(PSP_CTRL_MODE_DIGITAL);

  clearScreen();
	
  //Load the IRDA driver: Do not know if still necessary

  u32 mod_id = sceKernelLoadModule("flash0:/kd/irda.prx", 0, NULL);
  sceKernelStartModule(mod_id, 0, NULL, NULL, NULL);
  int fd = sceIoOpen("irda0:", PSP_O_RDWR, 0);
  while (1) {
    // read pad
    sceCtrlReadBufferPositive(&pad, 1);
    if (pad.Buttons != buttonold) {
      if (pad.Buttons & PSP_CTRL_CROSS)
        printf("\n");
      if (pad.Buttons & PSP_CTRL_SQUARE)
        clearScreen();
      if (pad.Buttons & PSP_CTRL_CIRCLE)
        sceKernelExitGame();

      buttonold = pad.Buttons;
    }

    // check for IrDA input
    int status = frame_analyze (fd);
    if (status == 1) {
      printf("JA : %04X PA : %02X  ", paddleA, buttonA);
      printf("JB : %04X PB : %02X  ", paddleB, buttonB);
      printf("J : %02X\n", atariJ); // Use ATARI_*_MASK to get corresponding info
    }
  }

  return 0;
}

int frame_decode (UInt8 c) {
  switch (mode) {
  case MODE_WAIT_FRAME: // Waiting start of frame
	if (c == '#') {
	  mode=MODE_STORE;
	  cnt=0;
	}
	break;

  case MODE_STORE:
    frame_buf[cnt]=c;
    cnt++;
    if (cnt >= FRAME_SIZE - 1) { // End of frame. frame size without '#' (start tag)
      mode = MODE_WAIT_FRAME;
      UInt8 f0 = frame_buf[0];
      UInt8 f2 = frame_buf[2];
      UInt16 msbJA = (UInt16) (f0 & 0x03) << 8;
      UInt16 msbJB = (UInt16) (f2 & 0x03) << 8;
      paddleA = msbJA | frame_buf[1];
      paddleB = msbJB | frame_buf[3];
      if (f0 & 0x04) buttonA = 1;
      else buttonA = 0;
      if (f2 & 0x04) buttonB = 1;
      else buttonB = 0;

      atariJ = (f0 & 0x7C) >> 2; // bits => 000<UP><DW><LT><RT><BT>
      return 1; // Frame is good
    }
    break;

  default:
    mode = MODE_WAIT_FRAME;
    break;
  }

  return 0; // Frame not completed yet
}


// return true if frame is completed and decoded
int frame_analyze (int fd) {
  UInt8 data;
  int len = sceIoRead(fd, &data, 1);
  if (len == 1) {
    return frame_decode (data);
  }
  return 0; // Nothing new
}
