import tkinter as tk
from tkinter import ttk
from typing import Callable, Self

from .. import display
from ..math_util import *



def validate_ch_int  ( text:str ):
	try:
		int(text)
		return True
	except:
		return False

def validate_ch_float( text:str ):
	try:
		float(text)
		return True
	except:
		return False



class WidgetBase:
	def set_grid( self:Self, col:int,row:int, sticky=tk.W ):
		if hasattr( self, "_widget" ):
			self._widget.grid( column=col,row=row, sticky=sticky )
		else:
			for k, widget in enumerate(self._widgets):
				widget.grid( column=col+k,row=row, sticky=sticky )
		return self



class Label(WidgetBase):
	def __init__( self:Self, parent:tk.Misc, text:str ):
		self._widget = ttk.Label( parent, text=text, justify="left",anchor="w" )



class Button(WidgetBase):
	def __init__( self:Self,
		parent:tk.Misc, text:str, callback_fn:Callable[[],None], force_lalign:bool=False
	):
		if force_lalign:
			self._widget = tk.Button( parent, text=text, command=callback_fn, justify="left",anchor="w" )
		else: #Not supported in `ttk`, apparently?
			self._widget = ttk.Button( parent, text=text, command=callback_fn )

	def set_enabled( self:Self, enabled:bool ):
		self._widget["state"] = tk.NORMAL if enabled else tk.DISABLED



class TextEntry(WidgetBase):
	def __init__( self:Self, parent:tk.Misc, default_text:str="", validate_fn=None ):
		self._var = tk.StringVar()
		if default_text:
			self.set_text(default_text)

		if validate_fn == None:
			self._widget = ttk.Entry(
				parent, textvariable=self._var
			)
		else:
			self._widget = ttk.Entry(
				parent, textvariable=self._var,
				validate="focusout", validatecommand=( display.tkroot.register(validate_fn), "%S" )
			)

	def get_text( self:Self ):
		return self._var.get()
	def set_text( self:Self, text:str ):
		self._var.set(text)
		return self

class FloatEntry(TextEntry):
	def __init__( self:Self, parent:tk.Misc, minval:float,maxval:float, default_text:str="" ):
		TextEntry.__init__( self, parent, default_text,
			lambda text: validate_ch_float(text)
		)

		self.minval = minval
		self.maxval = maxval
		def callback(evt):
			try   : val = clamp( float(self.get_text()), self.minval,self.maxval )
			except: val = self.minval
			self.set_value(val)
		self._widget.bind( "<FocusOut>", callback )

	def get_value( self:Self ):
		return float( self.get_text() )
	def set_value( self:Self, val:float ):
		self.set_text( str(val) )
		return self

class IntegerEntry(TextEntry):
	def __init__( self:Self, parent:tk.Misc, minval:int,maxval:int, default_text:str="" ):
		TextEntry.__init__( self, parent, default_text,
			lambda text: validate_ch_int(text)
		)

		self.minval = minval
		self.maxval = maxval
		def callback(evt):
			try   : val = clamp( int(self.get_text()), self.minval,self.maxval )
			except: val = self.minval
			self.set_value(val)
		self._widget.bind( "<FocusOut>", callback )

	def get_value( self:Self ):
		return int( self.get_text() )
	def set_value( self:Self, val:int ):
		self.set_text( str(val) )
		return self



class Checkbox(WidgetBase):
	def __init__( self:Self, parent:tk.Misc, default_val:bool=False, hover:str="" ):
		self._var = tk.BooleanVar()
		self.set_value(default_val)
		self._widget = tk.Checkbutton(
			parent, text=hover, variable=self._var, onvalue=True,offvalue=False
		)

	def get_value( self:Self ):
		return self._var.get()
	def set_value( self:Self, val:bool ):
		self._var.set(val)
		return self



class Radio(WidgetBase):
	def __init__( self:Self, parent:tk.Misc, options:dict[int,str], default_val:int ):
		self._var = tk.IntVar()
		self.set_value(default_val)
		self._widgets = [
			ttk.Radiobutton( parent, text=options[val],variable=self._var, value=val )
			for val in sorted( options.keys() )
		]

	def get_value( self:Self ):
		return self._var.get()
	def set_value( self:Self, val:int ):
		self._var.set(val)
		return self
