#!/usr/bin/env python
"""
  uhub - A tiny ADC p2p connection hub
  Copyright (C) 2007-2013, Jan Vidar Krey
"""

from xml.dom import minidom, Node
from datetime import datetime
import argparse

class OptionParseError(Exception):
	pass

class Option(object):
	def _get(self, node, name):
		self.__dict__[name] = None
		if (node.getElementsByTagName(name)):
			self.__dict__[name] = node.getElementsByTagName(name)[0].firstChild.nodeValue

	def _attr(self, node, name, required = False):
		try:
			return node.attributes[name].value
		except Exception:
			pass
		if (required):
			raise OptionParseError("Option %s is required but not found!" % name)
		return None

	def __init__(self, node):
		self.otype = self._attr(node, 'type', True)

		# Verify that the type is known
		if not self.otype in ["int", "boolean", "string", "message", "file"]:
			raise OptionParseError("Option %s has unknown type" % self.name)

		self.name = self._attr(node, 'name', True)
		self.default = self._attr(node, 'default', True)
		self.advanced = self._attr(node, 'advanced', False)
		self.is_string = self.otype in ["string", "message", "file"]

		self._get(node, "short");
		self._get(node, "description");
		self._get(node, "syntax");
		self._get(node, "since");
		self._get(node, "example");

		check = node.getElementsByTagName("check")
		if (check):
			check = node.getElementsByTagName("check")[0]
			self.check_min = self._attr(check, 'min', False)
			self.check_max = self._attr(check, 'max', False)
			self.check_regexp = self._attr(check, 'regexp', False)
		else:
			self.check_min = None
			self.check_max = None
			self.check_regexp = None


	def c_type(self):
		if self.otype == "boolean":
			return "int"
		elif self.is_string:
			return "char*"
		else:
			return self.otype

	def sql_type(self):
		if self.otype == "int":
			return "integer"
		return self.otype

	def c_comment(self):
		comment = ""
		if (self.otype == "message"):
			comment = self.formatted_default()
		elif len(self.short):
			comment = "%s (default: %s)" % (self.short, self.formatted_default())
		return comment

	def formatted_default(self):
		if self.is_string:
			return "\"%s\"" % self.default
		return self.default

class SourceGenerator(object):
	def __init__(self, filename, cppStyle = True):
		print ("Generating %s..." % filename)
		self.f = open(filename, 'w');

	def write_header(self, Comment = True):
		if Comment:
			s =  "/*\n * uhub - A tiny ADC p2p connection hub\n"
			s += " * Copyright (C) 2007-%s, Jan Vidar Krey\n *\n" % datetime.now().strftime("%Y")
			s += " * THIS FILE IS AUTOGENERATED - DO NOT MODIFY\n"
			s += " * Created %s, by config.py\n */\n\n" % datetime.now().strftime("%Y-%m-%d %H:%M")
			self.f.write(s)

class CHeaderGenerator(SourceGenerator):
	def __init__(self, filename):
		super(CHeaderGenerator, self).__init__(filename)

	def _write_declaration(self, option):
		comment = ' ' * (32 - len(option.name)) + "/*<<< %s */" % option.c_comment()
		ptype = option.c_type() + (5 - len(option.c_type())) * ' '
		self.f.write("\t%(type)s %(name)s;%(comment)s\n" % {
				"type": ptype,
				"name": option.name,
				"comment": comment})

	def write(self, options):
		self.write_header()
		self.f.write("struct hub_config\n{\n")
		for option in options:
			self._write_declaration(option)
		self.f.write("};\n\n")

