package hae

// assignLayers проставляет слой каждой группе точек.
//
// Правило проверено на всей истории (находка 33) и дважды ломалось на живых
// данных при более простых формулировках: классификация доставки целиком
// сложила минутные точки с посекундными и удвоила сумму за час, а
// классификация каждой метрики по отдельности растащила редкие метрики по трём
// слоям.
//
// Работающая формулировка в три шага:
//
//  1. плотная метрика (не меньше denseThreshold точек) классифицируется по
//     собственному выравниванию;
//  2. редкая наследует самый мелкий слой среди плотных метрик этой доставки —
//     её собственное выравнивание ничего не значит, одна метка на часе бывает
//     и у минутного ряда;
//  3. доставка без плотных метрик вовсе наследует последний надёжно выведенный
//     слой той же автоматизации, а если и его нет — берёт слой из НАДЁЖНОГО
//     заголовка.
//
// Третий шаг не роскошь: измерено 2 такие доставки из 89, обе с заголовком
// `Default`. Заменить его заголовком нельзя — `Default` не означает режима.
func assignLayers(groups []group, meta Meta, res *Result) error {
	delivery := deliveryLayer(groups)

	if delivery == "" {
		// Плотных метрик нет: наследуем от прошлых доставок этой автоматизации,
		// иначе — от надёжного заголовка.
		delivery = meta.FallbackLayer
		if delivery == "" {
			delivery = res.HeaderLayer
		}
		if delivery == "" && needsDerivedLayer(groups) {
			return ErrLayerUnknown
		}
	}

	res.Layer = delivery
	// Расхождение сверяется только с надёжным заголовком: `Default` не
	// означает режима, и сравнение с ним давало бы WARN на каждой доставке
	// потока в пять минут — сигнал утонул бы в собственном шуме.
	res.LayerMismatch = res.HeaderLayer != "" && delivery != res.HeaderLayer

	for i := range groups {
		g := &groups[i]
		switch {
		case g.fixed:
			// Слой назначен схемой, выводить нечего.
		case g.dense:
			g.layer = g.alignment
		default:
			g.layer = delivery
		}
	}
	return nil
}

// needsDerivedLayer говорит, осталась ли в доставке хоть одна группа, которой
// слой действительно надо вывести.
//
// Доставка, состоящая из одних суточных сводок сна, слоя не требует — он у них
// назначен схемой. Отказ ради неё был бы необратим: исход детерминирован, и
// пересборка из архива повторила бы его вечно, то есть автоматизация,
// настроенная только на сон, не донесла бы данных никогда.
func needsDerivedLayer(groups []group) bool {
	for _, g := range groups {
		if !g.fixed {
			return true
		}
	}
	return false
}

// deliveryLayer — самый мелкий слой среди плотных метрик доставки. Пустой,
// если плотных метрик нет.
//
// Группы с назначенным слоем в голосовании не участвуют: суточных сводок сна
// бывает больше порога плотности, и их полуночные метки иначе назначили бы
// всей доставке слой hour.
func deliveryLayer(groups []group) Layer {
	var out Layer
	for _, g := range groups {
		if g.fixed || !g.dense {
			continue
		}
		if out == "" {
			out = g.alignment
			continue
		}
		out = finer(out, g.alignment)
	}
	return out
}
