#pragma once

#include <memory>
#include <mutex>
#include <obs-frontend-api.h>
#include <QDockWidget>
#include <QLabel>
#include <qlistwidget.h>
#include <QMovie>
#include <QPixmap>
#include <qpushbutton.h>
#include <QResizeEvent>
#include <QStackedWidget>
#include <QTimer>
#include <QVBoxLayout>

#include <graphics/matrix4.h>
#include <graphics/vec2.h>

#include "config-dialog.hpp"
#include "obs.hpp"
#include "projector.hpp"
#include "qt-display.hpp"
#include "scenes-dock.hpp"
#include "sources-dock.hpp"

#define ITEM_LEFT (1 << 0)
#define ITEM_RIGHT (1 << 1)
#define ITEM_TOP (1 << 2)
#define ITEM_BOTTOM (1 << 3)
#define ITEM_ROT (1 << 4)

#define VIRTUAL_CAMERA_VERTICAL 0
#define VIRTUAL_CAMERA_MAIN 1
#define VIRTUAL_CAMERA_BOTH 2

enum class ItemHandle : uint32_t {
	None = 0,
	TopLeft = ITEM_TOP | ITEM_LEFT,
	TopCenter = ITEM_TOP,
	TopRight = ITEM_TOP | ITEM_RIGHT,
	CenterLeft = ITEM_LEFT,
	CenterRight = ITEM_RIGHT,
	BottomLeft = ITEM_BOTTOM | ITEM_LEFT,
	BottomCenter = ITEM_BOTTOM,
	BottomRight = ITEM_BOTTOM | ITEM_RIGHT,
	Rot = ITEM_ROT
};

typedef std::function<bool(QObject *, QEvent *)> EventFilterFunc;

class OBSEventFilter : public QObject {
	Q_OBJECT
public:
	OBSEventFilter(EventFilterFunc filter_) : filter(filter_) {}

protected:
	bool eventFilter(QObject *obj, QEvent *event) { return filter(obj, event); }

public:
	EventFilterFunc filter;
};

class CanvasScenesDock;
class CanvasSourcesDock;
class CanvasTransitionsDock;
class OBSProjector;

class StreamServer {
public:
	obs_output_t *output = nullptr;
	obs_service_t *service = nullptr;
	obs_data_t *settings = nullptr;
	std::string name;
	std::string stream_key;
	std::string stream_server;
	bool enabled = true;
	bool stopping = false;
};

class CanvasDock : public QFrame {
	Q_OBJECT
	friend class CanvasScenesDock;
	friend class CanvasSourcesDock;
	friend class CanvasTransitionsDock;
	friend class SourceTree;
	friend class SourceTreeItem;
	friend class SourceTreeModel;
	friend class OBSBasicSettings;
	friend class OBSProjector;

private:
	QPointer<QAction> action;
	QString newer_version_available;
	QVBoxLayout *mainLayout;
	OBSQTDisplay *preview;
	bool preview_disabled = false;
	QFrame *previewDisabledWidget;
	QPushButton *configButton;
	OBSWeakSource source;
	obs_source_t *transitionAudioWrapper;
	std::vector<OBSSource> transitions;
	std::vector<OBSProjector *> projectors;
	std::unique_ptr<OBSEventFilter> eventFilter;
	time_t partnerBlockTime = 0;

	std::vector<obs_sceneitem_t *> hoveredPreviewItems;
	std::vector<obs_sceneitem_t *> selectedItems;
	std::mutex selectMutex;
	bool drawSpacingHelpers = true;

	vec2 startPos{};
	vec2 mousePos{};
	vec2 lastMoveOffset{};
	vec2 scrollingFrom{};
	vec2 scrollingOffset{};
	bool mouseDown = false;
	bool mouseMoved = false;
	bool mouseOverItems = false;
	bool cropping = false;
	bool locked = false;
	bool scrollMode = false;
	bool fixedScaling = false;
	bool selectionBox = false;
	int32_t scalingLevel = 0;
	float scalingAmount = 1.0f;
	float groupRot = 0.0f;

	float previewScale = 1.0f;

