// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
// All rights reserved. This file is part of cc-backend.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package importer

import (
	"math"

	ccunits "github.com/ClusterCockpit/cc-lib/v2/ccUnits"
)

// getNormalizationFactor calculates the scaling factor needed to normalize a value
// to a more readable range (typically between 1.0 and 1000.0).
//
// For values greater than 1000, the function scales down by factors of 1000 (returns negative exponent).
// For values less than 1.0, the function scales up by factors of 1000 (returns positive exponent).
//
// Returns:
//   - factor: The multiplicative factor to apply (10^(count*scale))
//   - exponent: The power of 10 representing the adjustment (multiple of 3 for SI prefixes)
func getNormalizationFactor(v float64) (float64, int) {
	count := 0
	scale := -3

	// Prevent infinite loop for zero or negative values
	if v <= 0.0 {
		return 1.0, 0
	}

	if v > 1000.0 {
		for v > 1000.0 {
			v *= 1e-3
			count++
		}
	} else {
		for v < 1.0 {
			v *= 1e3
			count++
		}
		scale = 3
	}
	return math.Pow10(count * scale), count * scale
}

// getExponent calculates the SI prefix exponent from a numeric prefix value.
//
// For example:
//   - Input: 1000.0 (kilo) returns 3
//   - Input: 1000000.0 (mega) returns 6
//   - Input: 1000000000.0 (giga) returns 9
//
// Returns the exponent representing the power of 10 for the SI prefix.
func getExponent(p float64) int {
	count := 0

	// Prevent infinite loop for infinity or NaN values
	if math.IsInf(p, 0) || math.IsNaN(p) || p <= 0.0 {
		return 0
	}

	for p > 1.0 {
		p = p / 1000.0
		count++
	}

	return count * 3
}

// newPrefixFromFactor computes a new SI unit prefix after applying a normalization factor.
//
// Given an original prefix and an exponent adjustment, this function calculates
// the resulting SI prefix. For example, if normalizing from bytes (no prefix) by
// a factor of 10^9, the result would be the "G" (giga) prefix.
//
// Parameters:
//   - op: The original SI prefix value
//   - e: The exponent adjustment to apply
//
// Returns the new SI prefix after adjustment.
func newPrefixFromFactor(op ccunits.Prefix, e int) ccunits.Prefix {
	f := float64(op)
	exp := math.Pow10(getExponent(f) - e)
	return ccunits.Prefix(exp)
}

// Normalize adjusts a metric value and its SI unit prefix to a more readable range.
//
// This function is useful for automatically scaling metrics to appropriate units.
// For example, normalizing 2048 MiB might result in ~2.0 GiB.
//
// The function analyzes the average value and determines if a different SI prefix
// would make the number more human-readable (typically keeping values between 1 and 1000).
//
// Parameters:
//   - avg: The metric value to normalize
//   - p: The current SI prefix as a string (e.g., "K", "M", "G")
//
// Returns:
//   - factor: The multiplicative factor to apply to convert the value
//   - newPrefix: The new SI prefix string to use
//
// Example:
//
//	factor, newPrefix := Normalize(2048.0, "M")  // returns factor for MB->GB conversion, "G"
func Normalize(avg float64, p string) (float64, string) {
	f, e := getNormalizationFactor(avg)

	if e != 0 {
		np := newPrefixFromFactor(ccunits.NewPrefix(p), e)
		return f, np.Prefix()
	}

	return f, p
}
