"""
Spectral-related functions.
"""

#c Copyright 2008-2025, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL.  See the
#c COPYING file in the source distribution.


from gavo.base import unitconv
from gavo.base import osinter
from gavo.utils import excs

from gavo.utils.dachstypes import Callable, Dict, Tuple


######################## specconv

def toStdSpecUnit(fromUnit: str) -> Tuple[str, float]:
	"""returns a pair of (standard unit, factor) to turn fromUnit into
	one of J, Hz, or m.

	Unconvertable units raise a UfuncError.
	"""
	for stdUnit in ["J", "Hz", "m"]:
		try:
			 factor = unitconv.computeConversionFactor(fromUnit, stdUnit)
		except unitconv.IncompatibleUnits:
			continue
		return stdUnit, factor
	
	raise unitconv.IncompatibleUnits(
		f"{fromUnit} is not a spectral unit understood here")


SPEC_CONVERSION = {
	("J", "m"): "h*c/(({expr})*{f})",
	("J", "Hz"): "({expr})*{f}/h",
	("J", "J"): "({expr})*{f}",
	("Hz", "m"): "c/({expr})/{f}",
	("Hz", "Hz"): "{f}*({expr})",
	("Hz", "J"): "h*{f}*({expr})",
	("m", "m"): "{f}*({expr})",
	("m", "Hz"): "c/({expr})/{f}",
	("m", "J"): "h*c/({expr})/{f}",}


def getSpecExpr(fromUnit: str, toUnit: str) -> str:
	"""returns an expression that turns a value in fromUnit to one in toUnit.

	The value in fromUnit is represented by a {} so you can just .format
	the expression it is in into the resulting string.
	"""
	fromStd, fromFactor = toStdSpecUnit(fromUnit)
	toStd, toFactor = toStdSpecUnit(toUnit)
	convExpr = SPEC_CONVERSION[fromStd, toStd
			].replace("h", str(unitconv.PLANCK_H)
			).replace("c", str(unitconv.LIGHT_C))
	return "{}/{}".format(
		convExpr.format(f=fromFactor, expr="{}"),
		toFactor)


def getSpecConverter(fromUnit: str, toUnit: str) -> Callable[[float], float]:
	"""Returns a function converting spectral values in fromUnit to toUnit.
	"""
	return eval("lambda x: "+getSpecExpr(fromUnit, toUnit).format("x"))


########### our filter library

NM = 1e-9

def getFilterMap() -> Tuple[
		Dict[str, Tuple[float, float, float]],
		Dict[str, str]]:
	"""returns a pair for filter_def, aliases mapping.

	This parses from the built-in data/filters.txt; filter_def maps
	filter names to tuples of lower, central, long wavelength in metres.
	(we may add further fields later, so it's safer to [:3] these results).

	Aliases just maps common filter names to our canontical names
	for these filters.
	"""
	filterMap, aliases = {}, {}

	with osinter.openDistFile("data/filters.txt", encoding="utf-8") as f:
		for index, line in enumerate(f):
			if not line:
				break
			line = line.strip()
			if line.startswith("#"):
				continue
			parts = line.split("\t")
			primary = parts[0].strip()
			if primary.startswith("="):
				primary = primary[1:].strip()
				for p in parts[1:]:
					aliases[p.strip()] = primary
			else:
				try:
					filterMap[primary] = (
						float(parts[1])*NM, float(parts[2])*NM, float(parts[3])*NM)
				except IndexError:
					raise excs.ReportableError("Bad filtermap: filters.txt,"
							" line %d."%(index+1))
	
	return filterMap, aliases
