#include "stdafx.h"
#include "gtasaQueue.h"
#include "ogInput.h"
#include "ogEngine.h"

extern FILE * log;
CInputEvents * inputEvents = (CInputEvents *)0xB70198;
CPlayerControlData * playerControlData = (CPlayerControlData *)0xB73458;

// returns number of the pad button, pressed since last scan
int CInputEvents::getPadButtonJustPressed() {
	for (int i = 0; i < 16; i++) 
		if ((currPadState.buttons[i] & 0x80) && !(prevPadState.buttons[i] & 0x80))
			return i;
	return 0;
}

void CInputEvents::initEventNames() {
	strcpy_s (eventNames[ 0], 40, "PED_FIREWEAPON");				// 0
	strcpy_s (eventNames[ 1], 40, "PED_FIREWEAPON_ALT");			// 1
	strcpy_s (eventNames[ 2], 40, "PED_CYCLE_WEAPON_RIGHT");		// 2
    strcpy_s (eventNames[ 3], 40, "PED_CYCLE_WEAPON_LEFT");			// 3
	strcpy_s (eventNames[ 4], 40, "GO_FORWARD");					// 4
	strcpy_s (eventNames[ 5], 40, "GO_BACK");						// 5
	strcpy_s (eventNames[ 6], 40, "GO_LEFT");						// 6
    strcpy_s (eventNames[ 7], 40, "GO_RIGHT");						// 7
	strcpy_s (eventNames[ 8], 40, "PED_SNIPER_ZOOM_IN");			// 8
	strcpy_s (eventNames[ 9], 40, "PED_SNIPER_ZOOM_OUT");			// 9
	strcpy_s (eventNames[10], 40, "VEHICLE_ENTER_EXIT");			// 10
	strcpy_s (eventNames[11], 40, "CAMERA_CHANGE_VIEW_ALL_SITUATIONS");	// 11
	strcpy_s (eventNames[12], 40, "PED_JUMPING");					// 12
	strcpy_s (eventNames[13], 40, "PED_SPRINT");					// 13
	strcpy_s (eventNames[14], 40, "PED_LOOKBEHIND");				// 14
	strcpy_s (eventNames[15], 40, "PED_DUCK");						// 15
	strcpy_s (eventNames[16], 40, "PED_ANSWER_PHONE");				// 16
	strcpy_s (eventNames[17], 40, "SNEAK_ABOUT");					// 17
	strcpy_s (eventNames[18], 40, "VEHICLE_FIREWEAPON");			// 18
	strcpy_s (eventNames[19], 40, "VEHICLE_FIREWEAPON_ALT");		// 19
	strcpy_s (eventNames[20], 40, "VEHICLE_STEERLEFT");				// 20
	strcpy_s (eventNames[21], 40, "VEHICLE_STEERRIGHT");			// 21
	strcpy_s (eventNames[22], 40, "VEHICLE_STEERUP");				// 22
	strcpy_s (eventNames[23], 40, "VEHICLE_STEERDOWN");				// 23
	strcpy_s (eventNames[24], 40, "VEHICLE_ACCELERATE");			// 24
	strcpy_s (eventNames[25], 40, "VEHICLE_BRAKE");					// 25
	strcpy_s (eventNames[26], 40, "VEHICLE_RADIO_STATION_UP");		// 26
	strcpy_s (eventNames[27], 40, "VEHICLE_RADIO_STATION_DOWN");	// 27
	strcpy_s (eventNames[29], 40, "VEHICLE_HORN");					// 29 ^^
	strcpy_s (eventNames[30], 40, "TOGGLE_SUBMISSIONS");			// 30
	strcpy_s (eventNames[31], 40, "VEHICLE_HANDBRAKE");				// 31
	strcpy_s (eventNames[32], 40, "PED_1RST_PERSON_LOOK_LEFT");		// 32
	strcpy_s (eventNames[33], 40, "PED_1RST_PERSON_LOOK_RIGHT");	// 33
	strcpy_s (eventNames[34], 40, "VEHICLE_LOOKLEFT");				// 34
	strcpy_s (eventNames[35], 40, "VEHICLE_LOOKRIGHT");				// 35
	strcpy_s (eventNames[36], 40, "VEHICLE_LOOKBEHIND");			// 36
	strcpy_s (eventNames[37], 40, "VEHICLE_MOUSELOOK");				// 37
	strcpy_s (eventNames[38], 40, "VEHICLE_TURRETLEFT");			// 38
	strcpy_s (eventNames[39], 40, "VEHICLE_TURRETRIGHT");			// 39
	strcpy_s (eventNames[40], 40, "VEHICLE_TURRETUP");				// 40
	strcpy_s (eventNames[41], 40, "VEHICLE_TURRETDOWN");			// 41
	strcpy_s (eventNames[42], 40, "PED_CYCLE_TARGET_LEFT");			// 42
	strcpy_s (eventNames[43], 40, "PED_CYCLE_TARGET_RIGHT");		// 43
	strcpy_s (eventNames[44], 40, "PED_CENTER_CAMERA_BEHIND_PLAYER");	// 44
	strcpy_s (eventNames[45], 40, "PED_LOCK_TARGET");				// 45
	strcpy_s (eventNames[46], 40, "NETWORK_TALK");					// 46
	strcpy_s (eventNames[47], 40, "CONVERSATION_YES");				// 47
	strcpy_s (eventNames[48], 40, "CONVERSATION_NO");				// 48
	strcpy_s (eventNames[49], 40, "GROUP_CONTROL_FWD");				// 49
	strcpy_s (eventNames[50], 40, "GROUP_CONTROL_BWD");				// 50
	strcpy_s (eventNames[51], 40, "PED_1RST_PERSON_LOOK_UP");		// 51
	strcpy_s (eventNames[52], 40, "PED_1RST_PERSON_LOOK_DOWN");		// 52
	strcpy_s (eventNames[54], 40, "TOGGLE_DPAD");					// 54 ^^
	strcpy_s (eventNames[55], 40, "SWITCH_DEBUG_CAM_ON");			// 55
	strcpy_s (eventNames[56], 40, "TAKE_SCREEN_SHOT");				// 56
	strcpy_s (eventNames[57], 40, "SHOW_MOUSE_POINTER_TOGGLE");		// 57		
}