	obs_sceneitem_crop startCrop{};
	vec2 startItemPos{};
	vec2 cropSize{};
	OBSSceneItem stretchGroup;
	OBSSceneItem stretchItem;
	ItemHandle stretchHandle = ItemHandle::None;
	float rotateAngle{};
	vec2 rotatePoint{};
	vec2 offsetPoint{};
	vec2 stretchItemSize{};
	matrix4 screenToItem{};
	matrix4 itemToScreen{};
	matrix4 invGroupTransform{};
	obs_scene_t *scene = nullptr;
	obs_canvas_t *canvas = nullptr;
	obs_canvas_t *multiCanvas = nullptr;
	video_t *multiCanvasVideo = nullptr;
	obs_source_t *multiCanvasSource = nullptr;
	gs_texrender_t *texrender = nullptr;
	gs_stagesurf_t *stagesurface = nullptr;
	QPushButton *virtualCamButton;
	QPushButton *recordButton;
	QIcon recordActiveIcon = QIcon(":/aitum/media/recording.svg");
	QIcon recordInactiveIcon = QIcon(":/aitum/media/record.svg");
	QPushButton *replayButton;
	QPushButton *replayEnableButton;
	QCheckBox *replayEnable;
	QLabel *statusLabel;
	QTimer replayStatusResetTimer;
	QTimer recordDurationTimer;
	QPushButton *streamButton;
	QPushButton *streamButtonMulti;
	QIcon streamActiveIcon = QIcon(":/aitum/media/streaming.svg");
	QIcon streamInactiveIcon = QIcon(":/aitum/media/stream.svg");

	QIcon replayActiveIcon = QIcon(":/aitum/media/backtrack_on.svg");
	QIcon replayInactiveIcon = QIcon(":/aitum/media/backtrack_off.svg");

	QIcon virtualCamActiveIcon = QIcon(":/aitum/media/virtual_cam_on.svg");
	QIcon virtualCamInactiveIcon = QIcon(":/aitum/media/virtual_cam_off.svg");
	QComboBox *scenesCombo = nullptr;
	QCheckBox *linkedButton = nullptr;
	CanvasScenesDock *scenesDock = nullptr;
	QAction *scenesDockAction = nullptr;
	CanvasSourcesDock *sourcesDock = nullptr;
	QAction *sourcesDockAction = nullptr;
	CanvasTransitionsDock *transitionsDock = nullptr;
	QAction *transitionsDockAction = nullptr;
	OBSBasicSettings *configDialog = nullptr;

	obs_hotkey_pair_id stream_hotkey = OBS_INVALID_HOTKEY_PAIR_ID;
	obs_hotkey_pair_id record_hotkey = OBS_INVALID_HOTKEY_PAIR_ID;
	obs_hotkey_pair_id virtual_cam_hotkey = OBS_INVALID_HOTKEY_PAIR_ID;
	obs_hotkey_pair_id backtrack_hotkey = OBS_INVALID_HOTKEY_PAIR_ID;
	obs_hotkey_pair_id pause_hotkey = OBS_INVALID_HOTKEY_PAIR_ID;
	obs_hotkey_id chapter_hotkey = OBS_INVALID_HOTKEY_ID;
	obs_hotkey_id split_hotkey = OBS_INVALID_HOTKEY_ID;

	obs_output_t *virtualCamOutput = nullptr;
	obs_output_t *recordOutput = nullptr;
	obs_output_t *replayOutput = nullptr;

	uint32_t canvas_width;
	uint32_t canvas_height;
	bool restart_video = false;
	uint32_t streamingVideoBitrate;
	uint32_t recordVideoBitrate;
	uint32_t audioBitrate;
	bool streamingMatchMain;
	bool recordingMatchMain;
	bool startReplay;
	bool replayAlwaysOn;
	uint32_t replayDuration;
	std::string replayPath;
	std::string replayFilename;

	std::vector<StreamServer> streamOutputs;

	bool stream_delay_enabled;
	uint32_t stream_delay_duration;
	bool stream_delay_preserve;

	bool stream_advanced_settings;
	int stream_audio_track;
	std::string stream_encoder;
	obs_data_t *stream_encoder_settings;

	std::string recordPath;

