#include "IZ_joystick.h"

static IZ_ConfigItem joystick_config_items[IZ_PLAYERS * (IZ_CONTROLS - 4 + 2) + 1];

bool IZ_JoystickIsValidAxisThreshold(u16 value) {
	return (4000 <= value && value <= 12000);
}

void IZ_JoystickHandleDeviceEvents(IZ_JoystickState* state, SDL_Event e) {
	if (e.type == SDL_JOYDEVICEADDED) {
		u8 joysticks_count = SDL_NumJoysticks();
		if (joysticks_count <= IZ_PLAYERS && !state->device) {
			state->device = SDL_JoystickOpen(e.jdevice.which);
		}
		return;
	}

	if (e.type == SDL_JOYDEVICEREMOVED) {
		if (
			state->device
			&& SDL_JoystickInstanceID(state->device) == e.jdevice.which
		) {
			state->device = NULL;
		}
	}
}

void IZ_JoystickHandleAxisEvents(IZ_JoystickState* state, IZ_Action* action, SDL_Event e) {
	if (e.type == SDL_JOYAXISMOTION) {
		// XInput handling
		u8 control_index;
		for (control_index = 4; control_index < IZ_CONTROLS; control_index += 1) {
			if (e.jaxis.axis == IZ_JOY_AXIS_DIRECTION_LEFT_SHOULDER && state->config.control_mapping[control_index] == 10) {
				const u16 bitflag = (0x1 << control_index);

				if (e.jaxis.value > state->config.axis_threshold) {
					*action |= bitflag;
					return;
				}

				if (e.jaxis.value <= state->config.axis_threshold) {
					*action &= ~bitflag;
					return;
				}
			}

			if (e.jaxis.axis == IZ_JOY_AXIS_DIRECTION_RIGHT_SHOULDER && state->config.control_mapping[control_index] == 11) {
				const u16 bitflag = (0x1 << control_index);

				if (e.jaxis.value > state->config.axis_threshold) {
					*action |= bitflag;
					return;
				}

				if (e.jaxis.value <= state->config.axis_threshold) {
					*action &= ~bitflag;
					return;
				}
			}
		}

		if (
			e.jaxis.axis == IZ_JOY_AXIS_DIRECTION_HORIZONTAL1
			|| e.jaxis.axis == IZ_JOY_AXIS_DIRECTION_HORIZONTAL2
		) {
			*action &= ~(0x1 << IZ_ACTION_INDEX_RIGHT);
			*action &= ~(0x1 << IZ_ACTION_INDEX_LEFT);
			if (e.jaxis.value > state->config.axis_threshold) {
				*action |= (0x1 << IZ_ACTION_INDEX_RIGHT);
				return;
			}
			if (e.jaxis.value <= -state->config.axis_threshold) {
				*action |= (0x1 << IZ_ACTION_INDEX_LEFT);
			}
			return;
		}
		if (
			e.jaxis.axis == IZ_JOY_AXIS_DIRECTION_VERTICAL1
			|| e.jaxis.axis == IZ_JOY_AXIS_DIRECTION_VERTICAL2
		) {
			*action &= ~(0x1 << IZ_ACTION_INDEX_UP);
			*action &= ~(0x1 << IZ_ACTION_INDEX_DOWN);
			if (e.jaxis.value > state->config.axis_threshold) {
				*action |= (0x1 << IZ_ACTION_INDEX_DOWN);
				return;
			}
			if (e.jaxis.value <= -state->config.axis_threshold) {
				*action |= (0x1 << IZ_ACTION_INDEX_UP);
			}
		}
	}
}

void IZ_JoystickHandleHatEvents(IZ_Action* action, SDL_Event e) {
	if (e.type == SDL_JOYHATMOTION) {
		*action &= ~(0x1 << IZ_ACTION_INDEX_UP);
		*action &= ~(0x1 << IZ_ACTION_INDEX_RIGHT);
		*action &= ~(0x1 << IZ_ACTION_INDEX_DOWN);
		*action &= ~(0x1 << IZ_ACTION_INDEX_LEFT);
		if (e.jhat.value != 0) {
			*action |= e.jhat.value;
		}
	}
}

