#!/usr/bin/env python3
# Face.py - module for reading and parsing Scintilla.iface file
# Implemented 2000 by Neil Hodgson neilh@scintilla.org
# Released to the public domain.
# Requires Python 3.6 or later

def sanitiseLine(line):
	line = line.rstrip('\n')
	if "##" in line:
		line = line[:line.find("##")]
	line = line.strip()
	return line

def decodeFunction(featureVal):
	retType, rest = featureVal.split(" ", 1)
	nameIdent, params = rest.split("(")
	name, value = nameIdent.split("=")
	params, rest = params.split(")")
	param1, param2 = params.split(",")
	return retType, name, value, param1, param2

def decodeEvent(featureVal):
	retType, rest = featureVal.split(" ", 1)
	nameIdent, params = rest.split("(")
	name, value = nameIdent.split("=")
	return retType, name, value

def decodeParam(p):
	param = p.strip()
	type = ""
	name = ""
	value = ""
	if " " in param:
		type, nv = param.split(" ")
		if "=" in nv:
			name, value = nv.split("=")
		else:
			name = nv
	return type, name, value

def IsEnumeration(t):
	return t[:1].isupper()

def PascalCase(s):
	capitalized = s.title()
	# Remove '_' except between digits
	pascalCase = ""
	characterPrevious = " "
	# Loop until penultimate character
	for i in range(len(capitalized)-1):
		character = capitalized[i]
		characterNext = capitalized[i+1]
		if character != "_" or (
			characterPrevious.isnumeric() and characterNext.isnumeric()):
			pascalCase += character
		characterPrevious = character
	# Add last character - not between digits so no special treatment
	pascalCase += capitalized[-1]
	return pascalCase

class Face:

	def __init__(self):
		self.order = []
		self.features = {}
		self.values = {}
		self.events = {}
		self.aliases = {}

	def ReadFromFile(self, name, pickUpPixels=False):
		currentCategory = ""
		currentComment = []
		currentCommentFinished = 0
		file = open(name)
		for line in file:
			if pickUpPixels:
				stripped = line.strip()
				if stripped.startswith("## "):
					self.ApplyPixelComment(stripped[3:])
					continue
			line = sanitiseLine(line)
			if line:
				if line[0] == "#":
					if line[1] == " ":
						if currentCommentFinished:
							currentComment = []
							currentCommentFinished = 0
						currentComment.append(line[2:])
				else:
					currentCommentFinished = 1
					featureType, featureVal = line.split(" ", 1)
					if featureType in ["fun", "get", "set"]:
						try:
							retType, name, value, param1, param2 = decodeFunction(featureVal)
						except ValueError:
							print("Failed to decode %s" % line)
							raise
						p1 = decodeParam(param1)
						p2 = decodeParam(param2)
						self.features[name] = {
							"FeatureType": featureType,
							"ReturnType": retType,
							"Value": value,
							"Param1Type": p1[0], "Param1Name": p1[1], "Param1Value": p1[2],
							"Param2Type": p2[0], "Param2Name": p2[1], "Param2Value": p2[2],
							"Category": currentCategory, "Comment": currentComment
						}
						if value in self.values:
							raise Exception("Duplicate value " + value + " " + name)
						self.values[value] = 1
						self.order.append(name)
						currentComment = []
					elif featureType == "evt":
						retType, name, value = decodeEvent(featureVal)
						self.features[name] = {
							"FeatureType": featureType,
							"ReturnType": retType,
							"Value": value,
							"Category": currentCategory, "Comment": currentComment
						}
						if value in self.events:
							raise Exception("Duplicate event " + value + " " + name)
						self.events[value] = 1
						self.order.append(name)
					elif featureType == "cat":
						currentCategory = featureVal
					elif featureType == "val":
						try:
							name, value = featureVal.split("=", 1)
						except ValueError:
							print("Failure %s" % featureVal)
							raise Exception()
						self.features[name] = {
							"FeatureType": featureType,
							"Category": currentCategory,
							"Value": value }
						self.order.append(name)
					elif featureType == "enu" or featureType == "lex":
						name, value = featureVal.split("=", 1)
						self.features[name] = {
							"FeatureType": featureType,
							"Category": currentCategory,
							"Value": value,
							"Comment": currentComment }
						self.order.append(name)
						currentComment = []
					elif featureType == "ali":
						# Enumeration alias
						name, value = featureVal.split("=", 1)
						self.aliases[name] = value
						currentComment = []
		file.close()

	def ApplyPixelComment(self, featureVal):
		# A '## <fun|get|set ... pixels ...>' comment below a message re-types
		# the named feature's pixels slots, so a caller passing pickUpPixels=True
		# sees them. Other consumers ignore it, since sanitiseLine strips '##'.
		featureType, _, rest = featureVal.partition(" ")
		if featureType not in ("fun", "get", "set") or not rest:
			return
		try:
			retType, name, value, param1, param2 = decodeFunction(rest)
		except ValueError:
			return
		feature = self.features.get(name)
		if feature is None:
			return
		if retType == "pixels":
			feature["ReturnType"] = "pixels"
		if decodeParam(param1)[0] == "pixels":
			feature["Param1Type"] = "pixels"
		if decodeParam(param2)[0] == "pixels":
			feature["Param2Type"] = "pixels"