	bool record_advanced_settings;
	std::string filename_formatting;
	std::string file_format;
	long long record_audio_tracks;
	std::string record_encoder;
	obs_data_t *record_encoder_settings;
	bool virtual_cam_warned;
	uint32_t virtual_cam_mode = 0;
	uint32_t max_size_mb = 0;
	uint32_t max_time_sec = 0;

	QString currentSceneName;
	bool first_time = false;
	bool multi_rtmp = false;
	bool disable_stream_settings = false;

	QColor GetSelectionColor() const;
	QColor GetCropColor() const;
	QColor GetHoverColor() const;

	gs_texture_t *overflow = nullptr;
	gs_vertbuffer_t *rectFill = nullptr;
	gs_vertbuffer_t *circleFill = nullptr;

	gs_vertbuffer_t *box = nullptr;

	OBSSourceAutoRelease spacerLabel[4];
	int spacerPx[4] = {0};

	inline bool IsFixedScaling() const { return fixedScaling; }

	OBSEventFilter *BuildEventFilter();

	bool HandleMousePressEvent(QMouseEvent *event);
	bool HandleMouseReleaseEvent(QMouseEvent *event);
	bool HandleMouseMoveEvent(QMouseEvent *event);
	bool HandleMouseLeaveEvent(QMouseEvent *event);
	bool HandleMouseWheelEvent(QWheelEvent *event);
	bool HandleKeyPressEvent(QKeyEvent *event);
	bool HandleKeyReleaseEvent(QKeyEvent *event);

	void UpdateCursor(uint32_t &flags);
	void ProcessClick(const vec2 &pos);
	void DoCtrlSelect(const vec2 &pos);
	void DoSelect(const vec2 &pos);
	OBSSceneItem GetItemAtPos(const vec2 &pos, bool selectBelow);

	QMenu *CreateAddSourcePopupMenu();
	void AddSceneItemMenuItems(QMenu *popup, OBSSceneItem sceneItem);
	void LoadSourceTypeMenu(QMenu *menu, const char *type);
	QMenu *CreateVisibilityTransitionMenu(bool visible, obs_sceneitem_t *sceneItem);
	QIcon GetIconFromType(enum obs_icon_type icon_type) const;
	QIcon GetGroupIcon() const;
	QIcon GetSceneIcon() const;

	obs_scene_item *GetSelectedItem(obs_scene_t *scene = nullptr);

	bool SelectedAtPos(obs_scene_t *scene, const vec2 &pos);
	void DrawOverflow(float scale);
	void DrawBackdrop(float cx, float cy);
	void DrawSpacingHelpers(obs_scene_t *scene, float x, float y, float cx, float cy, float scale, float sourceX,
				float sourceY);
	void DrawSpacingLine(vec3 &start, vec3 &end, vec3 &viewport, float pixelRatio);
	void SetLabelText(int sourceIndex, int px);
	void RenderSpacingHelper(int sourceIndex, vec3 &start, vec3 &end, vec3 &viewport, float pixelRatio);
	bool GetSourceRelativeXY(int mouseX, int mouseY, int &relX, int &relY);

	void RotateItem(const vec2 &pos);
	void CropItem(const vec2 &pos);
	void StretchItem(const vec2 &pos);
	void SnapStretchingToScreen(vec3 &tl, vec3 &br);
	vec3 GetSnapOffset(const vec3 &tl, const vec3 &br);
	void MoveItems(const vec2 &pos);
	void SnapItemMovement(vec2 &offset);
	void BoxItems(const vec2 &startPos, const vec2 &pos);
	void GetStretchHandleData(const vec2 &pos, bool ignoreGroup);
	void ClampAspect(vec3 &tl, vec3 &br, vec2 &size, const vec2 &baseSize);
	vec3 CalculateStretchPos(const vec3 &tl, const vec3 &br);

	bool DrawSelectionBox(float x1, float y1, float x2, float y2, gs_vertbuffer_t *rectFill);

	vec2 GetMouseEventPos(QMouseEvent *event);
	float GetDevicePixelRatio();

	enum class CenterType {
		Scene,
		Vertical,
		Horizontal,
	};
	void CenterSelectedItems(CenterType centerType);

