import os
import textwrap
from xml.etree import ElementTree
from fontTools.ttLib import TTFont, newTable
from fontTools.misc.psCharStrings import T2CharString
from fontTools.ttLib.tables.otTables import GSUB,\
    ScriptList, ScriptRecord, Script, DefaultLangSys,\
    FeatureList, FeatureRecord, Feature,\
    LookupList, Lookup, AlternateSubst, SingleSubst

# paths
directory = os.path.dirname(__file__)
shellSourcePath = os.path.join(directory, "gsubtest-shell.ttx")
shellTempPath = os.path.join(directory, "gsubtest-shell.otf")
featureList = os.path.join(directory, "gsubtest-features.txt")
javascriptData = os.path.join(directory, "gsubtest-features.js")
outputPath = os.path.join(os.path.dirname(directory), "gsubtest-lookup%d")

baseCodepoint = 0xe000

# -------
# Features
# -------

f = open(featureList, "rb")
text = f.read()
f.close()
mapping = []
for line in text.splitlines():
    line = line.strip()
    if not line:
        continue
    if line.startswith("#"):
        continue
    # parse
    values = line.split("\t")
    tag = values.pop(0)
    mapping.append(tag);

# --------
# Outlines
# --------

def addGlyphToCFF(glyphName=None, program=None, private=None, globalSubrs=None, charStringsIndex=None, topDict=None, charStrings=None):
    charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs)
    charStringsIndex.append(charString)
    glyphID = len(topDict.charset)
    charStrings.charStrings[glyphName] = glyphID
    topDict.charset.append(glyphName)

