#pragma once

#include "VideoIOFormat.h"
#include "VideoMode.h"

#include <cstdint>
#include <functional>
#include <string>

enum class VideoIOBackend
{
	DeckLink
};

enum class VideoIOCompletionResult
{
	Completed,
	DisplayedLate,
	Dropped,
	Flushed,
	Unknown
};

struct VideoIOConfig
{
	VideoFormatSelection videoModes;
	bool externalKeyingEnabled = false;
	bool preferTenBit = true;
};

struct VideoIOState
{
	FrameSize inputFrameSize;
	FrameSize outputFrameSize;
	VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
	VideoIOPixelFormat outputPixelFormat = VideoIOPixelFormat::Bgra8;
	unsigned inputFrameRowBytes = 0;
	unsigned outputFrameRowBytes = 0;
	unsigned captureTextureWidth = 0;
	unsigned outputPackTextureWidth = 0;
	std::string inputDisplayModeName = "1080p59.94";
	std::string outputDisplayModeName = "1080p59.94";
	std::string outputModelName;
	std::string statusMessage;
	std::string formatStatusMessage;
	bool hasInputDevice = false;
	bool hasInputSource = false;
	bool supportsInternalKeying = false;
	bool supportsExternalKeying = false;
	bool keyerInterfaceAvailable = false;
	bool externalKeyingActive = false;
	double frameBudgetMilliseconds = 0.0;
	bool actualDeckLinkBufferedFramesAvailable = false;
	uint64_t actualDeckLinkBufferedFrames = 0;
	double deckLinkScheduleCallMilliseconds = 0.0;
	uint64_t deckLinkScheduleFailureCount = 0;
	bool deckLinkScheduleLeadAvailable = false;
	int64_t deckLinkPlaybackStreamTime = 0;
	uint64_t deckLinkPlaybackFrameIndex = 0;
	uint64_t deckLinkNextScheduleFrameIndex = 0;
	int64_t deckLinkScheduleLeadFrames = 0;
	uint64_t deckLinkScheduleRealignmentCount = 0;
};

struct VideoIOFrame
{
	void* bytes = nullptr;
	long rowBytes = 0;
	unsigned width = 0;
	unsigned height = 0;
	VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Uyvy8;
	bool hasNoInputSource = false;
};

struct VideoIOOutputFrame
{
	void* bytes = nullptr;
	long rowBytes = 0;
	unsigned width = 0;
	unsigned height = 0;
	VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
	void* nativeFrame = nullptr;
	void* nativeBuffer = nullptr;
};

struct VideoIOCompletion
{
	VideoIOCompletionResult result = VideoIOCompletionResult::Completed;
	void* outputFrameBuffer = nullptr;
};

struct VideoIOScheduleTime
{
	int64_t streamTime = 0;
	int64_t duration = 0;
	int64_t timeScale = 0;
	uint64_t frameIndex = 0;
};

struct VideoPlayoutRecoveryDecision
{
	VideoIOCompletionResult result = VideoIOCompletionResult::Completed;
	uint64_t completedFrameIndex = 0;
	uint64_t scheduledFrameIndex = 0;
	uint64_t readyQueueDepth = 0;
	uint64_t scheduledLeadFrames = 0;
	uint64_t measuredLagFrames = 0;
	uint64_t catchUpFrames = 0;
	uint64_t lateStreak = 0;
	uint64_t dropStreak = 0;
};

class VideoIODevice
{
public:
	using InputFrameCallback = std::function<void(const VideoIOFrame&)>;
	using OutputFrameCallback = std::function<void(const VideoIOCompletion&)>;

	virtual ~VideoIODevice() = default;
	virtual void ReleaseResources() = 0;
	virtual bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) = 0;
	virtual bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) = 0;
	virtual bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) = 0;
	virtual bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) = 0;
	virtual bool PrepareOutputSchedule() = 0;
	virtual bool StartInputStreams() = 0;
	virtual bool StartScheduledPlayback() = 0;
	virtual bool Start() = 0;
	virtual bool Stop() = 0;
	virtual const VideoIOState& State() const = 0;
	virtual VideoIOState& MutableState() = 0;
	virtual bool BeginOutputFrame(VideoIOOutputFrame& frame) = 0;
	virtual void EndOutputFrame(VideoIOOutputFrame& frame) = 0;
	virtual bool ScheduleOutputFrame(const VideoIOOutputFrame& frame) = 0;
	virtual VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth) = 0;

	bool HasInputDevice() const { return State().hasInputDevice; }
	bool HasInputSource() const { return State().hasInputSource; }
	bool InputOutputDimensionsDiffer() const { return State().inputFrameSize != State().outputFrameSize; }
	const FrameSize& InputFrameSize() const { return State().inputFrameSize; }
	const FrameSize& OutputFrameSize() const { return State().outputFrameSize; }
	unsigned InputFrameWidth() const { return State().inputFrameSize.width; }
	unsigned InputFrameHeight() const { return State().inputFrameSize.height; }
	unsigned OutputFrameWidth() const { return State().outputFrameSize.width; }
	unsigned OutputFrameHeight() const { return State().outputFrameSize.height; }
	VideoIOPixelFormat InputPixelFormat() const { return State().inputPixelFormat; }
	VideoIOPixelFormat OutputPixelFormat() const { return State().outputPixelFormat; }
	bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().inputPixelFormat); }
	bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().outputPixelFormat); }
	unsigned InputFrameRowBytes() const { return State().inputFrameRowBytes; }
	unsigned OutputFrameRowBytes() const { return State().outputFrameRowBytes; }
	unsigned CaptureTextureWidth() const { return State().captureTextureWidth; }
	unsigned OutputPackTextureWidth() const { return State().outputPackTextureWidth; }
	const std::string& FormatStatusMessage() const { return State().formatStatusMessage; }
	const std::string& InputDisplayModeName() const { return State().inputDisplayModeName; }
	const std::string& OutputModelName() const { return State().outputModelName; }
	bool SupportsInternalKeying() const { return State().supportsInternalKeying; }
	bool SupportsExternalKeying() const { return State().supportsExternalKeying; }
	bool KeyerInterfaceAvailable() const { return State().keyerInterfaceAvailable; }
	bool ExternalKeyingActive() const { return State().externalKeyingActive; }
	const std::string& StatusMessage() const { return State().statusMessage; }
	double FrameBudgetMilliseconds() const { return State().frameBudgetMilliseconds; }
	void SetStatusMessage(const std::string& message) { MutableState().statusMessage = message; }
};