// TODO: maybe sortPriority or sortTypes ?
// TODO: optimize it!
void CInputEvents::initPriority(InputEvent event) {
	DWORD	index;

	for (DWORD i = 1; i < 5; i++ ) {			
        if (events[event][0].priority != i &&
            events[event][1].priority != i &&
            events[event][2].priority != i &&
            events[event][3].priority != i)
            continue;

		bool found = false;

        if (events[event][0].priority > i) {
            found = true;
            index = 0;
        }
        if (events[event][1].priority > i) 
            if (!found || events[event][1].priority < events[event][index].priority) {
                found = true;
                index = 1;
            }
        if (events[event][2].priority > i) 
            if (!found || events[event][2].priority < events[event][index].priority) {
                found = true;
                index = 2;
            }
        if (events[event][3].priority > i) 
            if (!found || events[event][3].priority < events[event][index].priority) {
                // found = true; // ????
                index = 3;
            }
            
        if (found) 
            events[event][index].priority = i;
	}	
}

void CInputEvents::initEvent(InputEvent event, DWORD type) {
	if (type > 3)
		return;
	events[event][type].keycode = type > 1 ? 0 : rsNULL;
	events[event][type].priority = 0;
	initPriority (event);
}

void CInputEvents::mapEvent (InputEvent event, RsKeyCode keycode, DWORD type) {
	initPriority (event);

	int index = 0;
	if (events[event][0].keycode != rsNULL) 
		index = 1;
	if (events[event][1].keycode != rsNULL)
		index++;
	if (events[event][2].keycode != 0)
		index++;
	if (events[event][3].keycode != 0)
		index++;

	events[event][type].keycode = keycode;
	events[event][type].priority = index+1;
}