void IZ_JoystickHandleButtonEvents(IZ_JoystickState* state, IZ_Action* action, SDL_Event e) {
	u8 control_index;

	SDL_JoystickGUID joystick_guid = SDL_JoystickGetGUID(SDL_JoystickFromInstanceID(e.jbutton.which));
	bool is_valid_event = false;
	for (u8 zz = 0; zz < 16; zz += 1) {
		is_valid_event |= joystick_guid.data[zz] != 0;
	}
	if (!is_valid_event) {
		return;
	}

//	printf("button event from guid: ");
//	for (u8 zz = 0; zz < 16; zz += 1) {
//		printf("%02x", joystick_guid.data[zz]);
//	}
//	printf("\n");

	for (control_index = 4; control_index < IZ_CONTROLS; control_index += 1) {
		u8 normalized_button = e.jbutton.button;

		/*
		 *      ZL               ZR
		 *       L               R
		 *
		 *      U    SLCT STRT    3
		 *    L   R             2   0
		 *      D                 1
		 *
		 *
		 *
		 */

		// TODO test with XInput, make compatible with Apple

#ifdef IZ_MACOS
		//printf("%d\n", e.jbutton.button);
		if (e.jbutton.button == 2) {
			normalized_button = 4;
		} else if (e.jbutton.button == 6) {
			normalized_button = 11;
		} else if (e.jbutton.button == 4) {
			normalized_button = 10;
		}
#endif

		if (normalized_button == state->config.control_mapping[control_index]) {
			const u16 bitflag = (0x1 << control_index);

			if (e.type == SDL_JOYBUTTONDOWN) {
				*action |= bitflag;
				return;
			}

			if (e.type == SDL_JOYBUTTONUP) {
				*action &= ~bitflag;
				return;
			}
		}
	}
}

void IZ_JoystickHandleEvents(IZ_JoystickState(* state)[IZ_PLAYERS], IZ_Action(* action)[IZ_PLAYERS], SDL_Event e) {
	u8 player_index;
	for (player_index = 0; player_index < IZ_PLAYERS; player_index += 1) {
		IZ_JoystickHandleDeviceEvents(&(*state)[player_index], e);
		IZ_JoystickHandleAxisEvents(&(*state)[player_index], &(*action)[player_index], e);
		IZ_JoystickHandleHatEvents(&(*action)[player_index], e);
		IZ_JoystickHandleButtonEvents(&(*state)[player_index], &(*action)[player_index], e);
	}
}

void IZ_JoystickBindStateToConfig(IZ_JoystickState(* state)[IZ_PLAYERS], IZ_ConfigItem config_items[]) {
	u8 player_index;
	u8 control_index;
	for (player_index = 0; player_index < IZ_PLAYERS; player_index += 1) {
		u8 base_index = (player_index * (IZ_CONTROLS - 4 + 2));
		config_items[base_index].dest = &((*state)[player_index].config.device_id);
		config_items[base_index + 1].dest = &((*state)[player_index].config.axis_threshold);
		config_items[base_index + 2].dest = &((*state)[player_index].config.guid);
		for (control_index = 4; control_index < IZ_CONTROLS; control_index += 1) {
			config_items[base_index + 3 + (control_index - 4)].dest = &((*state)[player_index].config.control_mapping[control_index]);
		}
	}
}

IZ_ProcedureResult IZ_JoystickSaveConfig(IZ_JoystickState(* state)[IZ_PLAYERS], const char* config_path) {
	IZ_JoystickBindStateToConfig(state, joystick_config_items);
	return IZ_ConfigSave(joystick_config_items, config_path);
}