def makeLookup1():
    # make a variation of the shell TTX data
    f = open(shellSourcePath)
    ttxData = f.read()
    f.close()
    ttxData = ttxData.replace("__familyName__", "gsubtest-lookup1")
    tempShellSourcePath = shellSourcePath + ".temp"
    f = open(tempShellSourcePath, "wb")
    f.write(ttxData)
    f.close()

    # compile the shell
    shell = TTFont(sfntVersion="OTTO")
    shell.importXML(tempShellSourcePath)
    shell.save(shellTempPath)
    os.remove(tempShellSourcePath)

    # load the shell
    shell = TTFont(shellTempPath)

    # grab the PASS and FAIL data
    hmtx = shell["hmtx"]
    glyphSet = shell.getGlyphSet()

    failGlyph = glyphSet["F"]
    failGlyph.decompile()
    failGlyphProgram = list(failGlyph.program)
    failGlyphMetrics = hmtx["F"]

    passGlyph = glyphSet["P"]
    passGlyph.decompile()
    passGlyphProgram = list(passGlyph.program)
    passGlyphMetrics = hmtx["P"]

    # grab some tables
    hmtx = shell["hmtx"]
    cmap = shell["cmap"]

    # start the glyph order
    existingGlyphs = [".notdef", "space", "F", "P"]
    glyphOrder = list(existingGlyphs)

    # start the CFF
    cff = shell["CFF "].cff
    globalSubrs = cff.GlobalSubrs
    topDict = cff.topDictIndex[0]
    topDict.charset = existingGlyphs
    private = topDict.Private
    charStrings = topDict.CharStrings
    charStringsIndex = charStrings.charStringsIndex

    features = sorted(mapping)

    # build the outline, hmtx and cmap data
    cp = baseCodepoint
    for index, tag in enumerate(features):

    	# tag.pass
    	glyphName = "%s.pass" % tag
    	glyphOrder.append(glyphName)
    	addGlyphToCFF(
    		glyphName=glyphName,
    		program=passGlyphProgram,
    		private=private,
    		globalSubrs=globalSubrs,
    		charStringsIndex=charStringsIndex,
    		topDict=topDict,
            charStrings=charStrings
    	)
    	hmtx[glyphName] = passGlyphMetrics

    	for table in cmap.tables:
    		if table.format == 4:
    			table.cmap[cp] = glyphName
    		else:
    			raise NotImplementedError("Unsupported cmap table format: %d" % table.format)
    	cp += 1

    	# tag.fail
    	glyphName = "%s.fail" % tag
    	glyphOrder.append(glyphName)
    	addGlyphToCFF(
    		glyphName=glyphName,
    		program=failGlyphProgram,
    		private=private,
    		globalSubrs=globalSubrs,
    		charStringsIndex=charStringsIndex,
    		topDict=topDict,
            charStrings=charStrings
    	)
    	hmtx[glyphName] = failGlyphMetrics

    	for table in cmap.tables:
    		if table.format == 4:
    			table.cmap[cp] = glyphName
    		else:
    			raise NotImplementedError("Unsupported cmap table format: %d" % table.format)

        # bump this up so that the sequence is the same as the lookup 3 font
    	cp += 3

    # set the glyph order
    shell.setGlyphOrder(glyphOrder)

    # start the GSUB
    shell["GSUB"] = newTable("GSUB")
    gsub = shell["GSUB"].table = GSUB()
    gsub.Version = 1.0

    # make a list of all the features we will make
    featureCount = len(features)

    # set up the script list
    scriptList = gsub.ScriptList = ScriptList()
    scriptList.ScriptCount = 1
    scriptList.ScriptRecord = []
    scriptRecord = ScriptRecord()
    scriptList.ScriptRecord.append(scriptRecord)
    scriptRecord.ScriptTag = "DFLT"
    script = scriptRecord.Script = Script()
    defaultLangSys = script.DefaultLangSys = DefaultLangSys()
    defaultLangSys.FeatureCount = featureCount
    defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount)
    defaultLangSys.ReqFeatureIndex = 65535
    defaultLangSys.LookupOrder = None
    script.LangSysCount = 0
    script.LangSysRecord = []

    # set up the feature list
    featureList = gsub.FeatureList = FeatureList()
    featureList.FeatureCount = featureCount
    featureList.FeatureRecord = []
    for index, tag in enumerate(features):
        # feature record
        featureRecord = FeatureRecord()
        featureRecord.FeatureTag = tag
        feature = featureRecord.Feature = Feature()
        featureList.FeatureRecord.append(featureRecord)
        # feature
        feature.FeatureParams = None
        feature.LookupCount = 1
        feature.LookupListIndex = [index]

    # write the lookups
    lookupList = gsub.LookupList = LookupList()
    lookupList.LookupCount = featureCount
    lookupList.Lookup = []
    for tag in features:
        # lookup
        lookup = Lookup()
        lookup.LookupType = 1
        lookup.LookupFlag = 0
        lookup.SubTableCount = 1
        lookup.SubTable = []
        lookupList.Lookup.append(lookup)
        # subtable
        subtable = SingleSubst()
        subtable.Format = 2
        subtable.LookupType = 1
        subtable.mapping = {
            "%s.pass" % tag : "%s.fail" % tag,
            "%s.fail" % tag : "%s.pass" % tag,
        }
        lookup.SubTable.append(subtable)

    path = outputPath % 1 + ".otf"
    if os.path.exists(path):
    	os.remove(path)
    shell.save(path)

    # get rid of the shell
    if os.path.exists(shellTempPath):
        os.remove(shellTempPath)