void CInputEvents::init () {
	for (int i = 0; i < 4; i++) 
		for (int j = 0; j < EVENT_COUNT; j++) 
			initEvent ((InputEvent)j, i);
}


// copy controller configuration to the Settings::events
void CInputEvents::writeToSettings (DWORD * data) {
	for (int i = 0; i < 4; i++) 
		for (int j = 0; j < EVENT_COUNT; j++) {
			*data++ = j;
			*data++ = events[j][i].keycode;
			*data++ = events[j][i].priority;
		}
}
// copy controller configuration from the Settings::events
bool CInputEvents::readFromSettings(DWORD *data) {
	// check data integrity
	DWORD * tmp = data;
	for (int i = 0; i < 4; i++)
		for (int j = 0; j < EVENT_COUNT; j++) {
			if (*tmp != j)
				return false;
			tmp += 2;
		}

	init ();
	for (int i = 0; i < 4; i++)
		for (int j = 0; j < EVENT_COUNT; j++) {
			events[j][i].keycode = *(++data);
			events[j][i].priority = *(++data);
			data++;
		}
	return true;
}

bool CInputEvents::isKeyPressed (RsKeyCode key) {
	if (key <= 0xFF) 
		return currKeyState.stdKeys[key] != 0;
	if (key < rsESC || key > rsNULL)
		return false;
	return currKeyState.extKeys[key - rsESC] != 0;
}

bool CInputEvents::isKeyJustPressed(RsKeyCode key) {
	if (key <= 0xFF) 
		return currKeyState.stdKeys[key] && !prevKeyState.stdKeys[key];
	if (key < rsESC || key > rsNULL)
		return false;
	return currKeyState.extKeys[key - rsESC] && !prevKeyState.extKeys[key - rsESC];
}

//=============================================================================
void CPlayerControlData::init(BYTE a0, BYTE a4) {
	memset (&_f0, 0, sizeof (CPlayerControlData));	// TODO: неверно! нужно, в зависимости от флагов, часть полей не трогать

	// init keyboard states
	initKeyStates ();

	// init mouse states
	// TODO: move to CPlayerControlData::clearMouseState
	memset (activeMouseState, 0, 8);
	memset (currMouseState, 0, 8);
	memset (prevMouseState, 0, 8);
}
//=============================================================================
KeyState	activeKeyState;
KeyState	currKeyState;
KeyState	prevKeyState;

MouseState * activeMouseState = (MouseState *)0xB73404;
MouseState * currMouseState = (MouseState *)0xB73418;
MouseState * prevMouseState = (MouseState *)0xB7342C;

void processCheatcodes () {
	for (int i = 0; i <= 0xFF; i++) 
		if (currKeyState.stdKeys[i] && !prevKeyState.stdKeys[i])
			checkCheatcode (i);
}

bool isEnterJustPressed () {
	if(currKeyState.extKeys[rsENTER-rsESC] && !prevKeyState.extKeys[rsENTER-rsESC])
		return true;
	return (currKeyState.extKeys[rsEXTENTER-rsESC] && !prevKeyState.extKeys[rsEXTENTER-rsESC]);
}

bool isStdKeyJustPressed (int key) {
	return (currKeyState.stdKeys[key] && !prevKeyState.stdKeys[key]);
}

bool isNeedSkipCutscene () {
//	if (playerControlData[0]._f0.keys[16] && !playerControlData[0]._f30.[16])
//		return true;

	// TODO: check LMB 
	if (currMouseState->buttons[0] && !prevMouseState->buttons[0]) return true;	// TODO: mbLMB

	if (isEnterJustPressed ())
		return true;
	if (isStdKeyJustPressed (' '))
		return true;
	if (*(DWORD *)0xC920EC == 0)	// TODO: (isForeground == 0)
		return true;
	return false;	
}

