#!/usr/bin/python
#This greps the source for SET_NAMESPACE, REGISTER_CLASS_NAME2,
#sinit definition and the following ->set{Getter,Setter,Method}ByQName calls

#It greps the documentation for all properties and methods
#and distinquishes read-only from write-only and dual-access properties

import sys
import re
from subprocess import *
import os
import pickle

#These regexp are used for finding important pieces in the source
sinit = re.compile('.*void\s*(\w*)::sinit\(.*')
rconstructor = re.compile('[^/]*->setConstructor\(Class<IFunction>::getFunction\([^)]*\)\).*')
#looks like builtin->setVariableByQName("Object","namespace",Class<ASObject>::getClass());
rclass = re.compile('.*builtin->setVariableByQName\("([^"]*)","([^"]*)",Class<([^>]*)>::getClass\(\)\);.*')
#looks like builtin->setVariableByQName("BitmapData","flash.display",Class<ASObject>::getClass(QName("BitmapData","flash.display")));
rstupclass = re.compile('.*builtin->setVariableByQName\("([^"]*)","([^"]*)",Class<([^>]*)>::getClass\(QName.*')
#the line may start with anything but a comment character
rget = re.compile('[^/]*->setGetterByQName\("([^"]*)".*')
rset = re.compile('[^/]*->setSetterByQName\("([^"]*)".*')
rmet = re.compile('[^/]*->setMethodByQName\("([^"]*)".*')
#Constant names do not contain lower-case letters
rconst = re.compile('[^/]*->setVariableByQName\("([^"a-z]*)","",.*');

def getFullname(cls,name):
	if cls != "":
		return cls + "." + name
	else:
		return name

if not os.path.exists("pygil"):
	print("This script is stupid! Please got to the scripts directory")
	print("and run at from there by ./pygil")
	exit(1)

classes = set([])
getters = set([])
setters = set([])
methods = set([])
const = set([])
stubclasses = set([])
warnNotRegistered = set([])
internalToExternal = {}
curClass = ""

#1. Step: parse scripting/abc.cpp to map the AS3 names to our class names
f = open('../src/scripting/abc.cpp','r')
for line in f:
	m = rclass.match(line)
        if m:
		internalToExternal[m.group(3)] = getFullname(m.group(2),m.group(1))
	m = rstupclass.match(line)
	#Object is the only real implementation of ASObject
	if m and m.group(1) != "Object":
		stubclasses.add(m.group(1))
f.close()

#print internalToExternal

#2. Step: parse the rest of the source files for the implementation of 
p1 = Popen(["find", "..", "-name",'*.cpp'], stdout=PIPE)
#return p1.communicate()[0]
p2 = Popen(["xargs","grep","-h",
	    "-e","sinit",
	    "-e","set[a-zA-Z]*ByQName",
	    "-e","setConstructor"], stdin=p1.stdout, stdout=PIPE)
p1.stdout.close() 


for line in p2.communicate()[0].split("\n"):
	m = sinit.match(line)
	if m:
		curClass = m.group(1)
		if curClass not in internalToExternal:
			warnNotRegistered.add(curClass)
		else:
			curClass = internalToExternal[curClass]
		curScope = curClass
		classes.add(curClass)
		continue
	m = rconstructor.match(line)
	if m:
		#the constructor is named after the class
		methods.add((curClass,curClass[curClass.rfind('.')+1:]))
		continue
	m = rget.match(line)
	if m:
		getters.add((curClass, m.group(1)))
		continue
	m = rset.match(line)
	if m:
		setters.add((curClass, m.group(1)))
		continue
	m = rmet.match(line)
	if m:
		methods.add((curClass, m.group(1)))
		continue
	m = rconst.match(line)
	if m:
		const.add((curClass,m.group(1)))
		continue
	
#print getters
#print setters

#3. Step: parse documenation for classes, properties and methods
try:
	dbFile = open("langref.db", 'r')
	(version, dclasses, dproperties, dmethods) = pickle.load( dbFile )
	dbFile.close()
except IOError:
	print "Error opening langref.db. Have you run langref_parser in this directory?"
	exit(1)

