#!/usr/bin/env python3

import os as _os
import sys as _sys
import json as _json
import argparse as _argparse


def convey(
	x,
	fs
):
	y = x
	for f in fs:
		y = f(y)
	return y
	

def file_read(
	path
):
	handle = open(path, "r")
	content = handle.read()
	handle.close()
	return content
	

class interface_option(object):
	def is_filled(self):
		raise NotImplementedError()
	def cull(self):
		raise NotImplementedError()
	def propagate(self, function):
		raise NotImplementedError()
	

class class_option_empty(interface_option):
	def __init__(self):
		pass
	def is_filled(self):
		return False
	def cull(self):
		raise ValueError("cull from empty")
	def propagate(self, function):
		return class_option_empty()
	

class class_option_filled(interface_option):
	def __init__(self, value):
		self.value = value
	def is_filled(self):
		return True
	def cull(self):
		return self.value
	def propagate(self, function):
		return function(self.value)
	

def generate_defaults(
	schema_node
):
	if ("anyOf" in schema_node):
		## todo: o'rly?
		return generate_defaults(schema_node["anyOf"][0])
	else:
		if (not ("type" in schema_node)):
			raise ValueError(":?")
		else:
			if (not (schema_node["type"] == "object")):
				if (not ("default" in schema_node)):
					return class_option_empty()
				else:
					return class_option_filled(schema_node["default"])
			else:
				result = {}
				for (key, value, ) in schema_node["properties"].items():
					sub_result = generate_defaults(value)
					if (not sub_result.is_filled()):
						pass
					else:
						result[key] = sub_result.cull()
				return class_option_filled(result)
	

def generate_overrides(
	schema_node
):
	if ("anyOf" in schema_node):
		## todo: o'rly?
		return generate_overrides(schema_node["anyOf"][0])
	else:
		if (not ("type" in schema_node)):
			raise ValueError(":?")
		else:
			if (not (schema_node["type"] == "object")):
				if ("default" in schema_node):
					return class_option_empty()
				else:
					if ("enum" in schema_node):
						return class_option_filled(schema_node["enum"][0])
					else:
						if (schema_node.get("nullable", False)):
							return class_option_filled(None)
						else:
							if (schema_node["type"] == "boolean"):
								return class_option_filled(False)
							elif (schema_node["type"] == "integer"):
								return class_option_filled(0)
							elif (schema_node["type"] == "number"):
								return class_option_filled(0)
							elif (schema_node["type"] == "string"):
								return class_option_filled("")
							else:
								raise ValueError("unhandled type: %s" % schema_node["type"])
			else:
				result = {}
				for (key, value, ) in schema_node["properties"].items():
					sub_result = generate_overrides(value)
					if (not sub_result.is_filled()):
						pass
					else:
						result[key] = sub_result.cull()
				return (
					class_option_empty()
					if (len(result) <= 0) else
					class_option_filled(result)
				)
	

def role_name_derive(
	role_name
):
	return role_name.replace("-", "_")
	

def main(
):
	## args
	argument_parser = _argparse.ArgumentParser()
	argument_parser.add_argument(
		"action",
		type = str,
		choices = [
			"defaults",
			"overrides",
		],
		metavar = "<action>",
	)
	argument_parser.add_argument(
		"role",
		type = str,
		metavar = "<role>",
	)
	args = argument_parser.parse_args()
	
	## exec
	cfg_schema = convey(
		args.role,
		[
			lambda x: _os.path.join("roles", x, "cfg.schema.json"),
			file_read,
			_json.loads,
		]
	)
	if args.action == "defaults":
		raw = generate_defaults(cfg_schema)
		key = ("cfg_%s_defaults" % (role_name_derive(args.role)))
		result = {key: (raw.cull() if raw.is_filled() else {})}
		_sys.stdout.write(_json.dumps(result, indent = "\t") + "\n")
	elif args.action == "overrides":
		raw = generate_overrides(cfg_schema)
		key = ("cfg_%s_overrides" % (role_name_derive(args.role)))
		result = {key: (raw.cull() if raw.is_filled() else {})}
		_sys.stdout.write(_json.dumps(result, indent = "\t") + "\n")
	else:
		raise ValueError("invalid action: %s" % args.action)
	

main()
