import sys
import os
import re

from i18n import _

from importlib.machinery import SourceFileLoader


class RubberStamp:
	"""Howdy rubber stamp"""

	UI_TEXT = "ui_text"
	UI_SUBTEXT = "ui_subtext"

	def set_ui_text(self, text, type=None):
		"""Convert an ui string to input howdy-gtk understands"""
		typedec = "M"

		if type == self.UI_SUBTEXT:
			typedec = "S"

		return self.send_ui_raw(typedec + "=" + text)

	def send_ui_raw(self, command):
		"""Write raw command to howdy-gtk stdin"""
		if self.config.getboolean("debug", "verbose_stamps", fallback=False):
			print("Sending command to howdy-gtk: " + command)

		# Add a newline because the ui reads per line
		command += " \n"

		# If we're connected to the ui
		if self.gtk_proc:
			# Send the command as bytes
			self.gtk_proc.stdin.write(bytearray(command.encode("utf-8")))
			self.gtk_proc.stdin.flush()

			# Write a padding line to force the command through any buffers
			self.gtk_proc.stdin.write(bytearray("P=_PADDING \n".encode("utf-8")))
			self.gtk_proc.stdin.flush()


def execute(config, gtk_proc, opencv):
	verbose = config.getboolean("debug", "verbose_stamps", fallback=False)
	dir_path = os.path.dirname(os.path.realpath(__file__))
	installed_stamps = []

	# Go through each file in the rubberstamp folder
	for filename in os.listdir(dir_path):
		# Remove non-readable file or directories
		if not os.path.isfile(dir_path + "/" + filename):
			continue

		# Remove meta files
		if filename in ["__init__.py", ".gitignore"]:
			continue

		# Add the found file to the list of enabled rubberstamps
		installed_stamps.append(filename.split(".")[0])

	if verbose: print("Installed rubberstamps: " + ", ".join(installed_stamps))

	# Get the rules defined in the config
	raw_rules = config.get("rubberstamps", "stamp_rules")
	rules = raw_rules.split("\n")

	# Go through the rules one by one
	for rule in rules:
		rule = rule.strip()

		if len(rule) <= 1:
			continue

		# Parse the rule with regex
		regex_result = re.search("^(\w+)\s+([\w\.]+)\s+([a-z]+)(.*)?$", rule, re.IGNORECASE)

		# Error out if the regex did not match (invalid line)
		if not regex_result:
			print(_("Error parsing rubberstamp rule: {}").format(rule))
			continue

		type = regex_result.group(1)

		# Error out if the stamp name in the rule is not a file
		if type not in installed_stamps:
			print(_("Stamp not installed: {}").format(type))
			continue

		# Load the module from file
		module = SourceFileLoader(type, dir_path + "/" + type + ".py").load_module()

		# Try to get the class with the same name
		try:
			constructor = getattr(module, type)
		except AttributeError:
			print(_("Stamp error: Class {} not found").format(type))
			continue

		# Init the class and set common values
		instance = constructor()
		instance.verbose = verbose
		instance.config = config
		instance.gtk_proc = gtk_proc
		instance.opencv = opencv

		# Set some opensv shorthands
		instance.video_capture = opencv["video_capture"]
		instance.face_detector = opencv["face_detector"]
		instance.pose_predictor = opencv["pose_predictor"]
		instance.clahe = opencv["clahe"]

		# Parse and set the 2 required options for all rubberstamps
		instance.options = {
			"timeout": float(re.sub("[a-zA-Z]", "", regex_result.group(2))),
			"failsafe": regex_result.group(3) != "faildeadly"
		}

		# Try to get the class do declare its other config variables
		try:
			instance.declare_config()
		except Exception:
			print(_("Internal error in rubberstamp configuration declaration:"))

			import traceback
			traceback.print_exc()
			continue

		# Split the optional arguments at the end of the rule by spaces
		raw_options = regex_result.group(4).split()

		# For each of those aoptional arguments
		for option in raw_options:
			# Get the key to the left, and the value to the right of the equal sign
			key, value = option.split("=")

			# Error out if a key has been set that was not declared by the module before
			if key not in instance.options:
				print("Unknown config option for rubberstamp " + type + ": " + key)
				continue

			# Convert the argument string to an int or float if the declared option has that type
			if isinstance(instance.options[key], int):
				value = int(value)
			elif isinstance(instance.options[key], float):
				value = float(value)

			instance.options[key] = value

		if verbose:
			print("Stamp \"" + type + "\" options parsed:")
			print(instance.options)
			print("Executing stamp")

		# Make the stamp fail by default
		result = False

		# Run the stamp code
		try:
			result = instance.run()
		except Exception:
			print(_("Internal error in rubberstamp:"))

			import traceback
			traceback.print_exc()
			continue

		if verbose: print("Stamp \"" + type + "\" returned: " + str(result))

		# Abort authentication if the stamp returned false
		if result is False:
			if verbose: print("Authentication aborted by rubber stamp")
			sys.exit(15)

	# This is outside the for loop, so we've run all the rules
	if verbose: print("All rubberstamps processed, authentication successful")

	# Exit with no errors
	sys.exit(0)