	void AddSourceToScene(obs_source_t *source);
	void AddSourceTypeToMenu(QMenu *popup, const char *source_type, const char *name);

	bool StartVideo();
	void HandleRecordError(int code, QString last_error);

	void CreateScenesRow();
	void AddScene(QString duplicate = "", bool ask_name = true);
	void RemoveScene(const QString &sceneName);
	void SetLinkedScene(obs_source_t *scene, const QString &linkedScene);
	bool HasScene(QString scene) const;
	void CheckReplayBuffer(bool start = false);
	void SendVendorEvent(const char *e);
	void DeleteProjector(OBSProjector *projector);
	OBSProjector *OpenProjector(int monitor);
	void AddProjectorMenuMonitors(QMenu *parent, QObject *target, const char *slot);

	void TryRemux(QString path);
	void StartStreamOutput(std::vector<StreamServer>::iterator it);
	void CreateStreamOutput(std::vector<StreamServer>::iterator it);

	void StreamButtonMultiMenu(QMenu *menu);

	enum class MoveDir { Up, Down, Left, Right };
	void Nudge(int dist, MoveDir dir);

	static bool DrawSelectedOverflow(obs_scene_t *scene, obs_sceneitem_t *item, void *param);
	static bool FindSelected(obs_scene_t *scene, obs_sceneitem_t *item, void *param);
	static void DrawPreview(void *data, uint32_t cx, uint32_t cy);
	static bool DrawSelectedItem(obs_scene_t *scene, obs_sceneitem_t *item, void *param);
	static bool add_sources_of_type_to_menu(void *param, obs_source_t *source);

	static void virtual_cam_output_start(void *p, calldata_t *calldata);
	static void virtual_cam_output_stop(void *p, calldata_t *calldata);
	static void record_output_start(void *p, calldata_t *calldata);
	static void record_output_stop(void *p, calldata_t *calldata);
	static void record_output_stopping(void *p, calldata_t *calldata);
	static void replay_output_start(void *p, calldata_t *calldata);
	static void replay_output_stop(void *p, calldata_t *calldata);
	static void replay_saved(void *p, calldata_t *calldata);
	static void stream_output_start(void *p, calldata_t *calldata);
	static void stream_output_stop(void *p, calldata_t *calldata);
	static void source_rename(void *p, calldata_t *calldata);
	static void source_remove(void *p, calldata_t *calldata);
	static void source_save(void *p, calldata_t *calldata);
	static bool start_virtual_cam_hotkey(void *data, obs_hotkey_pair_id id, obs_hotkey_t *hotkey, bool pressed);
	static bool stop_virtual_cam_hotkey(void *data, obs_hotkey_pair_id id, obs_hotkey_t *hotkey, bool pressed);
	static bool start_recording_hotkey(void *data, obs_hotkey_pair_id id, obs_hotkey_t *hotkey, bool pressed);
	static bool stop_recording_hotkey(void *data, obs_hotkey_pair_id id, obs_hotkey_t *hotkey, bool pressed);
	static bool start_streaming_hotkey(void *data, obs_hotkey_pair_id id, obs_hotkey_t *hotkey, bool pressed);
	static bool stop_streaming_hotkey(void *data, obs_hotkey_pair_id id, obs_hotkey_t *hotkey, bool pressed);
	static bool pause_recording_hotkey(void *data, obs_hotkey_pair_id id, obs_hotkey_t *hotkey, bool pressed);
	static bool unpause_recording_hotkey(void *data, obs_hotkey_pair_id id, obs_hotkey_t *hotkey, bool pressed);
	static void recording_chapter_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed);
	static void recording_split_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed);

	static void SceneItemAdded(void *data, calldata_t *params);
	static void SceneReordered(void *data, calldata_t *params);
	static void SceneRefreshed(void *data, calldata_t *params);

	static void transition_override_stop(void *data, calldata_t *);

	static void get_transitions(void *data, struct obs_frontend_source_list *sources);
	static void save_load(obs_data_t *save_data, bool saving, void *param);

	static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val);
	static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val);

