#include "OutputProductionController.h"

#include <algorithm>

namespace
{
std::size_t ClampReadyLimit(unsigned value, std::size_t capacity)
{
	const std::size_t requested = static_cast<std::size_t>(value);
	if (capacity == 0)
		return requested;
	return (std::min)(requested, capacity);
}
}

OutputProductionController::OutputProductionController(const VideoPlayoutPolicy& policy) :
	mPolicy(NormalizeVideoPlayoutPolicy(policy))
{
}

void OutputProductionController::Configure(const VideoPlayoutPolicy& policy)
{
	mPolicy = NormalizeVideoPlayoutPolicy(policy);
}

OutputProductionDecision OutputProductionController::Decide(const OutputProductionPressure& pressure) const
{
	OutputProductionDecision decision;

	const std::size_t configuredMaxReadyFrames = static_cast<std::size_t>(mPolicy.maxReadyFrames);
	const std::size_t effectiveMaxReadyFrames = pressure.readyQueueCapacity > 0
		? (std::min)(configuredMaxReadyFrames, pressure.readyQueueCapacity)
		: configuredMaxReadyFrames;
	const std::size_t effectiveTargetReadyFrames = (std::min)(
		ClampReadyLimit(mPolicy.targetReadyFrames, pressure.readyQueueCapacity),
		effectiveMaxReadyFrames);

	decision.targetReadyFrames = effectiveTargetReadyFrames;
	decision.maxReadyFrames = effectiveMaxReadyFrames;

	if (effectiveMaxReadyFrames == 0)
	{
		decision.action = OutputProductionAction::Throttle;
		decision.reason = "no-ready-frame-capacity";
		return decision;
	}

	if (pressure.readyQueueDepth >= effectiveMaxReadyFrames)
	{
		decision.action = OutputProductionAction::Throttle;
		decision.reason = "ready-queue-full";
		return decision;
	}

	if (pressure.readyQueueDepth < effectiveTargetReadyFrames)
	{
		decision.action = OutputProductionAction::Produce;
		decision.requestedFrames = effectiveTargetReadyFrames - pressure.readyQueueDepth;
		decision.reason = "ready-queue-below-target";
		return decision;
	}

	if ((pressure.lateStreak > 0 || pressure.dropStreak > 0 || pressure.readyQueueUnderrunCount > 0) &&
		pressure.readyQueueDepth < effectiveMaxReadyFrames)
	{
		decision.action = OutputProductionAction::Produce;
		decision.requestedFrames = 1;
		decision.reason = "playout-pressure";
		return decision;
	}

	decision.action = OutputProductionAction::Wait;
	decision.reason = "ready-queue-at-target";
	return decision;
}

const char* OutputProductionActionName(OutputProductionAction action)
{
	switch (action)
	{
	case OutputProductionAction::Produce:
		return "Produce";
	case OutputProductionAction::Throttle:
		return "Throttle";
	case OutputProductionAction::Wait:
	default:
		return "Wait";
	}
}
