#include "VideoIOTypes.h"

#include <array>
#include <iostream>

namespace
{
int gFailures = 0;

void Expect(bool condition, const char* message)
{
	if (condition)
		return;

	std::cerr << "FAIL: " << message << "\n";
	++gFailures;
}

class FakeVideoIODevice : public VideoIODevice
{
public:
	void ReleaseResources() override {}

	bool DiscoverDevicesAndModes(const VideoFormatSelection&, std::string&) override
	{
		mState.inputFrameSize = { 1920, 1080 };
		mState.outputFrameSize = { 1920, 1080 };
		mState.inputDisplayModeName = "fake 1080p";
		mState.outputModelName = "Fake Video IO";
		mState.hasInputDevice = true;
		return true;
	}

	bool SelectPreferredFormats(const VideoFormatSelection&, bool, std::string&) override
	{
		mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8;
		mState.outputPixelFormat = VideoIOPixelFormat::Bgra8;
		mState.inputFrameRowBytes = VideoIORowBytes(mState.inputPixelFormat, mState.inputFrameSize.width);
		mState.outputFrameRowBytes = VideoIORowBytes(mState.outputPixelFormat, mState.outputFrameSize.width);
		mState.captureTextureWidth = PackedTextureWidthFromRowBytes(mState.inputFrameRowBytes);
		mState.outputPackTextureWidth = mState.outputFrameSize.width;
		return true;
	}

	bool ConfigureInput(InputFrameCallback callback, const VideoFormat&, std::string&) override
	{
		mInputCallback = callback;
		return true;
	}

	bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat&, bool, std::string&) override
	{
		mOutputCallback = callback;
		return true;
	}

	bool PrepareOutputSchedule() override
	{
		mPreparedOutputSchedule = true;
		return true;
	}

	bool StartInputStreams() override
	{
		mInputStreamsStarted = true;
		mState.hasInputSource = true;
		VideoIOFrame input;
		input.bytes = mInputBytes.data();
		input.rowBytes = static_cast<long>(mState.inputFrameRowBytes);
		input.width = mState.inputFrameSize.width;
		input.height = mState.inputFrameSize.height;
		input.pixelFormat = mState.inputPixelFormat;
		if (mInputCallback)
			mInputCallback(input);
		return true;
	}

	bool StartScheduledPlayback() override
	{
		mScheduledPlaybackStarted = true;
		if (mOutputCallback)
			mOutputCallback(VideoIOCompletion{ VideoIOCompletionResult::Completed });
		return true;
	}

	bool Start() override
	{
		return PrepareOutputSchedule() && StartInputStreams() && StartScheduledPlayback();
	}

	bool Stop() override { return true; }
	const VideoIOState& State() const override { return mState; }
	VideoIOState& MutableState() override { return mState; }

	bool BeginOutputFrame(VideoIOOutputFrame& frame) override
	{
		frame.bytes = mOutputBytes.data();
		frame.rowBytes = static_cast<long>(mState.outputFrameRowBytes);
		frame.width = mState.outputFrameSize.width;
		frame.height = mState.outputFrameSize.height;
		frame.pixelFormat = mState.outputPixelFormat;
		return true;
	}

	void EndOutputFrame(VideoIOOutputFrame&) override {}

	bool ScheduleOutputFrame(const VideoIOOutputFrame&) override
	{
		++mScheduledFrames;
		return true;
	}

	VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth) override
	{
		mLastCompletion = result;
		mLastReadyQueueDepth = readyQueueDepth;
		VideoPlayoutRecoveryDecision decision;
		decision.result = result;
		decision.readyQueueDepth = readyQueueDepth;
		return decision;
	}

	unsigned ScheduledFrames() const { return mScheduledFrames; }
	bool PreparedOutputSchedule() const { return mPreparedOutputSchedule; }
	bool InputStreamsStarted() const { return mInputStreamsStarted; }
	bool ScheduledPlaybackStarted() const { return mScheduledPlaybackStarted; }
	VideoIOCompletionResult LastCompletion() const { return mLastCompletion; }
	uint64_t LastReadyQueueDepth() const { return mLastReadyQueueDepth; }

private:
	VideoIOState mState;
	InputFrameCallback mInputCallback;
	OutputFrameCallback mOutputCallback;
	std::array<unsigned char, 3840> mInputBytes = {};
	std::array<unsigned char, 7680> mOutputBytes = {};
	unsigned mScheduledFrames = 0;
	bool mPreparedOutputSchedule = false;
	bool mInputStreamsStarted = false;
	bool mScheduledPlaybackStarted = false;
	VideoIOCompletionResult mLastCompletion = VideoIOCompletionResult::Unknown;
	uint64_t mLastReadyQueueDepth = 0;
};
}

int main()
{
	FakeVideoIODevice device;
	VideoFormatSelection selection;
	std::string error;
	bool inputSeen = false;
	bool outputSeen = false;

	Expect(device.DiscoverDevicesAndModes(selection, error), "fake discovery succeeds");
	Expect(device.SelectPreferredFormats(selection, false, error), "fake format selection succeeds");
	Expect(device.ConfigureInput([&](const VideoIOFrame& frame) {
		inputSeen = frame.bytes != nullptr && frame.width == 1920 && frame.pixelFormat == VideoIOPixelFormat::Uyvy8;
	}, selection.input, error), "fake input config succeeds");
	Expect(device.ConfigureOutput([&](const VideoIOCompletion& completion) {
		outputSeen = completion.result == VideoIOCompletionResult::Completed;
	}, selection.output, false, error), "fake output config succeeds");
	Expect(device.Start(), "fake device starts");

	VideoIOOutputFrame outputFrame;
	Expect(device.BeginOutputFrame(outputFrame), "fake output frame can be acquired");
	device.EndOutputFrame(outputFrame);
	device.AccountForCompletionResult(VideoIOCompletionResult::Completed, 2);
	Expect(device.ScheduleOutputFrame(outputFrame), "fake output frame can be scheduled");

	Expect(inputSeen, "fake input callback emits generic frame");
	Expect(outputSeen, "fake output callback emits generic completion");
	Expect(device.PreparedOutputSchedule(), "fake output schedule was prepared");
	Expect(device.InputStreamsStarted(), "fake input streams started");
	Expect(device.ScheduledPlaybackStarted(), "fake scheduled playback started");
	Expect(device.ScheduledFrames() == 1, "fake backend schedules one frame");
	Expect(device.LastCompletion() == VideoIOCompletionResult::Completed, "fake backend records generic completion");
	Expect(device.LastReadyQueueDepth() == 2, "fake backend records ready queue depth");

	if (gFailures != 0)
	{
		std::cerr << gFailures << " VideoIODevice fake test failure(s).\n";
		return 1;
	}

	std::cout << "VideoIODevice fake tests passed.\n";
	return 0;
}