void IZ_JoystickInitializeConfigItems(IZ_ConfigItem config_items[]) {
	u8 player_index;
	u8 control_index;
	char* main_section_name;
	char* control_mapping_section_name;
	for (player_index = 0; player_index < IZ_PLAYERS; player_index += 1) {
		main_section_name = IZ_calloc(64, sizeof(char));
		sprintf(main_section_name, "Joystick.%d", player_index);
		u8 base_index = (player_index * (IZ_CONTROLS - 4 + 2));
		config_items[base_index] = (IZ_ConfigItem) {
			IZ_CONFIG_TYPE_I32,
			sizeof(i32),
			main_section_name,
			"DeviceID",
			NULL,
			&IZ_JOYSTICK_DEFAULT_STATE[player_index].config.device_id,
			NULL,
			NULL,
		};

		config_items[base_index + 1] = (IZ_ConfigItem) {
			IZ_CONFIG_TYPE_U16,
			sizeof(u16),
			main_section_name,
			"AxisThreshold",
			NULL,
			&IZ_JOYSTICK_DEFAULT_STATE[player_index].config.axis_threshold,
			IZ_JoystickIsValidAxisThreshold,
			NULL,
		};

		config_items[base_index + 2] = (IZ_ConfigItem) {
			IZ_CONFIG_TYPE_GUID,
			sizeof(SDL_GUID),
			main_section_name,
			"GUID",
			NULL,
			&IZ_JOYSTICK_DEFAULT_STATE[player_index].config.guid,
			NULL,
			NULL,
		};

		// todo add game controller GUID for determining mappings

		control_mapping_section_name = IZ_calloc(64, sizeof(char));
		sprintf(control_mapping_section_name, "Joystick.%d.ControlMapping", player_index);
		for (control_index = 4; control_index < IZ_CONTROLS; control_index += 1) {
			config_items[base_index + 3 + (control_index - 4)] = (IZ_ConfigItem) {
				IZ_CONFIG_TYPE_U8,
				sizeof(u8),
				control_mapping_section_name,
				IZ_ACTION_NAMES[control_index],
				NULL,
				&IZ_JOYSTICK_DEFAULT_STATE[player_index].config.control_mapping[control_index],
				NULL,
				NULL,
			};
		}
	}

	config_items[IZ_PLAYERS * (IZ_CONTROLS - 4 + 2)] = (IZ_ConfigItem) {
		IZ_CONFIG_TYPE_VOID,
		0,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
	};
}

IZ_ProcedureResult IZ_JoystickInitializeConfig(IZ_JoystickState(* state)[IZ_PLAYERS], const char* config_path, u8 argc, const char* argv[]) {
	IZ_JoystickInitializeConfigItems(joystick_config_items);
	IZ_JoystickBindStateToConfig(state, joystick_config_items);
	if (IZ_ConfigInitialize(joystick_config_items, config_path, argc, argv) < 0) {
		return -1;
	}
	return 0;
}

IZ_ProcedureResult IZ_JoystickInitialize(IZ_JoystickState(* state)[IZ_PLAYERS], const char* config_path, u8 argc, const char* argv[]) {
	IZ_memcpy(state, sizeof(IZ_JoystickState) * IZ_PLAYERS, &IZ_JOYSTICK_DEFAULT_STATE, sizeof(IZ_JoystickState) * IZ_PLAYERS);

	// TODO query this file from the internet?
	SDL_GameControllerAddMappingsFromFile("assets/gamecontrollerdb.txt");
	u8 player_index;

	if (IZ_JoystickInitializeConfig(state, config_path, argc, argv) < 0) {
		return -2;
	}

	u8 joysticks_count = SDL_NumJoysticks();
	for (player_index = 0; player_index < joysticks_count; player_index += 1) {
		if (player_index >= IZ_PLAYERS) {
			break;
		}

		(*state)[player_index].device = SDL_JoystickOpen(state[player_index]->config.device_id);
		if (!(*state)[player_index].device) {
			break;
		}

		(*state)[player_index].config.device_id = SDL_JoystickInstanceID((*state)[player_index].device);
		SDL_GUID joystick_guid = SDL_JoystickGetGUID((*state)[player_index].device);
		IZ_memcpy(&(*state)[player_index].config.guid, sizeof(SDL_GUID), &joystick_guid, sizeof(SDL_GUID));
		//(*state)[player_index].config.guid = joystick_guid;

//		printf("[INPUT:JOYSTICK] Initialize event from GUID: ");
//		for (u8 zz = 0; zz < 16; zz += 1) {
//			printf("%02x", (*state)[player_index].config.guid.data[zz]);
//		}
//		printf("\n");
	}

	// Post config (after joystick GUIDs have been queried), this is unique to joysticks since they can be plugged in any
	// time.
	IZ_ConfigSaveResult post_config_save_result = IZ_JoystickSaveConfig(state, config_path);
	if (post_config_save_result < 0) {
		return -3;
	}

	return 0;
}

void IZ_JoystickTeardown(IZ_JoystickState(* state)[IZ_PLAYERS]) {
	u8 player_index;
	for (player_index = 0; player_index < IZ_PLAYERS; player_index += 1) {
		if (!(*state)[player_index].device) {
			continue;
		}
		SDL_JoystickClose((*state)[player_index].device);
	}
}