class CSourceGenerator(SourceGenerator):
	def __init__(self, filename):
		super(CSourceGenerator, self).__init__(filename)

	def _write_default_impl(self, option):
		s = "\tconfig->%s = " % option.name
		if option.is_string:
			s += "hub_strdup(%s);\n" % option.formatted_default()
		else:
			s += option.formatted_default() + ";\n"
		self.f.write(s)

	def _write_apply_impl(self, option):
		s = "\tif (!strcmp(key, \"%s\"))\n\t{\n" % option.name
		if option.otype == "int":
			s_min = "0"
			s_max = "0"
			if (option.check_min):
				s += "\t\tmin = %s;\n" % option.check_min
				s_min = "&min"
			if (option.check_max):
				s += "\t\tmax = %s;\n" % option.check_max
				s_max = "&max"
			s+= "\t\tif (!apply_integer(key, data, &config->%s, %s, %s))\n" % (option.name, s_min, s_max)
		elif option.otype == "boolean":
			s += "\t\tif (!apply_boolean(key, data, &config->%s))\n" % option.name
		elif option.is_string:
			s += "\t\tif (!apply_string(key, data, &config->%s, (char*) \"\"))\n" % option.name
		s += "\t\t{\n\t\t\tLOG_ERROR(\"Configuration parse error on line %d\", line_count);\n\t\t\treturn -1;\n\t\t}\n\t\treturn 0;\n\t}\n\n"
		self.f.write(s)

	def _write_free_impl(self, option):
		if option.is_string:
			self.f.write("\thub_free(config->%s);\n\n" % option.name)

	def _write_dump_impl(self, option):
		s = ""
		fmt = "%s"
		val = "config->%s" % option.name
		test = "config->%s != %s" % (option.name, option.default)

		if (option.otype == "int"):
			fmt = "%d"
		elif (option.otype == "boolean"):
			val = "config->%s ? \"yes\" : \"no\"" % option.name
		elif (option.is_string):
			fmt = "\\\"%s\\\"";
			test = "strcmp(config->%s, %s) != 0" % (option.name, option.formatted_default())

		s += "\tif (!ignore_defaults || %s)\n" % test;
		s += "\t\tfprintf(stdout, \"%s = %s\\n\", %s);\n\n" % (option.name, fmt, val)
		self.f.write(s)

	def write(self, options):
		self.write_header()
		self.f.write("void config_defaults(struct hub_config* config)\n{\n")
		for option in options:
			self._write_default_impl(option)
		self.f.write("}\n\n")
		self.f.write("static int apply_config(struct hub_config* config, char* key, char* data, int line_count)\n{\n\tint max = 0;\n\tint min = 0;\n\n")
		for option in options:
			self._write_apply_impl(option)
		self.f.write("\t/* Still here -- unknown directive */\n\tLOG_ERROR(\"Unknown configuration directive: '%s'\", key);\n\treturn -1;\n}\n\n")
		self.f.write("void free_config(struct hub_config* config)\n{\n")
		for option in options:
			self._write_free_impl(option)
		self.f.write("}\n\n")
		self.f.write("void dump_config(struct hub_config* config, int ignore_defaults)\n{\n")
		for option in options:
			self._write_dump_impl(option)
		self.f.write("}\n\n")

class SqlWebsiteDocsGenerator(SourceGenerator):
	def __init__(self, filename, sqlite_support = False):
		self.sqlite_support = sqlite_support
		super(SqlWebsiteDocsGenerator, self).__init__(filename)

	def _sql_escape(self, s):
		if self.sqlite_support:
			return s.replace("\"", "\"\"")
		return s.replace("\"", "\\\"")
		

	def _write_or_null(self, s):
		if (not s or len(s) == 0):
			return "NULL"
		return "\"%s\"" % self._sql_escape(s)

	def write(self, options):
		self.write_header(False)
		table = "uhub_config"

		s = ""
		if not self.sqlite_support:
			s += "START TRANSACTION;\n\nDROP TABLE %(table)s IF EXISTS;" % { "table": table }
		s += "\n\nCREATE TABLE %(table)s (\n\tname VARCHAR(32) UNIQUE NOT NULL,\n\tdefaultValue TINYTEXT NOT NULL,\n\tdescription LONGTEXT NOT NULL,\n\ttype TINYTEXT NOT NULL,\n\tadvanced BOOLEAN,\n\texample LONGTEXT,\n\tsince TINYTEXT\n);\n\n" % { "table": table }
		self.f.write(s)

		for option in options:
			s = "INSERT INTO %(table)s VALUES(\"%(name)s\", \"%(default)s\", \"%(description)s\", \"%(type)s\", %(example)s, %(since)s, %(advanced)s);\n" % {
					"table": table,
					"name": self._sql_escape(option.name),
					"default": self._sql_escape(option.formatted_default()),
					"description": self._sql_escape(option.description),
					"type": option.sql_type(),
					"example": self._write_or_null(option.example),
					"since": self._write_or_null(option.since),
					"advanced": self._write_or_null(option.example),
				}
			self.f.write(s)

		if not self.sqlite_support:
			self.f.write("\n\nCOMMIT;\n\n")

if __name__ == "__main__":
	# parser = argparse.ArgumentParser(description = "Configuration file parser and source generator")
	# parser.add_argument("--in", nargs=1, type=argparse.FileType('r'), default="config.xml", help="Input file (config.xml)", required = True)
	# parser.add_argument("--c-decl", nargs=1, type=argparse.FileType('w'), default="gen_config.h", help="Output file for C declarations (gen_config.h)")
	# parser.add_argument("--c-impl", nargs=1, type=argparse.FileType('w'), default="gen_config.c", help="Output file for C implementation (gen_config.c)")
	# parser.add_argument("--doc-sql", nargs=1, type=argparse.FileType('w'), help="Output file for SQL documentation")
	# args = parser.parse_args()

	xmldoc = minidom.parse("./config.xml")
	opt_tags = xmldoc.getElementsByTagName('option')
	options = []
	for option in opt_tags:
		opt = Option(option)
		options.append(opt)

	header = CHeaderGenerator("./gen_config.h");
	header.write(options);

	source = CSourceGenerator("./gen_config.c");
	source.write(options);

	#sql = SqlWebsiteDocsGenerator("./gen_config.sql", True);
	#sql.write(options);


