"""
Common code for DaCHS's base package.
"""

#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.

import enum
import warnings

from gavo import utils
from gavo.utils import NotGiven #noflake: backwards compatiblity import

from gavo.utils.excs import (  #noflake: exceptions are historically in base	
	Error,
	BadCode, DataError, DBTableError, EmptyData, ExecutiveAction,
	LiteralParseError,
	MultiplicityError, NotFoundError, ParseException, RDNotFound,
	ReportableError, RestrictedElement, SkipThis, StructureError,
	SourceParseError, ValidationError)

from gavo.utils.dachstypes import (
	Any, NoReturn, Optional,
	Self, Structure, StructParseContext, StructParserValue, Type, Union)
	

class Ignore(ExecutiveAction):
	"""An executive action causing an element to be not adopted by its
	parent.

	Raise this in -- typically -- onElementComplete if the element just
	built goes somewhere else but into its parent structure (or is
	somehow benignly unusable).  Classic use case: Active Tags.
	"""


class Replace(ExecutiveAction):
	"""An executive action replacing the current child with the Exception's
	argument.

	Use this sparingly.  I'd like to get rid of it.
	"""
	def __init__(self,
			newOb: "Structure",
			newName: Optional[str] = None) -> None:
		self.newOb, self.newName = newOb, newName


# the tree event types for our struct parsing
try:
	ParserEventType = enum.StrEnum("ParserEventType", ["start", "end", "value"])
except AttributeError:
	# python < 3.11
	class ParserEventType: # type: ignore
		start = "start"
		end = "end"
		value = "value"


class Parser:
	"""A mixin for a structure you can feed events to.

	The class mixing thing in must define methods ``start_``, ``end_``,
	and ``value_``, each accepting a parse context (a base.RDParseContext
	instance), the name of an element or attribute, and a value
	(which is an attribute of dicts in the case of start, and a
	string in the case of value.

	They all must return either None or the next parser; start_ may also
	return a string, which then names the attribute the next value is for.
	"""
	def __init__(self) -> NoReturn:
		raise StructureError("Parser is now a pure mixin")
	
	def feedEvent(self,
			ctx: StructParseContext,
			type: ParserEventType,
			name: str,
			value: StructParserValue) -> Optional[Union["Parser", str]]:
		if type=="start":
			return self.start_(ctx, name, value)
		elif type=="value":
			return self.value_(ctx, name, value)
		elif type=="end":
			return self.end_(ctx, name, value)
		else:
			raise StructureError(
				"Illegal event type while building: '%s'"%type)

	def start_(self,
			ctx: StructParseContext,
			name: str,
			value: StructParserValue) -> Optional[Union["Parser", str]]:
		pass

	def end_(self,
			ctx: StructParseContext,
			name: str,
			value: StructParserValue) -> Optional["Parser"]:
		pass
	
	def value_(self,
			ctx: StructParseContext,
			name: str,
			value: StructParserValue) -> Optional["Parser"]:
		pass


class ParserEvent(tuple):
	"""a tuple consists of an event type (a ParserEventType)
	the name and value from common.Parser, and the position object.

	We probably should use a namedtuple here one day and actually
	check we get what we need, but for now, this is only to acquiesce
	the type checker.
	"""
	def __new__(cls, *args):
		return super().__new__(cls, args)


class StructParseDebugMixin:
	"""put this before Parser in the parent class list of a struct,
	and you'll see the events coming in to your parser.
	"""
	def feedEvent(self,
			ctx: StructParseContext,
			type: ParserEventType,
			name: str,
			value: StructParserValue) -> Optional[Self]:
		print(type, name, value, self)
		return super().feedEvent(   # type: ignore
			self, ctx, type, name, value)


class StructCallbacks:
	"""A class terminating super() calls for the structure callbacks.

	Structs, when finished, call completeElement(ctx), validate(),
	and onElementComplete() in sequence.  All these need to up-call.
	This mixin provides non-upcalling implementations of these methods,
	and it needs to be present in mixins for structure.

	This will only work properly if self actually is a struct, as we rely
	on the presence of name_ attributes in case of prematurely terminated
	method resolution.
	"""
	name_: Union[str, Type[utils.Undefined]] = "(StructCallbacks terminator)"

	def __warnTermination(self, methname: str) -> None:
		if hasattr(super(), methname):
			mro = " ".join(c.__name__ for c in self.__class__.__mro__)
			warnings.warn(f"Discarding {methname} method while calling up"
				f" from {self.name_}, mro {mro} ")

	def validate(self) -> None:
		self.__warnTermination("validate")
	
	def completeElement(self, ctx: StructParseContext) -> None:
		self.__warnTermination("completeElement")

	def onElementComplete(self) -> None:
		self.__warnTermination("onElementComplete")
	
	def setParent(self, parent: Any) -> None:
		self.__warnTermination("setParent")