def makeLookup3():
    # make a variation of the shell TTX data
    f = open(shellSourcePath)
    ttxData = f.read()
    f.close()
    ttxData = ttxData.replace("__familyName__", "gsubtest-lookup3")
    tempShellSourcePath = shellSourcePath + ".temp"
    f = open(tempShellSourcePath, "wb")
    f.write(ttxData)
    f.close()

    # compile the shell
    shell = TTFont(sfntVersion="OTTO")
    shell.importXML(tempShellSourcePath)
    shell.save(shellTempPath)
    os.remove(tempShellSourcePath)

    # load the shell
    shell = TTFont(shellTempPath)

    # grab the PASS and FAIL data
    hmtx = shell["hmtx"]
    glyphSet = shell.getGlyphSet()

    failGlyph = glyphSet["F"]
    failGlyph.decompile()
    failGlyphProgram = list(failGlyph.program)
    failGlyphMetrics = hmtx["F"]

    passGlyph = glyphSet["P"]
    passGlyph.decompile()
    passGlyphProgram = list(passGlyph.program)
    passGlyphMetrics = hmtx["P"]

    # grab some tables
    hmtx = shell["hmtx"]
    cmap = shell["cmap"]

    # start the glyph order
    existingGlyphs = [".notdef", "space", "F", "P"]
    glyphOrder = list(existingGlyphs)

    # start the CFF
    cff = shell["CFF "].cff
    globalSubrs = cff.GlobalSubrs
    topDict = cff.topDictIndex[0]
    topDict.charset = existingGlyphs
    private = topDict.Private
    charStrings = topDict.CharStrings
    charStringsIndex = charStrings.charStringsIndex

    features = sorted(mapping)

    # build the outline, hmtx and cmap data
    cp = baseCodepoint
    for index, tag in enumerate(features):

    	# tag.pass
    	glyphName = "%s.pass" % tag
    	glyphOrder.append(glyphName)
    	addGlyphToCFF(
    		glyphName=glyphName,
    		program=passGlyphProgram,
    		private=private,
    		globalSubrs=globalSubrs,
    		charStringsIndex=charStringsIndex,
    		topDict=topDict,
            charStrings=charStrings
    	)
    	hmtx[glyphName] = passGlyphMetrics

    	# tag.fail
    	glyphName = "%s.fail" % tag
    	glyphOrder.append(glyphName)
    	addGlyphToCFF(
    		glyphName=glyphName,
    		program=failGlyphProgram,
    		private=private,
    		globalSubrs=globalSubrs,
    		charStringsIndex=charStringsIndex,
    		topDict=topDict,
            charStrings=charStrings
    	)
    	hmtx[glyphName] = failGlyphMetrics

    	# tag.default
    	glyphName = "%s.default" % tag
    	glyphOrder.append(glyphName)
    	addGlyphToCFF(
    		glyphName=glyphName,
    		program=passGlyphProgram,
    		private=private,
    		globalSubrs=globalSubrs,
    		charStringsIndex=charStringsIndex,
    		topDict=topDict,
            charStrings=charStrings
    	)
    	hmtx[glyphName] = passGlyphMetrics

    	for table in cmap.tables:
    		if table.format == 4:
    			table.cmap[cp] = glyphName
    		else:
    			raise NotImplementedError("Unsupported cmap table format: %d" % table.format)
    	cp += 1

    	# tag.alt1,2,3
    	for i in range(1,4):
    		glyphName = "%s.alt%d" % (tag, i)
    		glyphOrder.append(glyphName)
    		addGlyphToCFF(
    			glyphName=glyphName,
    			program=failGlyphProgram,
    			private=private,
    			globalSubrs=globalSubrs,
    			charStringsIndex=charStringsIndex,
    			topDict=topDict,
                charStrings=charStrings
    		)
    		hmtx[glyphName] = failGlyphMetrics
    		for table in cmap.tables:
    			if table.format == 4:
    				table.cmap[cp] = glyphName
    			else:
    				raise NotImplementedError("Unsupported cmap table format: %d" % table.format)
    		cp += 1

    # set the glyph order
    shell.setGlyphOrder(glyphOrder)

    # start the GSUB
    shell["GSUB"] = newTable("GSUB")
    gsub = shell["GSUB"].table = GSUB()
    gsub.Version = 1.0

    # make a list of all the features we will make
    featureCount = len(features)

    # set up the script list
    scriptList = gsub.ScriptList = ScriptList()
    scriptList.ScriptCount = 1
    scriptList.ScriptRecord = []
    scriptRecord = ScriptRecord()
    scriptList.ScriptRecord.append(scriptRecord)
    scriptRecord.ScriptTag = "DFLT"
    script = scriptRecord.Script = Script()
    defaultLangSys = script.DefaultLangSys = DefaultLangSys()
    defaultLangSys.FeatureCount = featureCount
    defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount)
    defaultLangSys.ReqFeatureIndex = 65535
    defaultLangSys.LookupOrder = None
    script.LangSysCount = 0
    script.LangSysRecord = []

    # set up the feature list
    featureList = gsub.FeatureList = FeatureList()
    featureList.FeatureCount = featureCount
    featureList.FeatureRecord = []
    for index, tag in enumerate(features):
    	# feature record
    	featureRecord = FeatureRecord()
    	featureRecord.FeatureTag = tag
    	feature = featureRecord.Feature = Feature()
    	featureList.FeatureRecord.append(featureRecord)
    	# feature
    	feature.FeatureParams = None
    	feature.LookupCount = 1
    	feature.LookupListIndex = [index]

    # write the lookups
    lookupList = gsub.LookupList = LookupList()
    lookupList.LookupCount = featureCount
    lookupList.Lookup = []
    for tag in features:
    	# lookup
    	lookup = Lookup()
    	lookup.LookupType = 3
    	lookup.LookupFlag = 0
    	lookup.SubTableCount = 1
    	lookup.SubTable = []
    	lookupList.Lookup.append(lookup)
    	# subtable
    	subtable = AlternateSubst()
    	subtable.Format = 1
    	subtable.LookupType = 3
    	subtable.alternates = {
    		"%s.default" % tag : ["%s.fail" % tag, "%s.fail" % tag, "%s.fail" % tag],
    		"%s.alt1" % tag    : ["%s.pass" % tag, "%s.fail" % tag, "%s.fail" % tag],
    		"%s.alt2" % tag    : ["%s.fail" % tag, "%s.pass" % tag, "%s.fail" % tag],
    		"%s.alt3" % tag    : ["%s.fail" % tag, "%s.fail" % tag, "%s.pass" % tag]
    	}
    	lookup.SubTable.append(subtable)

    path = outputPath % 3 + ".otf"
    if os.path.exists(path):
    	os.remove(path)
    shell.save(path)

    # get rid of the shell
    if os.path.exists(shellTempPath):
        os.remove(shellTempPath)

