#include "OutputProductionController.h"

#include <iostream>
#include <string>

namespace
{
int gFailures = 0;

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

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

void TestLowQueueRequestsProductionToTarget()
{
	VideoPlayoutPolicy policy;
	policy.targetReadyFrames = 3;
	policy.maxReadyFrames = 5;
	OutputProductionController controller(policy);

	OutputProductionPressure pressure;
	pressure.readyQueueDepth = 1;
	pressure.readyQueueCapacity = 5;

	const OutputProductionDecision decision = controller.Decide(pressure);
	Expect(decision.action == OutputProductionAction::Produce, "low ready queue requests production");
	Expect(decision.requestedFrames == 2, "low ready queue requests enough frames to reach target");
	Expect(decision.targetReadyFrames == 3, "decision reports effective target");
	Expect(decision.maxReadyFrames == 5, "decision reports effective max");
	Expect(decision.reason == "ready-queue-below-target", "low queue decision names reason");
}

void TestFullQueueThrottles()
{
	VideoPlayoutPolicy policy;
	policy.targetReadyFrames = 2;
	policy.maxReadyFrames = 4;
	OutputProductionController controller(policy);

	OutputProductionPressure pressure;
	pressure.readyQueueDepth = 4;
	pressure.readyQueueCapacity = 4;

	const OutputProductionDecision decision = controller.Decide(pressure);
	Expect(decision.action == OutputProductionAction::Throttle, "full ready queue throttles production");
	Expect(decision.requestedFrames == 0, "full ready queue requests no frames");
	Expect(decision.reason == "ready-queue-full", "full queue decision names reason");
}

void TestAtTargetWaitsWithoutPressure()
{
	VideoPlayoutPolicy policy;
	policy.targetReadyFrames = 2;
	policy.maxReadyFrames = 4;
	OutputProductionController controller(policy);

	OutputProductionPressure pressure;
	pressure.readyQueueDepth = 2;
	pressure.readyQueueCapacity = 4;

	const OutputProductionDecision decision = controller.Decide(pressure);
	Expect(decision.action == OutputProductionAction::Wait, "ready queue at target waits without pressure");
	Expect(decision.requestedFrames == 0, "wait decision requests no frames");
	Expect(decision.reason == "ready-queue-at-target", "wait decision names reason");
}

void TestLateDropPressureRequestsHeadroom()
{
	VideoPlayoutPolicy policy;
	policy.targetReadyFrames = 2;
	policy.maxReadyFrames = 4;
	OutputProductionController controller(policy);

	OutputProductionPressure pressure;
	pressure.readyQueueDepth = 2;
	pressure.readyQueueCapacity = 4;
	pressure.lateStreak = 1;

	OutputProductionDecision decision = controller.Decide(pressure);
	Expect(decision.action == OutputProductionAction::Produce, "late pressure requests extra headroom");
	Expect(decision.requestedFrames == 1, "late pressure requests one frame");
	Expect(decision.reason == "playout-pressure", "late pressure decision names reason");

	pressure.lateStreak = 0;
	pressure.dropStreak = 2;
	decision = controller.Decide(pressure);
	Expect(decision.action == OutputProductionAction::Produce, "drop pressure requests extra headroom");

	pressure.dropStreak = 0;
	pressure.readyQueueUnderrunCount = 1;
	decision = controller.Decide(pressure);
	Expect(decision.action == OutputProductionAction::Produce, "underrun pressure requests extra headroom");
}

void TestPolicyNormalizesAndClampsToCapacity()
{
	VideoPlayoutPolicy policy;
	policy.targetReadyFrames = 0;
	policy.maxReadyFrames = 8;
	OutputProductionController controller(policy);

	OutputProductionPressure pressure;
	pressure.readyQueueDepth = 1;
	pressure.readyQueueCapacity = 3;

	const OutputProductionDecision decision = controller.Decide(pressure);
	Expect(decision.action == OutputProductionAction::Wait, "normalized target at current depth waits");
	Expect(decision.targetReadyFrames == 1, "target normalizes to at least one frame");
	Expect(decision.maxReadyFrames == 3, "max ready frames clamps to queue capacity");
}

void TestActionNames()
{
	Expect(OutputProductionActionName(OutputProductionAction::Produce) == std::string("Produce"), "produce action has name");
	Expect(OutputProductionActionName(OutputProductionAction::Wait) == std::string("Wait"), "wait action has name");
	Expect(OutputProductionActionName(OutputProductionAction::Throttle) == std::string("Throttle"), "throttle action has name");
}
}

int main()
{
	TestLowQueueRequestsProductionToTarget();
	TestFullQueueThrottles();
	TestAtTargetWaitsWithoutPressure();
	TestLateDropPressureRequestsHeadroom();
	TestPolicyNormalizesAndClampsToCapacity();
	TestActionNames();

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

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