// clear all key states
void initKeyStates () {
	memset (&activeKeyState, 0, sizeof (KeyState));
	memset (&currKeyState, 0, sizeof (KeyState));
	memset (&prevKeyState, 0, sizeof (KeyState));
}

// returns code of the key, pressed since last scan
RsKeyCode * getKeyJustPressed (RsKeyCode * key) { // >> 541490
	for (int i = 0; i <= 0xFF; i++) 
		if (currKeyState.stdKeys[i] && !prevKeyState.stdKeys[i]) {
			*key = (RsKeyCode)i;
			return key;
		}
	for (int i = 0; i < rsNULL-rsESC; i++)
		if (currKeyState.extKeys[i] && !prevKeyState.extKeys[i]) {
			*key = (RsKeyCode)(rsESC+i);
			return key;
		}
	*key = rsNULL;
	return key;
}

bool isEscJustPressed () {
	return (currKeyState.extKeys[rsESC-rsESC] && !prevKeyState.extKeys[rsESC-rsESC]);
}
/* TODO: (CKeyState)
CPlayerControlData::init
processInput

*/

// main input processing function
void processInput () {	// >> 541DD0 (used in processGame)
	// playerControlData[0].processMouse ();
	// processPadInput (0);
	memset (inputEvents->_f12D0, 0, 16);
	// inputEvents->processKeyboard ();
	// inputEvents->processMouse ();
	// playerControlData[0].processKeyboard (0);
	// playerControlData[1].processKeyboard (1);	
	prevKeyState = currKeyState;
	currKeyState = activeKeyState;
}

// ** Event handlers **
static RsEventStatus handleKeyDown (RsKeyCode * keyCode) {	// >> 743DF0
	if (*keyCode == rsNULL)
		return rsEVENTPROCESSED;
	if (*keyCode >= rsESC && *keyCode < rsNULL) {
		activeKeyState.extKeys[*keyCode-rsESC] = 0xFF;
		return rsEVENTPROCESSED;
	}
	if (*keyCode > 0xFF)		
		return rsEVENTPROCESSED;
	
	activeKeyState.stdKeys[*keyCode] = 0xFF;
	WORD * keys = playerControlData[0]._f0.keys;
	// TODO: а это нафига? Как быть с переопределением клавиш?
	switch ((*keyCode)) {
	case 'D':
		keys[0] = 0x80;
		break;
	case 'A':
		keys[0] = 0xFF80;
		break;
	case 'W':
		keys[1] = 0x80;
		break;
	case 'S':
		keys[1] = 0xFF80;
		break;
	case 'J':
		keys[2] = 0x80;
		break;
	case 'G':
		keys[2] = 0xFF80;
		break;
	case 'Y':
		keys[3] = 0x80;
		break;
	case 'H':
		keys[3] = 0xFF80;
		break;
	case 'Z':
		keys[4] = 0xFF;
		break;
	case 'X':
		keys[5] = 0xFF;
		break;
	case 'C':
		keys[6] = 0xFF;
		break;
	case 'V':
		keys[7] = 0xFF;
		break;
	case 'O':
		keys[8] = 0xFF;
		break;
	case 'L':
		keys[9] = 0xFF;
		break;
	case 'K':
		keys[10] = 0xFF;
		break;
	case ';':
		keys[11] = 0xFF;
		break;
	case 'B':
		keys[12] = 0xFF;
		break;
	case 'N':
		keys[13] = 0xFF;
		break;
	case 'M':
		keys[14] = 0xFF;
		break;
	case ',':
		keys[15] = 0xFF;
		break;
	case '.':
		keys[16] = 0xFF;
		break;
	case '/':
		keys[17] = 0xFF;
		break;
	case rsRSHIFT:
		keys[18] = 0xFF;
		break;
	case rsRCTRL:
		keys[19] = 0xFF;
		break;
	
	}
	return rsEVENTPROCESSED;
}