def makeJavascriptData():
    features = sorted(mapping)
    outStr = []

    outStr.append("")
    outStr.append("/* This file is autogenerated by makegsubfonts.py */")
    outStr.append("")
    outStr.append("/* ")
    outStr.append("  Features defined in gsubtest fonts with associated base")
    outStr.append("  codepoints for each feature:")
    outStr.append("")
    outStr.append("    cp = codepoint for feature featX")
    outStr.append("")
    outStr.append("    cp   default   PASS")
    outStr.append("    cp   featX=1   FAIL")
    outStr.append("    cp   featX=2   FAIL")
    outStr.append("")
    outStr.append("    cp+1 default   FAIL")
    outStr.append("    cp+1 featX=1   PASS")
    outStr.append("    cp+1 featX=2   FAIL")
    outStr.append("")
    outStr.append("    cp+2 default   FAIL")
    outStr.append("    cp+2 featX=1   FAIL")
    outStr.append("    cp+2 featX=2   PASS")
    outStr.append("")
    outStr.append("*/")
    outStr.append("")
    outStr.append("var gFeatures = {");
    cp = baseCodepoint

    taglist = []
    for tag in features:
        taglist.append("\"%s\": 0x%x" % (tag, cp))
        cp += 4

    outStr.append(textwrap.fill(", ".join(taglist), initial_indent="  ", subsequent_indent="  "))
    outStr.append("};");
    outStr.append("");

    if os.path.exists(javascriptData):
    	os.remove(javascriptData)

    f = open(javascriptData, "wb")
    f.write("\n".join(outStr))
    f.close()


# build fonts

print("Making lookup type 1 font...")
makeLookup1()

print("Making lookup type 3 font...")
makeLookup3()

# output javascript data

print("Making javascript data file...")
makeJavascriptData()
