/*
 * Copyright (c) 2009, Wei Mingzhi <whistler_wmz@users.sf.net>.
 * All Rights Reserved.
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses>.
 */

#include "pad.h"
#if !SDL_VERSION_ATLEAST(2,0,0) && defined(__linux__)
#include <linux/input.h>
#include <sys/file.h>
#include <time.h>
#endif

#if SDL_VERSION_ATLEAST(2,0,0)
int has_haptic;
#endif

static void (*gpuVisualVibration)(uint32_t, uint32_t) = NULL;

char *PSEgetLibName(void) {
	return _("Gamepad/Keyboard/Mouse Input");
}

uint32_t PSEgetLibType(void) {
	return PSE_LT_PAD;
}

uint32_t PSEgetLibVersion(void) {
	return (1 << 16) | (2 << 8);
}

static int padDataLenght[] = {0, 2, 3, 1, 1, 3, 3, 3};
void PADsetMode(const int pad, const int mode) {
	g.PadState[pad].PadMode = mode;
    
    if (g.cfg.PadDef[pad].Type == PSE_PAD_TYPE_ANALOGPAD) {
        g.PadState[pad].PadID = mode ? 0x73 : 0x41;
    }
    else {
        g.PadState[pad].PadID = (g.cfg.PadDef[pad].Type << 4) |
                                 padDataLenght[g.cfg.PadDef[pad].Type];
    }
    
	g.PadState[pad].Vib0 = 0;
	g.PadState[pad].Vib1 = 0;
	g.PadState[pad].VibF[0] = 0;
	g.PadState[pad].VibF[1] = 0;
}

long PADinit(long flags) {
	LoadPADConfig();

	PADsetMode(0, 0);
	PADsetMode(1, 0);

	gpuVisualVibration = NULL;

	return PSE_PAD_ERR_SUCCESS;
}

long PADshutdown(void) {
	PADclose();
	return PSE_PAD_ERR_SUCCESS;
}

static pthread_t			ThreadID;
static volatile uint8_t		TerminateThread = 0;

static void *JoyThread(void *param) {
	while (!TerminateThread) {
		CheckJoy();
		usleep(1000);
	}
	pthread_exit(0);
	return NULL;
}

long PADopen(unsigned long *Disp) {
	g.Disp = (Display *)*Disp;

	if (!g.Opened) {
		if (SDL_WasInit(SDL_INIT_EVERYTHING)) {
			if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) == -1) {
				return PSE_PAD_ERR_FAILURE;
			}
		} else if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE) == -1) {
			return PSE_PAD_ERR_FAILURE;
		}
 
#if SDL_VERSION_ATLEAST(2,0,0)
    has_haptic = 0;
    if (SDL_InitSubSystem(SDL_INIT_HAPTIC) == 0)
      has_haptic = 1;
#endif

		InitSDLJoy();
		InitKeyboard();

		g.KeyLeftOver = 0;

		if (g.cfg.Threaded) {
			TerminateThread = 0;

			if (pthread_create(&ThreadID, NULL, JoyThread, NULL) != 0) {
				// thread creation failed, fallback to polling
				g.cfg.Threaded = 0;
			}
		}
	}

	g.Opened = 1;

	return PSE_PAD_ERR_SUCCESS;
}

long PADclose(void) {
	if (g.Opened) {
		if (g.cfg.Threaded) {
			TerminateThread = 1;
			pthread_join(ThreadID, NULL);
		}

		DestroySDLJoy();
		DestroyKeyboard();
#if SDL_VERSION_ATLEAST(2,0,0)
		if (SDL_WasInit(SDL_INIT_EVERYTHING & ~(SDL_INIT_HAPTIC | SDL_INIT_JOYSTICK))) {
			SDL_QuitSubSystem(SDL_INIT_HAPTIC);
			SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
		} else
#endif
		if (SDL_WasInit(SDL_INIT_EVERYTHING & ~SDL_INIT_JOYSTICK)) {
			SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
		} else {
			SDL_Quit();
		}
	}
	g.Opened = 0;

	return PSE_PAD_ERR_SUCCESS;
}

long PADquery(void) {
	return PSE_PAD_USE_PORT1 | PSE_PAD_USE_PORT2;
}

static void UpdateInput(void) {
	int pad;
	if (!g.cfg.Threaded) CheckJoy();
	for(pad = 0; pad < 2; pad++) {
		if(g.PadState[pad].PadModeSwitch) {
			g.PadState[pad].PadModeSwitch = 0;
			PADsetMode(pad, 1 - g.PadState[pad].PadMode);
		}
	}
	CheckKeyboard();
}