static RsEventStatus handleKeyUp (RsKeyCode * keyCode) {
	if (*keyCode == rsNULL)
		return rsEVENTPROCESSED;
	if (*keyCode >= rsESC && *keyCode < rsNULL) {
		activeKeyState.extKeys[*keyCode-rsESC] = 0;
		return rsEVENTPROCESSED;
	}
	if (*keyCode > 0xFF)		
		return rsEVENTPROCESSED;
	
	activeKeyState.stdKeys[*keyCode] = 0;
	WORD * keys = playerControlData[0]._f0.keys;

	switch ((*keyCode)) {
	case 'D':
	case 'A':
		keys[0] = 0;
		break;
	case 'W':
	case 'S':
		keys[1] = 0;
		break;
	case 'J':
	case 'G':
		keys[2] = 0;
		break;
	case 'Y':
	case 'H':
		keys[3] = 0;
		break;
	case 'Z':
		keys[4] = 0;
		break;
	case 'X':
		keys[5] = 0;
		break;
	case 'C':
		keys[6] = 0;
		break;
	case 'V':
		keys[7] = 0;
		break;
	case 'O':
		keys[8] = 0;
		break;
	case 'L':
		keys[9] = 0;
		break;
	case 'K':
		keys[10] = 0;
		break;
	case ';':
		keys[11] = 0;
		break;
	case 'B':
		keys[12] = 0;
		break;
	case 'N':
		keys[13] = 0;
		break;
	case 'M':
		keys[14] = 0;
		break;
	case ',':
		keys[15] = 0;
		break;
	case '.':
		keys[16] = 0;
		break;
	case '/':
		keys[17] = 0;
		break;
	case rsRSHIFT:
		keys[18] = 0;
		break;
	case rsRCTRL:
		keys[19] = 0;
		break;
	
	}
	return rsEVENTPROCESSED;

}

static RsEventStatus keyboardHandler (RsEvent event, void * param) {	// NOTE: used in attachInputDevices
	if (event == rsKEYDOWN)
		return handleKeyDown ((RsKeyCode *)param);
	if (event == rsKEYUP)
		return handleKeyUp ((RsKeyCode *)param);
	return rsEVENTNOTPROCESSED;
}
//=============================================================================
extern void injectFunction (DWORD address, DWORD function);

void __stdcall input__initEvent (InputEvent event, DWORD type) { inputEvents->initEvent (event, type); }
void __stdcall input__mapEvent (InputEvent event, RsKeyCode keycode, DWORD type) { inputEvents->mapEvent(event, keycode, type); }
int __stdcall input__getPadButtonJustPressed () { return inputEvents->getPadButtonJustPressed (); }
void __stdcall input__initEventNames () { inputEvents->initEventNames (); }

// заглушка-времянка
void __stdcall input__initPlayerData (BYTE a0, BYTE a4) { playerControlData[0].init (a0, a4); playerControlData[1].init (a0, a4); }

void eventsInject () {	
	injectFunction (0x52D1E0, (DWORD)input__getPadButtonJustPressed);
	injectFunction (0x52D260, (DWORD)input__initEventNames);
	injectFunction (0x52FD70, (DWORD)input__initEvent);
	injectFunction (0x530490, (DWORD)input__mapEvent);	

	// injectFunction (0x439AF0, (DWORD)processCheatcodes);
	// injectFunction (0x4D5980, (DWORD)isEnterJustPressed);
	// injectFunction (0x4D59B0, (DWORD)isStdKeyJustPressed);
	// injectFunction (0x4D5D10, (DWORD)isNeedSkipCutscene);
	// injectFunction (0x572DB0, (DWORD)isEscJustPressed);
	// injectFunction (0x541A70, (DWORD)input__initPlayerData);


	// injectFunction (0x744880, (DWORD)keyboardHandler);	// TODO: remove after implementation of attachInputDevices
}