#include <selectorResolve.h>
#include <algorithm>
#include <cctype>
#include <optional>
#include <sstream>
#include <stdexcept>

namespace lcamera_dev {

namespace {

std::string toLowerAscii(const std::string& text)
{
	std::string lowered = text;
	for (char& ch : lowered) {
		ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
	}
	return lowered;
}

bool recordMatchesCriterion(
	const CameraIdentityRecord& record, const SelectorCriterion& criterion)
{
	switch (criterion.kind)
	{
	case SelectorCriterionKind::LibcameraId:
		return record.id == criterion.value;

	case SelectorCriterionKind::Index:
		return false;

	case SelectorCriterionKind::Model:
		return record.model == criterion.value;

	case SelectorCriterionKind::ModelSubstr:
		return record.model.find(criterion.value) != std::string::npos;

	case SelectorCriterionKind::Location:
		return toLowerAscii(record.locationLabel)
			== toLowerAscii(criterion.value);
	}

	return false;
}

int parseIndexCriterion(const SelectorCriterion& criterion)
{
	try {
		return std::stoi(criterion.value);
	}
	catch (const std::exception&) {
		throw std::runtime_error("Invalid index: value in deviceSelector");
	}
}

} // namespace

std::string formatCameraListForDiagnostics(
	const std::vector<CameraIdentityRecord>& records)
{
	std::ostringstream result;
	result << "Known cameras:\n";

	for (size_t i = 0; i < records.size(); ++i)
	{
		const CameraIdentityRecord& record = records[i];
		result << "  [" << i << "] id=" << record.id;
		if (!record.model.empty()) {
			result << " model=" << record.model;
		}
		if (!record.locationLabel.empty()) {
			result << " location=" << record.locationLabel;
		}
		result << '\n';
	}

	return result.str();
}

CameraIdentityRecord resolveSelectorAgainstRecords(
	const std::vector<SelectorCriterion>& criteria,
	const std::vector<CameraIdentityRecord>& records)
{
	std::optional<int> indexCriterion;
	for (const SelectorCriterion& criterion : criteria)
	{
		if (criterion.kind != SelectorCriterionKind::Index) {
			continue;
		}

		const int index = parseIndexCriterion(criterion);
		if (index < 0
			|| static_cast<size_t>(index) >= records.size())
		{
			throw std::runtime_error("index: selector out of range");
		}

		indexCriterion = index;
	}

	std::vector<const CameraIdentityRecord*> matches;
	matches.reserve(records.size());

	for (const CameraIdentityRecord& record : records)
	{
		bool matchesAll = true;
		for (const SelectorCriterion& criterion : criteria)
		{
			if (criterion.kind == SelectorCriterionKind::Index) {
				continue;
			}

			if (!recordMatchesCriterion(record, criterion))
			{
				matchesAll = false;
				break;
			}
		}

		if (!matchesAll) {
			continue;
		}

		matches.push_back(&record);
	}

	if (indexCriterion.has_value())
	{
		const CameraIdentityRecord& indexedRecord =
			records.at(static_cast<size_t>(*indexCriterion));

		bool hasNonIndexCriteria = false;
		for (const SelectorCriterion& criterion : criteria)
		{
			if (criterion.kind != SelectorCriterionKind::Index)
			{
				hasNonIndexCriteria = true;
				break;
			}
		}

		if (!hasNonIndexCriteria) { return indexedRecord; }

		auto it = std::find_if(
			matches.begin(), matches.end(),
			[&indexedRecord](const CameraIdentityRecord* candidate) {
				return candidate->id == indexedRecord.id;
			});

		if (it == matches.end())
		{
			throw std::runtime_error(
				"index: criterion conflicts with other selector clauses");
		}

		return indexedRecord;
	}

	if (matches.empty())
	{
		throw std::runtime_error(
			"No camera matches deviceSelector\n"
			+ formatCameraListForDiagnostics(records));
	}

	if (matches.size() > 1)
	{
		throw std::runtime_error(
			"Ambiguous deviceSelector: multiple cameras match\n"
			+ formatCameraListForDiagnostics(records));
	}

	return *matches.front();
}

CameraIdentityRecord resolveDeviceSelectorAgainstRecords(
	const std::string& deviceSelector,
	const std::vector<CameraIdentityRecord>& records)
{
	const std::vector<SelectorCriterion> criteria =
		parseDeviceSelector(deviceSelector);
	return resolveSelectorAgainstRecords(criteria, records);
}

} // namespace lcamera_dev