dconst = set([])
dgetters = set([])
dsetters = set([])
mclasses = set([])
for (curClass,name,isConst,isStatic,isReadOnly,isWriteOnly,isOverride) in dproperties:
	if isConst:
		dconst.add((curClass,name))
		continue
	if not isReadOnly:
		dsetters.add((curClass,name))
	if not isWriteOnly:
		dgetters.add((curClass,name))

for cls in dclasses:
	if cls not in classes:
		mclasses.add(cls)

mgetters = []
msetters = []
mmethods = []
pmclasses = set([])
missing = []
fclasses = set([])

for (cls,name) in dgetters:
	if cls in mclasses:
		continue #do not list member of missing classes
	fullName = getFullname(cls,name)
	if (cls,name) not in getters:
		if (cls,name) in dsetters and (cls,name) not in setters:
			missing.append(fullName + " (getter/setter)")
		else:
			missing.append(fullName + " (getter)")
		pmclasses.add(cls)
		#mgetters.append(i)
		#print "\t" +i

for (cls,name) in dsetters:
	if cls in mclasses:
		continue #do not list member of missing classes
	fullName = getFullname(cls,name)
	if (cls,name) not in setters:
		if (cls,name) in dgetters and (cls,name) not in getters:
			pass #handled abovec
		else:
			missing.append(fullName + " (setter)")
		pmclasses.add(cls)
		#msetters.append(i)
		#print "\t" + i

for (cls,name) in dmethods:
	if cls in mclasses:
		continue #do not list member of missing classes
	fullName = getFullname(cls,name)
	if (cls,name) not in methods:
		missing.append(fullName + " (method)")
		#mmethods.append(i)
		#print "\t" + i
		pmclasses.add(cls)

for (cls,name) in dconst:
	if cls in mclasses:
		continue
	fullName = getFullname(cls,name)
	if (cls,name) not in const:
		missing.append(fullName + " (constant)")
		pmclasses.add(cls)

#Fully implemented are classes which are in the reference
#docs and not in missing classes or partly missing classes
for i in dclasses:
	if i not in mclasses and i not in pmclasses:
		fclasses.add(i)

print "Fully implemented classes:"
for i in sorted(fclasses):
	print "\t" + i

print "\nStub classes (currently implemented as ASObject):"
for i in sorted(stubclasses):
	print "\t" + i

print "\nFully Missing classes:"
for i in sorted(mclasses):
	print "\t" + i

print "\nMissing getters/setters/methods (in partially implemented classes):"
for i in sorted(missing):
	print "\t" + i

print ""

#If a subclass overrides a method of its base, then it has an entry
# (flash.*.subclass,overridenMethod)
for (cls,name) in sorted(getters):
	if (cls,name) not in dgetters:
		print "Warning: Implemented non-spec getter", getFullname(cls,name) 
for (cls,name) in sorted(setters):
	if (cls,name) not in dsetters:
		print "Warning: Implemented non-spec setter", getFullname(cls,name) 
for (cls,name) in sorted(methods):
	if (cls,name) not in dmethods:
		print "Warning: Implemented non-spec method", getFullname(cls,name) 
print ""
for i in warnNotRegistered:
	print "Warning: " + i + " is not registered in scripting/abc.cpp"


#print mmethods
#print mclasses
#print pmclasses
print ""
print "Number of fully implemented classes:                ", len(fclasses)
print "Number of implemented getters/setters/methods/const:", len(setters),len(getters),len(methods),len(const)
print "Total number of implemented                        :", len(setters)+len(getters)+len(methods)+len(const)
print "Number of missing classes (of which are stubs):     ", len(mclasses), len(stubclasses)
print "Number of missing getters/setters/methods/consts:   ", (len(dsetters)-len(setters)), len(dgetters)-len(getters), len(dmethods)-len(methods), len(dconst)-len(const)
print "Total number of missing:                            ", (len(dsetters)+len(dgetters)+len(dmethods)+len(dconst)) - (len(setters)+len(getters)+len(methods)-len(const))
print "Overall percentage completed:                       ", float(len(setters)+len(getters)+len(methods)+len(const))/float(len(dsetters)+len(dgetters)+len(dmethods)+len(dconst))*100