private slots:
	void AddSourceFromAction();
	void VirtualCamButtonClicked();
	void ReplayButtonClicked(QString filename = "");
	void RecordButtonClicked();
	void StreamButtonClicked();
	void ConfigButtonClicked();
	void OnVirtualCamStart();
	void OnVirtualCamStop();
	void OnRecordStart();
	void OnRecordStop(int code, QString last_error);
	void OnReplaySaved();
	void OnStreamStart();
	void OnStreamStop(int code, QString last_error, QString stream_server, QString stream_key);
	void OnReplayBufferStart();
	void OnReplayBufferStop(int code, QString last_error);
	void SwitchScene(const QString &scene_name, bool transition = true);
	obs_source_t *GetTransition(const char *transition_name);
	bool SwapTransition(obs_source_t *transition);
	void StartVirtualCam();
	void StopVirtualCam();
	void SetRecordAudioEncoders(obs_output_t *output);
	obs_encoder_t *GetRecordVideoEncoder();
	obs_encoder_t *GetStreamVideoEncoder();
	obs_encoder_t *GetStreamAudioEncoder();
	void ShowNoReplayOutputError();
	void StartRecord();
	void StopRecord();
	void StartReplayBuffer();
	void StopReplayBuffer();
	void StartStream();
	void StopStream();
	void AddSceneItem(OBSSceneItem item);
	void RefreshSources(OBSScene scene);
	void ReorderSources(OBSScene scene);
	void DestroyVideo();
	void MainSceneChanged();
	void MainStreamStart();
	void MainStreamStop();
	void MainRecordStart();
	void MainRecordStop();
	void MainReplayBufferStart();
	void MainReplayBufferStop();
	void MainVirtualCamStart();
	void MainVirtualCamStop();
	void ProfileChanged();
	void OpenPreviewProjector();
	void OpenSourceProjector();
	void SwitchBackToSelectedTransition();
	void SceneRemoved(const QString name);

	void ApiInfo(QString data);
	void updateStreamKey(const QString &newStreamKey, int index);
	void updateStreamServer(const QString &newStreamServer, int index);

public:
	CanvasDock(obs_data_t *settings, QWidget *parent = nullptr);
	~CanvasDock();

	void ClearScenes();
	void StopOutputs();
	void LoadScenes();
	void LogScenes();
	void FinishLoading();
	void setAction(QAction *action);
	CanvasScenesDock *GetScenesDock();
	inline uint32_t GetCanvasWidth() const { return canvas_width; }
	inline uint32_t GetCanvasHeight() const { return canvas_height; }
	inline video_t *GetVideo() const { return obs_canvas_get_video(canvas); }
	inline QString GetScene() const { return currentSceneName; }
	bool LoadStreamOutputs(obs_data_array_t *outputs);
	obs_data_array_t *SaveStreamOutputs();
	void StartStreamOutput(std::string name);
	void StopStreamOutput(std::string name);
	obs_output_t *GetStreamOutput(std::string name);
	inline obs_output_t *GetRecordOutput() const { return obs_output_get_ref(recordOutput); }
	void UpdateMulti();
	void DisableStreamSettings();

	obs_data_t *SaveSettings();

	obs_scene_t *GetCurrentScene();
	std::vector<QString> GetScenes();
	bool StreamingActive();
	bool RecordingActive();
	bool BacktrackActive();
	bool VirtualCameraActive();
};

class LockedCheckBox : public QCheckBox {
	Q_OBJECT

public:
	LockedCheckBox();
	explicit LockedCheckBox(QWidget *parent);
};

class VisibilityCheckBox : public QCheckBox {
	Q_OBJECT

public:
	VisibilityCheckBox();
	explicit VisibilityCheckBox(QWidget *parent);
};

class AspectRatioPixmapLabel : public QLabel {
	Q_OBJECT
public:
	explicit AspectRatioPixmapLabel(QWidget *parent = 0);
	virtual int heightForWidth(int width) const;
	virtual QSize sizeHint() const;
	QPixmap scaledPixmap() const;
public slots:
	void setPixmap(const QPixmap &);
	void resizeEvent(QResizeEvent *);

private:
	QPixmap pix;
};