static uint8_t stdpar[2][8] = {
	{0xFF, 0x5A, 0xFF, 0xFF, 0x80, 0x80, 0x80, 0x80},
	{0xFF, 0x5A, 0xFF, 0xFF, 0x80, 0x80, 0x80, 0x80}
};

static uint8_t unk46[2][8] = {
	{0xFF, 0x5A, 0x00, 0x00, 0x01, 0x02, 0x00, 0x0A},
	{0xFF, 0x5A, 0x00, 0x00, 0x01, 0x02, 0x00, 0x0A}
};

static uint8_t unk47[2][8] = {
	{0xFF, 0x5A, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00},
	{0xFF, 0x5A, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00}
};

static uint8_t unk4c[2][8] = {
	{0xFF, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
	{0xFF, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
};

static uint8_t unk4d[2][8] = { 
	{0xFF, 0x5A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
	{0xFF, 0x5A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
};

static uint8_t stdcfg[2][8]   = { 
	{0xFF, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
	{0xFF, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
};

static uint8_t stdmode[2][8]  = { 
	{0xFF, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
	{0xFF, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
};

static uint8_t stdmodel[2][8] = { 
	{0xFF,
	 0x5A,
	 0x01, // 03 - dualshock2, 01 - dualshock
	 0x02, // number of modes
	 0x01, // current mode: 01 - analog, 00 - digital
	 0x02,
	 0x01,
	 0x00},
	{0xFF, 
	 0x5A,
	 0x01, // 03 - dualshock2, 01 - dualshock
	 0x02, // number of modes
	 0x01, // current mode: 01 - analog, 00 - digital
	 0x02,
	 0x01,
	 0x00}
};
 
#if !SDL_VERSION_ATLEAST(2,0,0) && defined(__linux__)
/* lifted from SDL; but it's GPL as well */
/* added ffbit, though */
#define test_bit(nr, addr) \
	(((1UL << ((nr) % (sizeof(long) * 8))) & ((addr)[(nr) / (sizeof(long) * 8)])) != 0)
#define NBITS(x) ((((x)-1)/(sizeof(long) * 8))+1)
static int EV_IsJoystick(int fd)
{
	unsigned long evbit[NBITS(EV_MAX)] = { 0 };
	unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
	unsigned long absbit[NBITS(ABS_MAX)] = { 0 };
	unsigned long ffbit[NBITS(FF_MAX)] = { 0 };

	if ( (ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit) < 0) ||
	     (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) < 0) ||
	     (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) < 0) ) {
		return(0);
	}
	if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) &&
	      test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit) &&
	     (test_bit(BTN_TRIGGER, keybit) || test_bit(BTN_A, keybit) || test_bit(BTN_1, keybit)))) return 0;
	if ( (ioctl(fd, EVIOCGBIT(EV_FF, sizeof(ffbit)), ffbit) < 0) ||
	     !test_bit(FF_RUMBLE, ffbit) )
		return(1);
	return(2);
}

static void linux_set_vibrate(int pad)
{
	int jno = 0, devno, dev;
	const char *sdlj;
	int tjno = g.cfg.PadDef[pad].DevNum;

	g.PadState[pad].VibrateDev = -2;
	/* simulate SDL device opening; probably not very accurate */
	/* works for me, though */
	sdlj = getenv("SDL_JOYSTICK_DEVICE");
	if(sdlj) {
		dev = open(sdlj, O_RDONLY);
		if(dev >= 0) {
			if(!tjno) {
				close(dev);
				dev = open(sdlj, O_RDWR);
				if(dev < 0) {
					printf("%s has no permission to rumble\n", sdlj);
					return;
				}
				if(EV_IsJoystick(dev) != 2) {
					printf("%s has no rumble\n", sdlj);
					close(dev);
					return;
				}
				g.PadState[pad].VibrateDev = dev;
				return;
			}
			close(dev);
			jno++;
		} else
			perror(sdlj);
	}
	for(devno = 0; devno < 32; devno++) {
		char buf[20];

		sprintf(buf, "/dev/input/event%d", devno);
		dev = open(buf, O_RDONLY);
		if(dev >= 0) {
			int isj = EV_IsJoystick(dev);
			if(isj) {
				if(tjno == jno) {
					close(dev);
					if(isj != 2) {
						printf("%s has no rumble\n", buf);
						return;
					}
					dev = open(buf, O_RDWR);
					if(dev < 0) {
						printf("%s has no permission to rumble\n", buf);
						return;
					}
					g.PadState[pad].VibrateDev = dev;
					return;
				}
				jno++;
			}
			close(dev);
		}
	}
}

static int linux_vibrate(PADSTATE *pad)
{
	struct ff_effect ffe = { 0 };
	struct input_event ev = { { 0 } };
	struct timespec t;
	uint32_t stime;

	if(pad->VibrateDev < 0)
		return 0;
	ev.type = EV_FF;
	if(!pad->VibF[0] && !pad->VibF[1] && pad->VibrateEffect < 0) {
		return 1;
	}
	clock_gettime(CLOCK_REALTIME, &t);
	stime = (uint32_t)(t.tv_sec * 1000 + t.tv_nsec / 1000000);
	if(pad->VibrateEffect >= 0 &&
	   pad->VibrLow == pad->VibF[0] && pad->VibrHigh == pad->VibF[1]) {
		if(stime - pad->VibrSetTime < 300)
			return 1;
	}
	if(pad->VibrateEffect < 0 || pad->VibrLow != pad->VibF[0] ||
	   pad->VibrHigh != pad->VibF[1]) {
		if(pad->VibrateEffect >= 0) {
			ev.code = pad->VibrateEffect;
			ev.value = 0;
			if(write(pad->VibrateDev, &ev, sizeof(ev)) < 0)
				perror("ev write");
		}
		ffe.type = FF_RUMBLE;
		ffe.id = pad->VibrateEffect;
		/* DG says refresh 1/vsync = 166, but add for delays/etc. */
		ffe.replay.length = 500;
		ffe.replay.delay = 0;
		ffe.u.rumble.strong_magnitude = pad->VibF[1] * 256;
		ffe.u.rumble.weak_magnitude = pad->VibF[0] * 256;
		pad->VibrLow = pad->VibF[0];
		pad->VibrHigh = pad->VibF[1];
		if(ioctl(pad->VibrateDev, EVIOCSFF,&ffe) < 0) {
			perror("SFF ioctl");
			close(pad->VibrateDev);
			pad->VibrateDev = -2;
			return 0;
		}
	}
	pad->VibrSetTime = stime;
	ev.code = pad->VibrateEffect = ffe.id;
	ev.value = 1;
	if(write(pad->VibrateDev, &ev, sizeof(ev)) != sizeof(ev)) {
		close(pad->VibrateDev);
		pad->VibrateDev = -2;
		perror("ev write");
		return 0;
	}
	return 1;
}
#endif

static uint8_t CurPad = 0, CurByte = 0, CurCmd = 0, CmdLen = 0;

unsigned char PADstartPoll(int pad) {
	CurPad = pad - 1;
	CurByte = 0;

	return 0xFF;
}

unsigned char PADpoll(unsigned char value) {
	static uint8_t		*buf = NULL;
	uint16_t			n;

	if (CurByte == 0) {
		CurByte++;

		// Don't enable Analog/Vibration for a standard pad
		if (g.cfg.PadDef[CurPad].Type != PSE_PAD_TYPE_ANALOGPAD) {
			CurCmd = CMD_READ_DATA_AND_VIBRATE;
		} else {
			CurCmd = value;
		}

		switch (CurCmd) {
			case CMD_CONFIG_MODE:
				CmdLen = 8;
				buf = stdcfg[CurPad];
				if (stdcfg[CurPad][3] == 0xFF) return 0xF3;
				else return g.PadState[CurPad].PadID;

			case CMD_SET_MODE_AND_LOCK:
				CmdLen = 8;
				buf = stdmode[CurPad];
				return 0xF3;

			case CMD_QUERY_MODEL_AND_MODE:
				CmdLen = 8;
				buf = stdmodel[CurPad];
				buf[4] = g.PadState[CurPad].PadMode;
				return 0xF3;

			case CMD_QUERY_ACT:
				CmdLen = 8;
				buf = unk46[CurPad];
				return 0xF3;

			case CMD_QUERY_COMB:
				CmdLen = 8;
				buf = unk47[CurPad];
				return 0xF3;

			case CMD_QUERY_MODE:
				CmdLen = 8;
				buf = unk4c[CurPad];
				return 0xF3;

			case CMD_VIBRATION_TOGGLE:
				CmdLen = 8;
				buf = unk4d[CurPad];
				return 0xF3;

			case CMD_READ_DATA_AND_VIBRATE:
			default:
				n = g.PadState[CurPad].KeyStatus;
				n &= g.PadState[CurPad].JoyKeyStatus;

				stdpar[CurPad][2] = n & 0xFF;
				stdpar[CurPad][3] = n >> 8;

				if (g.PadState[CurPad].PadMode == 1) {
					CmdLen = 8;

					stdpar[CurPad][4] = g.PadState[CurPad].AnalogStatus[ANALOG_RIGHT][0];
					stdpar[CurPad][5] = g.PadState[CurPad].AnalogStatus[ANALOG_RIGHT][1];
					stdpar[CurPad][6] = g.PadState[CurPad].AnalogStatus[ANALOG_LEFT][0];
					stdpar[CurPad][7] = g.PadState[CurPad].AnalogStatus[ANALOG_LEFT][1];
				}
				else if(g.PadState[CurPad].PadID == 0x12)
                {
                    CmdLen = 6;
                    
                    stdpar[CurPad][4] = g.PadState[0].MouseAxis[0][0];
                    stdpar[CurPad][5] = g.PadState[0].MouseAxis[0][1];
                }
                else {
					CmdLen = 4;
				}

				buf = stdpar[CurPad];
				return g.PadState[CurPad].PadID;
		}
	}

	//makes it so that the following switch doesn't try to dereference a null pointer
	//quiets a warning in the Clang static analyzer.
	if (buf == NULL) {
		return 0;
	}
	
	switch (CurCmd) {
		case CMD_READ_DATA_AND_VIBRATE:
			if (g.cfg.PadDef[CurPad].Type == PSE_PAD_TYPE_ANALOGPAD) {
				if (CurByte == g.PadState[CurPad].Vib0) {
					g.PadState[CurPad].VibF[0] = value;

					if (g.PadState[CurPad].VibF[0] != 0 || g.PadState[CurPad].VibF[1] != 0) {
#if !SDL_VERSION_ATLEAST(2,0,0) && defined(__linux__)
						if (g.PadState[CurPad].VibrateDev == -1 &&
							g.PadState[CurPad].JoyDev != NULL) {
							linux_set_vibrate(CurPad);
						}
						if (!linux_vibrate(&g.PadState[CurPad]))
						/* only do visual if joy fails */
#endif
							if (!JoyHapticRumble(CurPad, g.PadState[CurPad].VibF[0], g.PadState[CurPad].VibF[1])) {
								//gpuVisualVibration(g.PadState[CurPad].VibF[0], g.PadState[CurPad].VibF[1]);
							}
							
						if(gpuVisualVibration != NULL &&
						   g.cfg.PadDef[CurPad].VisualVibration) {
							gpuVisualVibration(g.PadState[CurPad].VibF[0], g.PadState[CurPad].VibF[1]);
						}
					}
				}

				if (CurByte == g.PadState[CurPad].Vib1) {
					g.PadState[CurPad].VibF[1] = value;

					if (g.PadState[CurPad].VibF[0] != 0 || g.PadState[CurPad].VibF[1] != 0) {
#if !SDL_VERSION_ATLEAST(2,0,0) && defined(__linux__)
						if (g.PadState[CurPad].VibrateDev == -1 &&
							g.PadState[CurPad].JoyDev != NULL) {
							linux_set_vibrate(CurPad);
						}
						if (!linux_vibrate(&g.PadState[CurPad]))
							/* only do visual if joy fails */
#endif
							if (!JoyHapticRumble(CurPad, g.PadState[CurPad].VibF[0], g.PadState[CurPad].VibF[1])) {
								//gpuVisualVibration(g.PadState[CurPad].VibF[0], g.PadState[CurPad].VibF[1]);
							}
						
						if(gpuVisualVibration != NULL &&
						   g.cfg.PadDef[CurPad].VisualVibration) {
							gpuVisualVibration(g.PadState[CurPad].VibF[0], g.PadState[CurPad].VibF[1]);
						}
					}
				}
			}
			break;

		case CMD_CONFIG_MODE:
			if (CurByte == 2) {
				switch (value) {
					case 0:
						buf[2] = 0;
						buf[3] = 0;
						break;

					case 1:
						buf[2] = 0xFF;
						buf[3] = 0xFF;
						break;
				}
			}
			break;

		case CMD_SET_MODE_AND_LOCK:
			if (CurByte == 2) {
				PADsetMode(CurPad, value);
			}
			break;

		case CMD_QUERY_ACT:
			if (CurByte == 2) {
				switch (value) {
					case 0: // default
						buf[5] = 0x02;
						buf[6] = 0x00;
						buf[7] = 0x0A;
						break;

					case 1: // Param std conf change
						buf[5] = 0x01;
						buf[6] = 0x01;
						buf[7] = 0x14;
						break;
				}
			}
			break;

		case CMD_QUERY_MODE:
			if (CurByte == 2) {
				switch (value) {
					case 0: // mode 0 - digital mode
						buf[5] = PSE_PAD_TYPE_STANDARD;
						break;

					case 1: // mode 1 - analog mode
						buf[5] = PSE_PAD_TYPE_ANALOGPAD;
						break;
				}
			}
			break;

		case CMD_VIBRATION_TOGGLE:
			if (CurByte >= 2 && CurByte < CmdLen) {
				if (CurByte == g.PadState[CurPad].Vib0) {
					buf[CurByte] = 0;
				}
				if (CurByte == g.PadState[CurPad].Vib1) {
					buf[CurByte] = 1;
				}

				if (value == 0) {
					g.PadState[CurPad].Vib0 = CurByte;
					if ((g.PadState[CurPad].PadID & 0x0f) < (CurByte - 1) / 2) {
						g.PadState[CurPad].PadID = (g.PadState[CurPad].PadID & 0xf0) + (CurByte - 1) / 2;
					}
				} else if (value == 1) {
					g.PadState[CurPad].Vib1 = CurByte;
					if ((g.PadState[CurPad].PadID & 0x0f) < (CurByte - 1) / 2) {
						g.PadState[CurPad].PadID = (g.PadState[CurPad].PadID & 0xf0) + (CurByte - 1) / 2;
					}
				}
			}
			break;
	}

	if (CurByte >= CmdLen) return 0;
	if (buf == NULL) return 0;
	return buf[CurByte++];
}

static long PADreadPort(int num, PadDataS *pad) {
	UpdateInput();

	pad->buttonStatus = (g.PadState[num].KeyStatus & g.PadState[num].JoyKeyStatus);

	// ePSXe different from pcsxr, swap bytes
	pad->buttonStatus = (pad->buttonStatus >> 8) | (pad->buttonStatus << 8);

	switch (g.cfg.PadDef[num].Type) {
		case PSE_PAD_TYPE_ANALOGPAD: // Analog Controller SCPH-1150
			pad->controllerType = PSE_PAD_TYPE_ANALOGPAD;
			pad->rightJoyX = g.PadState[num].AnalogStatus[ANALOG_RIGHT][0];
			pad->rightJoyY = g.PadState[num].AnalogStatus[ANALOG_RIGHT][1];
			pad->leftJoyX = g.PadState[num].AnalogStatus[ANALOG_LEFT][0];
			pad->leftJoyY = g.PadState[num].AnalogStatus[ANALOG_LEFT][1];
			break;

		case PSE_PAD_TYPE_STANDARD: // Standard Pad SCPH-1080, SCPH-1150
		default:
			pad->controllerType = PSE_PAD_TYPE_STANDARD;
			break;
	}

	return PSE_PAD_ERR_SUCCESS;
}

long PADreadPort1(PadDataS *pad) {
	return PADreadPort(0, pad);
}

long PADreadPort2(PadDataS *pad) {
	return PADreadPort(1, pad);
}

long PADkeypressed(void) {
	long s;
    
    static int frame = 0;
    if( !frame )
        UpdateInput();
    frame ^= 1;

	s = g.KeyLeftOver;
	g.KeyLeftOver = 0;

	return s;
}

void PADregisterVibration(void (*callback)(uint32_t, uint32_t)) {
	gpuVisualVibration = callback;
}

#ifndef _MACOSX

long PADconfigure(void) {
	int pid = fork();

	if (pid == 0) {
		if (fork() == 0) {
			execl("cfg/cfgDFInput", "cfgDFInput", "configure", NULL);
		}
		exit(0);
	} else if (pid > 0) {
		waitpid(pid, NULL, 0);
	}

	return PSE_PAD_ERR_SUCCESS;
}

void PADabout(void) {
	int pid = fork();

	if (pid == 0) {
		if (fork() == 0) {
			execl("cfg/cfgDFInput", "cfgDFInput", "about", NULL);
		}
		exit(0);
	} else if (pid > 0) {
		waitpid(pid, NULL, 0);
	}
}

#endif

long PADtest(void) {
	return PSE_PAD_ERR_SUCCESS;
}
