/* Copyright (c) 2013-2023 Jeffrey Pfau
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once

#include "input/GamepadAxisEvent.h"
#include "input/GamepadHatEvent.h"
#include "input/InputDriver.h"
#include "input/InputMapper.h"

#include <QHash>
#include <QImage>
#include <QMutex>
#include <QReadWriteLock>
#include <QObject>
#include <QSet>
#include <QTimer>
#include <QVector>

#include <memory>

#include <mgba/core/input.h>
#include <mgba/gba/interface.h>

#ifdef BUILD_QT_MULTIMEDIA
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
#include "VideoDumper.h"
#endif
#include <QCamera>
#endif

struct mRotationSource;
struct mRumble;

namespace QGBA {

class ConfigController;
class Gamepad;
class InputSource;

class InputController : public QObject {
Q_OBJECT

public:
	enum class CameraDriver : int {
		NONE = 0,
#ifdef BUILD_QT_MULTIMEDIA
		QT_MULTIMEDIA = 1,
#endif
	};

	static const uint32_t KEYBOARD = 0x51545F4B;

	InputController(QWidget* topLevel = nullptr, QObject* parent = nullptr);
	~InputController();

	void addInputDriver(std::shared_ptr<InputDriver>);

	int playerId() const { return m_playerId; }

	void setConfiguration(ConfigController* config);
	void saveConfiguration();
	bool loadConfiguration(uint32_t type);
	bool loadProfile(uint32_t type, const QString& profile);
	void saveConfiguration(uint32_t type);
	void saveProfile(uint32_t type, const QString& profile);
	QString profileForType(uint32_t type);

	int mapKeyboard(int key) const;

	mInputMap* map() { return &m_inputMap; }
	const mInputMap* map() const { return &m_inputMap; }

	int pollEvents();

	static const int32_t AXIS_THRESHOLD = 0x3000;

	void setGamepadDriver(uint32_t type);
	const InputDriver* gamepadDriver() const { return m_inputDrivers.value(m_gamepadDriver).get(); }
	InputDriver* gamepadDriver() { return m_inputDrivers.value(m_gamepadDriver).get(); }

	QStringList connectedGamepads(uint32_t type = 0) const;
	int gamepadIndex(uint32_t type = 0) const;
	void setGamepad(uint32_t type, int index);
	void setGamepad(int index);
	void setPreferredGamepad(uint32_t type, int index);
	void setPreferredGamepad(int index);

	InputMapper mapper(uint32_t type);
	InputMapper mapper(InputDriver*);
	InputMapper mapper(InputSource*);

	void setSensorDriver(uint32_t type);
	const InputDriver* sensorDriver() const { return m_inputDrivers.value(m_sensorDriver).get(); }
	InputDriver* sensorDriver() { return m_inputDrivers.value(m_sensorDriver).get(); }

	void stealFocus(QWidget* focus);
	void releaseFocus(QWidget* focus);

	QList<QPair<QByteArray, QString>> listCameras() const;

	mRumble* rumble();
	mRotationSource* rotationSource();
	mImageSource* imageSource() { return &m_image; }
	GBALuminanceSource* luminance() { return &m_lux; }

signals:
	void updated();
	void profileLoaded(const QString& profile);
	void luminanceValueChanged(int value);

public slots:
	void testGamepad(uint32_t type);
	void update();

	void increaseLuminanceLevel();
	void decreaseLuminanceLevel();
	void setLuminanceLevel(int level);
	void setLuminanceValue(uint8_t value);

	void loadCamImage(const QString& path);
	void setCamImage(const QImage& image);

	void setCamera(const QByteArray& id);

private slots:
#ifdef BUILD_QT_MULTIMEDIA
	void prepareCamSettings(QCamera::Status);
#endif
	void setupCam();
	void teardownCam();

private:
	void postPendingEvent(int key);
	void clearPendingEvent(int key);
	void postPendingEvents(int keys);
	void clearPendingEvents(int keys);
	bool hasPendingEvent(int key) const;
	void sendGamepadEvent(QEvent*);

	static int claimPlayer();
	static void freePlayer(int);

	std::shared_ptr<Gamepad> gamepad(uint32_t type);
	QList<std::shared_ptr<Gamepad>> gamepads();

	QSet<int> activeGamepadButtons(uint32_t type);
	QSet<QPair<int, GamepadAxisEvent::Direction>> activeGamepadAxes(uint32_t type);
	QSet<QPair<int, GamepadHatEvent::Direction>> activeGamepadHats(uint32_t type);

	struct InputControllerLux : GBALuminanceSource {
		InputController* p;
		uint8_t value;
	} m_lux;
	uint8_t m_luxValue;
	int m_luxLevel;

	struct InputControllerImage : mImageSource {
		InputController* p;
		QImage image;
		QImage resizedImage;
		bool outOfDate;
		QMutex mutex;
		int w, h;
	} m_image;

#ifdef BUILD_QT_MULTIMEDIA
	bool m_cameraActive = false;
	QByteArray m_cameraDevice;
	std::unique_ptr<QCamera> m_camera;
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
	VideoDumper m_videoDumper;
#endif
#endif

	static int s_claimedPlayers;
	mInputMap m_inputMap;
	ConfigController* m_config = nullptr;
	int m_playerId;
	QWidget* m_topLevel;
	QWidget* m_focusParent;

	QHash<uint32_t, std::shared_ptr<InputDriver>> m_inputDrivers;
	uint32_t m_gamepadDriver = 0;
	uint32_t m_sensorDriver = 0;

	QSet<int> m_activeButtons;
	QSet<QPair<int, GamepadAxisEvent::Direction>> m_activeAxes;
	QSet<QPair<int, GamepadHatEvent::Direction>> m_activeHats;
	QTimer m_gamepadTimer{nullptr};

	QSet<int> m_pendingEvents;
	QReadWriteLock m_eventsLock;
};

}
