#!/usr/bin/env python
# WidgetGen.py - regenerate the ScintillaWidgetCpp.cpp and ScintillaWidgetCpp.h files
# Check that API includes all gtkscintilla2 functions

import sys
import os
import getopt

scintillaDirectory = "../.."
scintillaScriptsDirectory = os.path.join(scintillaDirectory, "scripts")
sys.path.append(scintillaScriptsDirectory)
import Face
from FileGenerator import GenerateFile

def underscoreName(s):
	# Name conversion fixes to match gtkscintilla2
	irregular = ['WS', 'EOL', 'AutoC', 'KeyWords', 'BackSpace', 'UnIndents', 'RE', 'RGBA']
	for word in irregular:
		replacement = word[0] + word[1:].lower()
		s = s.replace(word, replacement)

	out = ""
	for c in s:
		if c.isupper():
			if out:
				out += "_"
			out += c.lower()
		else:
			out += c
	return out

def normalisedName(s, options, role=None):
	if options["qtStyle"]:
		if role == "get":
			s = s.replace("Get", "")
		return s[0].lower() + s[1:]
	else:
		return underscoreName(s)

typeAliases = {
	"position": "int",
	"colour": "int",
	"keymod": "int",
	"string": "const char *",
	"stringresult": "const char *",
	"cells": "const char *",
}

def cppAlias(s):
	if s in typeAliases:
		return typeAliases[s]
	else:
		return s

understoodTypes = ["", "void", "int", "bool", "position",
	"colour", "keymod", "string", "stringresult", "cells"]

def checkTypes(name, v):
	understandAllTypes = True
	if v["ReturnType"] not in understoodTypes:
		#~ print("Do not understand", v["ReturnType"], "for", name)
		understandAllTypes = False
	if v["Param1Type"] not in understoodTypes:
		#~ print("Do not understand", v["Param1Type"], "for", name)
		understandAllTypes = False
	if v["Param2Type"] not in understoodTypes:
		#~ print("Do not understand", v["Param2Type"], "for", name)
		understandAllTypes = False
	return understandAllTypes

def arguments(v, stringResult, options):
	ret = ""
	p1Type = cppAlias(v["Param1Type"])
	if p1Type == "int":
		p1Type = "sptr_t"
	if p1Type:
		ret = ret + p1Type + " " + normalisedName(v["Param1Name"], options)
	p2Type = cppAlias(v["Param2Type"])
	if p2Type == "int":
		p2Type = "sptr_t"
	if p2Type and not stringResult:
		if p1Type:
			ret = ret + ", "
		ret = ret + p2Type + " " + normalisedName(v["Param2Name"], options)
	return ret

def printPyFile(f, options):
	out = []
	for name in f.order:
		v = f.features[name]
		if v["Category"] != "Deprecated":
			feat = v["FeatureType"]
			if feat in ["val"]:
				out.append(name + "=" + v["Value"])
			if feat in ["evt"]:
				out.append("SCN_" + name.upper() + "=" + v["Value"])
			if feat in ["fun"]:
				out.append("SCI_" + name.upper() + "=" + v["Value"])
	return out

def printHFile(f, options):
	out = []
	for name in f.order:
		v = f.features[name]
		if v["Category"] != "Deprecated":
			feat = v["FeatureType"]
			if feat in ["fun", "get", "set"]:
				if checkTypes(name, v):
					constDeclarator = " const" if feat == "get" else ""
					returnType = cppAlias(v["ReturnType"])
					if returnType == "int":
						returnType = "sptr_t"
					stringResult = v["Param2Type"] == "stringresult"
					if stringResult:
						returnType = "QByteArray"
					out.append("\t" + returnType + " " + normalisedName(name, options, feat) + "(" +
						arguments(v, stringResult, options)+
						")" + constDeclarator + ";")
	return out

def methodNames(f, options):
	for name in f.order:
		v = f.features[name]
		if v["Category"] != "Deprecated":
			feat = v["FeatureType"]
			if feat in ["fun", "get", "set"]:
				if checkTypes(name, v):
					yield normalisedName(name, options)

def printCPPFile(f, options):
	out = []
	for name in f.order:
		v = f.features[name]
		if v["Category"] != "Deprecated":
			feat = v["FeatureType"]
			if feat in ["fun", "get", "set"]:
				if checkTypes(name, v):
					constDeclarator = " const" if feat == "get" else ""
					featureDefineName = "SCI_" + name.upper()
					returnType = cppAlias(v["ReturnType"])
					if returnType == "int":
						returnType = "sptr_t"
					stringResult = v["Param2Type"] == "stringresult"
					if stringResult:
						returnType = "QByteArray"
					returnStatement = ""
					if returnType != "void":
						returnStatement = "return "
					out.append(returnType + " ScintillaEdit::" + normalisedName(name, options, feat) + "(" +
						arguments(v, stringResult, options) +
						")" + constDeclarator + " {")
					returns = ""
					if stringResult:
						returns += "    " + returnStatement + "TextReturner(" + featureDefineName + ", "
						if "*" in cppAlias(v["Param1Type"]):
							returns += "(sptr_t)"
						if v["Param1Name"]:
							returns += normalisedName(v["Param1Name"], options)
						else:
							returns += "0"
						returns += ");"
					else:
						returns += "    " + returnStatement + "send(" + featureDefineName + ", "
						if "*" in cppAlias(v["Param1Type"]):
							returns += "(sptr_t)"
						if v["Param1Name"]:
							returns += normalisedName(v["Param1Name"], options)
						else:
							returns += "0"
						returns += ", "
						if "*" in cppAlias(v["Param2Type"]):
							returns += "(sptr_t)"
						if v["Param2Name"]:
							returns += normalisedName(v["Param2Name"], options)
						else:
							returns += "0"
						returns += ");"
					out.append(returns)
					out.append("}")
					out.append("")
	return out

def gtkNames():
	# The full path on my machine: should be altered for anyone else
	p = "C:/Users/Neil/Downloads/wingide-source-4.0.1-1/wingide-source-4.0.1-1/external/gtkscintilla2/gtkscintilla.c"
	with open(p) as f:
		for l in f.readlines():
			if "gtk_scintilla_" in l:
				name = l.split()[1][14:]
				if '(' in name:
					name = name.split('(')[0]
					yield name

def usage():
	print("WidgetGen.py [-c|--clean][-h|--help][-u|--underscore-names]")
	print("")
	print("Generate full APIs for ScintillaEdit class and ScintillaConstants.py.")
	print("")
	print("options:")
	print("")
	print("-c --clean remove all generated code from files")
	print("-h --help  display this text")
	print("-u --underscore-names  use method_names consistent with GTK+ standards")

def readInterface(cleanGenerated):
	f = Face.Face()
	if not cleanGenerated:
		f.ReadFromFile("../../include/Scintilla.iface")
	return f

def main(argv):
	# Using local path for gtkscintilla2 so don't default to checking
	checkGTK = False
	cleanGenerated = False
	qtStyleInterface = True
	# The --gtk-check option checks for full coverage of the gtkscintilla2 API but
	# depends on a particular directory so is not mentioned in --help.
	opts, args = getopt.getopt(argv, "hcgu", ["help", "clean", "gtk-check", "underscore-names"])
	for opt, arg in opts:
		if opt in ("-h", "--help"):
			usage()
			sys.exit()
		elif opt in ("-c", "--clean"):
			cleanGenerated = True
		elif opt in ("-g", "--gtk-check"):
			checkGTK = True
		elif opt in ("-u", "--underscore-names"):
			qtStyleInterface = False

	options = {"qtStyle": qtStyleInterface}
	f = readInterface(cleanGenerated)
	try:
		GenerateFile("ScintillaEdit.cpp.template", "ScintillaEdit.cpp",
			"/* ", True, printCPPFile(f, options))
		GenerateFile("ScintillaEdit.h.template", "ScintillaEdit.h",
			"/* ", True, printHFile(f, options))
		GenerateFile("../ScintillaEditPy/ScintillaConstants.py.template",
			"../ScintillaEditPy/ScintillaConstants.py",
			"# ", True, printPyFile(f, options))
		if checkGTK:
			names = set(methodNames(f))
			#~ print("\n".join(names))
			namesGtk = set(gtkNames())
			for name in namesGtk:
				if name not in names:
					print(name, "not found in Qt version")
			for name in names:
				if name not in namesGtk:
					print(name, "not found in GTK+ version")
	except:
		raise

	if cleanGenerated:
		for file in ["ScintillaEdit.cpp", "ScintillaEdit.h", "../ScintillaEditPy/ScintillaConstants.py"]:
			try:
				os.remove(file)
			except OSError:
				pass

if __name__ == "__main__":
	main(sys.argv[1:])
