#!/usr/bin/python

# FontDame-to-fontemon_blender_addon.fontTools for OpenType Layout tables
#
# Source language spec is available at:
# http://monotype.github.io/OpenType_Table_Source/otl_source.html
# https://github.com/Monotype/OpenType_Table_Source/

from fontemon_blender_addon.fontTools.misc.py23 import *
from fontemon_blender_addon.fontTools import ttLib
from fontemon_blender_addon.fontTools.ttLib.tables._c_m_a_p import cmap_classes
from fontemon_blender_addon.fontTools.ttLib.tables import otTables as ot
from fontemon_blender_addon.fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
from fontemon_blender_addon.fontTools.otlLib import builder as otl
from contextlib import contextmanager
from operator import setitem
import logging

class MtiLibError(Exception): pass
class ReferenceNotFoundError(MtiLibError): pass
class FeatureNotFoundError(ReferenceNotFoundError): pass
class LookupNotFoundError(ReferenceNotFoundError): pass


log = logging.getLogger("fontemon_blender_addon.fontTools.mtiLib")


def makeGlyph(s):
	if s[:2] in ['U ', 'u ']:
		return ttLib.TTFont._makeGlyphName(int(s[2:], 16))
	elif s[:2] == '# ':
		return "glyph%.5d" % int(s[2:])
	assert s.find(' ') < 0, "Space found in glyph name: %s" % s
	assert s, "Glyph name is empty"
	return s

def makeGlyphs(l):
	return [makeGlyph(g) for g in l]

def mapLookup(sym, mapping):
	# Lookups are addressed by name.  So resolved them using a map if available.
	# Fallback to parsing as lookup index if a map isn't provided.
	if mapping is not None:
		try:
			idx = mapping[sym]
		except KeyError:
			raise LookupNotFoundError(sym)
	else:
		idx = int(sym)
	return idx

def mapFeature(sym, mapping):
	# Features are referenced by index according the spec.  So, if symbol is an
	# integer, use it directly.  Otherwise look up in the map if provided.
	try:
		idx = int(sym)
	except ValueError:
		try:
			idx = mapping[sym]
		except KeyError:
			raise FeatureNotFoundError(sym)
	return idx

def setReference(mapper, mapping, sym, setter, collection, key):
	try:
		mapped = mapper(sym, mapping)
	except ReferenceNotFoundError as e:
		try:
			if mapping is not None:
				mapping.addDeferredMapping(lambda ref: setter(collection, key, ref), sym, e)
				return
		except AttributeError:
			pass
		raise
	setter(collection, key, mapped)

class DeferredMapping(dict):

	def __init__(self):
		self._deferredMappings = []

	def addDeferredMapping(self, setter, sym, e):
		log.debug("Adding deferred mapping for symbol '%s' %s", sym, type(e).__name__)
		self._deferredMappings.append((setter,sym, e))

	def applyDeferredMappings(self):
		for setter,sym,e in self._deferredMappings:
			log.debug("Applying deferred mapping for symbol '%s' %s", sym, type(e).__name__)
			try:
				mapped = self[sym]
			except KeyError:
				raise e
			setter(mapped)
			log.debug("Set to %s", mapped)
		self._deferredMappings = []


def parseScriptList(lines, featureMap=None):
	self = ot.ScriptList()
	records = []
	with lines.between('script table'):
		for line in lines:
			while len(line) < 4:
				line.append('')
			scriptTag, langSysTag, defaultFeature, features = line
			log.debug("Adding script %s language-system %s", scriptTag, langSysTag)

			langSys = ot.LangSys()
			langSys.LookupOrder = None
			if defaultFeature:
				setReference(mapFeature, featureMap, defaultFeature, setattr, langSys, 'ReqFeatureIndex')
			else:
				langSys.ReqFeatureIndex = 0xFFFF
			syms = stripSplitComma(features)
			langSys.FeatureIndex = theList = [3] * len(syms)
			for i,sym in enumerate(syms):
				setReference(mapFeature, featureMap, sym, setitem, theList, i)
			langSys.FeatureCount = len(langSys.FeatureIndex)

			script = [s for s in records if s.ScriptTag == scriptTag]
			if script:
				script = script[0].Script
			else:
				scriptRec = ot.ScriptRecord()
				scriptRec.ScriptTag = scriptTag
				scriptRec.Script = ot.Script()
				records.append(scriptRec)
				script = scriptRec.Script
				script.DefaultLangSys = None
				script.LangSysRecord = []
				script.LangSysCount = 0

			if langSysTag == 'default':
				script.DefaultLangSys = langSys
			else:
				langSysRec = ot.LangSysRecord()
				langSysRec.LangSysTag = langSysTag + ' '*(4 - len(langSysTag))
				langSysRec.LangSys = langSys
				script.LangSysRecord.append(langSysRec)
				script.LangSysCount = len(script.LangSysRecord)

	for script in records:
		script.Script.LangSysRecord = sorted(script.Script.LangSysRecord, key=lambda rec: rec.LangSysTag)
	self.ScriptRecord = sorted(records, key=lambda rec: rec.ScriptTag)
	self.ScriptCount = len(self.ScriptRecord)
	return self

def parseFeatureList(lines, lookupMap=None, featureMap=None):
	self = ot.FeatureList()
	self.FeatureRecord = []
	with lines.between('feature table'):
		for line in lines:
			name, featureTag, lookups = line
			if featureMap is not None:
				assert name not in featureMap, "Duplicate feature name: %s" % name
				featureMap[name] = len(self.FeatureRecord)
			# If feature name is integer, make sure it matches its index.
			try:
				assert int(name) == len(self.FeatureRecord), "%d %d" % (name, len(self.FeatureRecord))
			except ValueError:
				pass
			featureRec = ot.FeatureRecord()
			featureRec.FeatureTag = featureTag
			featureRec.Feature = ot.Feature()
			self.FeatureRecord.append(featureRec)
			feature = featureRec.Feature
			feature.FeatureParams = None
			syms = stripSplitComma(lookups)
			feature.LookupListIndex = theList = [None] * len(syms)
			for i,sym in enumerate(syms):
				setReference(mapLookup, lookupMap, sym, setitem, theList, i)
			feature.LookupCount = len(feature.LookupListIndex)

	self.FeatureCount = len(self.FeatureRecord)
	return self

def parseLookupFlags(lines):
	flags = 0
	filterset = None
	allFlags = [
		'righttoleft',
		'ignorebaseglyphs',
		'ignoreligatures',
		'ignoremarks',
		'markattachmenttype',
		'markfiltertype',
	]
	while lines.peeks()[0].lower() in allFlags:
		line = next(lines)
		flag = {
			'righttoleft':		0x0001,
			'ignorebaseglyphs':	0x0002,
			'ignoreligatures':	0x0004,
			'ignoremarks':		0x0008,
			}.get(line[0].lower())
		if flag:
			assert line[1].lower() in ['yes', 'no'], line[1]
			if line[1].lower() == 'yes':
				flags |= flag
			continue
		if line[0].lower() == 'markattachmenttype':
			flags |= int(line[1]) << 8
			continue
		if line[0].lower() == 'markfiltertype':
			flags |= 0x10
			filterset = int(line[1])
	return flags, filterset

def parseSingleSubst(lines, font, _lookupMap=None):
	mapping = {}
	for line in lines:
		assert len(line) == 2, line
		line = makeGlyphs(line)
		mapping[line[0]] = line[1]
	return otl.buildSingleSubstSubtable(mapping)

def parseMultiple(lines, font, _lookupMap=None):
	mapping = {}
	for line in lines:
		line = makeGlyphs(line)
		mapping[line[0]] = line[1:]
	return otl.buildMultipleSubstSubtable(mapping)

def parseAlternate(lines, font, _lookupMap=None):
	mapping = {}
	for line in lines:
		line = makeGlyphs(line)
		mapping[line[0]] = line[1:]
	return otl.buildAlternateSubstSubtable(mapping)

def parseLigature(lines, font, _lookupMap=None):
	mapping = {}
	for line in lines:
		assert len(line) >= 2, line
		line = makeGlyphs(line)
		mapping[tuple(line[1:])] = line[0]
	return otl.buildLigatureSubstSubtable(mapping)

def parseSinglePos(lines, font, _lookupMap=None):
	values = {}
	for line in lines:
		assert len(line) == 3, line
		w = line[0].title().replace(' ', '')
		assert w in valueRecordFormatDict
		g = makeGlyph(line[1])
		v = int(line[2])
		if g not in values:
			values[g] = ValueRecord()
		assert not hasattr(values[g], w), (g, w)
		setattr(values[g], w, v)
	return otl.buildSinglePosSubtable(values, font.getReverseGlyphMap())

def parsePair(lines, font, _lookupMap=None):
	self = ot.PairPos()
	self.ValueFormat1 = self.ValueFormat2 = 0
	typ = lines.peeks()[0].split()[0].lower()
	if typ in ('left', 'right'):
		self.Format = 1
		values = {}
		for line in lines:
			assert len(line) == 4, line
			side = line[0].split()[0].lower()
			assert side in ('left', 'right'), side
			what = line[0][len(side):].title().replace(' ', '')
			mask = valueRecordFormatDict[what][0]
			glyph1, glyph2 = makeGlyphs(line[1:3])
			value = int(line[3])
			if not glyph1 in values: values[glyph1] = {}
			if not glyph2 in values[glyph1]: values[glyph1][glyph2] = (ValueRecord(),ValueRecord())
			rec2 = values[glyph1][glyph2]
			if side == 'left':
				self.ValueFormat1 |= mask
				vr = rec2[0]
			else:
				self.ValueFormat2 |= mask
				vr = rec2[1]
			assert not hasattr(vr, what), (vr, what)
			setattr(vr, what, value)
		self.Coverage = makeCoverage(set(values.keys()), font)
		self.PairSet = []
		for glyph1 in self.Coverage.glyphs:
			values1 = values[glyph1]
			pairset = ot.PairSet()
			records = pairset.PairValueRecord = []
			for glyph2 in sorted(values1.keys(), key=font.getGlyphID):
				values2 = values1[glyph2]
				pair = ot.PairValueRecord()
				pair.SecondGlyph = glyph2
				pair.Value1 = values2[0]
				pair.Value2 = values2[1] if self.ValueFormat2 else None
				records.append(pair)
			pairset.PairValueCount = len(pairset.PairValueRecord)
			self.PairSet.append(pairset)
		self.PairSetCount = len(self.PairSet)
	elif typ.endswith('class'):
		self.Format = 2
		classDefs = [None, None]
		while lines.peeks()[0].endswith("class definition begin"):
			typ = lines.peek()[0][:-len("class definition begin")].lower()
			idx,klass = {
				'first':	(0,ot.ClassDef1),
				'second':	(1,ot.ClassDef2),
			}[typ]
			assert classDefs[idx] is None
			classDefs[idx] = parseClassDef(lines, font, klass=klass)
		self.ClassDef1, self.ClassDef2 = classDefs
		self.Class1Count, self.Class2Count = (1+max(c.classDefs.values()) for c in classDefs)
		self.Class1Record = [ot.Class1Record() for i in range(self.Class1Count)]
		for rec1 in self.Class1Record:
			rec1.Class2Record = [ot.Class2Record() for j in range(self.Class2Count)]
			for rec2 in rec1.Class2Record:
				rec2.Value1 = ValueRecord()
				rec2.Value2 = ValueRecord()
		for line in lines:
			assert len(line) == 4, line
			side = line[0].split()[0].lower()
			assert side in ('left', 'right'), side
			what = line[0][len(side):].title().replace(' ', '')
			mask = valueRecordFormatDict[what][0]
			class1, class2, value = (int(x) for x in line[1:4])
			rec2 = self.Class1Record[class1].Class2Record[class2]
			if side == 'left':
				self.ValueFormat1 |= mask
				vr = rec2.Value1
			else:
				self.ValueFormat2 |= mask
				vr = rec2.Value2
			assert not hasattr(vr, what), (vr, what)
			setattr(vr, what, value)
		for rec1 in self.Class1Record:
			for rec2 in rec1.Class2Record:
				rec2.Value1 = ValueRecord(self.ValueFormat1, rec2.Value1)
				rec2.Value2 = ValueRecord(self.ValueFormat2, rec2.Value2) \
						if self.ValueFormat2 else None

		self.Coverage = makeCoverage(set(self.ClassDef1.classDefs.keys()), font)
	else:
		assert 0, typ
	return self

def parseKernset(lines, font, _lookupMap=None):
	typ = lines.peeks()[0].split()[0].lower()
	if typ in ('left', 'right'):
		with lines.until(("firstclass definition begin", "secondclass definition begin")):
			return parsePair(lines, font)
	return parsePair(lines, font)

def makeAnchor(data, klass=ot.Anchor):
	assert len(data) <= 2
	anchor = klass()
	anchor.Format = 1
	anchor.XCoordinate,anchor.YCoordinate = intSplitComma(data[0])
	if len(data) > 1 and data[1] != '':
		anchor.Format = 2
		anchor.AnchorPoint = int(data[1])
	return anchor

def parseCursive(lines, font, _lookupMap=None):
	records = {}
	for line in lines:
		assert len(line) in [3,4], line
		idx,klass = {
			'entry':	(0,ot.EntryAnchor),
			'exit':		(1,ot.ExitAnchor),
		}[line[0]]
		glyph = makeGlyph(line[1])
		if glyph not in records:
			records[glyph] = [None,None]
		assert records[glyph][idx] is None, (glyph, idx)
		records[glyph][idx] = makeAnchor(line[2:], klass)
	return otl.buildCursivePosSubtable(records, font.getReverseGlyphMap())

def makeMarkRecords(data, coverage, c):
	records = []
	for glyph in coverage.glyphs:
		klass, anchor = data[glyph]
		record = c.MarkRecordClass()
		record.Class = klass
		setattr(record, c.MarkAnchor, anchor)
		records.append(record)
	return records

def makeBaseRecords(data, coverage, c, classCount):
	records = []
	idx = {}
	for glyph in coverage.glyphs:
		idx[glyph] = len(records)
		record = c.BaseRecordClass()
		anchors = [None] * classCount
		setattr(record, c.BaseAnchor, anchors)
		records.append(record)
	for (glyph,klass),anchor in data.items():
		record = records[idx[glyph]]
		anchors = getattr(record, c.BaseAnchor)
		assert anchors[klass] is None, (glyph, klass)
		anchors[klass] = anchor
	return records

def makeLigatureRecords(data, coverage, c, classCount):
	records = [None] * len(coverage.glyphs)
	idx = {g:i for i,g in enumerate(coverage.glyphs)}

	for (glyph,klass,compIdx,compCount),anchor in data.items():
		record = records[idx[glyph]]
		if record is None:
			record = records[idx[glyph]] = ot.LigatureAttach()
			record.ComponentCount = compCount
			record.ComponentRecord = [ot.ComponentRecord() for i in range(compCount)]
			for compRec in record.ComponentRecord:
				compRec.LigatureAnchor = [None] * classCount
		assert record.ComponentCount == compCount, (glyph, record.ComponentCount, compCount)

		anchors = record.ComponentRecord[compIdx - 1].LigatureAnchor
		assert anchors[klass] is None, (glyph, compIdx, klass)
		anchors[klass] = anchor
	return records

def parseMarkToSomething(lines, font, c):
	self = c.Type()
	self.Format = 1
	markData = {}
	baseData = {}
	Data = {
		'mark':		(markData, c.MarkAnchorClass),
		'base':		(baseData, c.BaseAnchorClass),
		'ligature':	(baseData, c.BaseAnchorClass),
	}
	maxKlass = 0
	for line in lines:
		typ = line[0]
		assert typ in ('mark', 'base', 'ligature')
		glyph = makeGlyph(line[1])
		data, anchorClass = Data[typ]
		extraItems = 2 if typ == 'ligature' else 0
		extras = tuple(int(i) for i in line[2:2+extraItems])
		klass = int(line[2+extraItems])
		anchor = makeAnchor(line[3+extraItems:], anchorClass)
		if typ == 'mark':
			key,value = glyph,(klass,anchor)
		else:
			key,value = ((glyph,klass)+extras),anchor
		assert key not in data, key
		data[key] = value
		maxKlass = max(maxKlass, klass)

	# Mark
	markCoverage = makeCoverage(set(markData.keys()), font, c.MarkCoverageClass)
	markArray = c.MarkArrayClass()
	markRecords = makeMarkRecords(markData, markCoverage, c)
	setattr(markArray, c.MarkRecord, markRecords)
	setattr(markArray, c.MarkCount, len(markRecords))
	setattr(self, c.MarkCoverage, markCoverage)
	setattr(self, c.MarkArray, markArray)
	self.ClassCount = maxKlass + 1

	# Base
	self.classCount = 0 if not baseData else 1+max(k[1] for k,v in baseData.items())
	baseCoverage = makeCoverage(set([k[0] for k in baseData.keys()]), font, c.BaseCoverageClass)
	baseArray = c.BaseArrayClass()
	if c.Base == 'Ligature':
		baseRecords = makeLigatureRecords(baseData, baseCoverage, c, self.classCount)
	else:
		baseRecords = makeBaseRecords(baseData, baseCoverage, c, self.classCount)
	setattr(baseArray, c.BaseRecord, baseRecords)
	setattr(baseArray, c.BaseCount, len(baseRecords))
	setattr(self, c.BaseCoverage, baseCoverage)
	setattr(self, c.BaseArray, baseArray)

	return self

class MarkHelper(object):
	def __init__(self):
		for Which in ('Mark', 'Base'):
			for What in ('Coverage', 'Array', 'Count', 'Record', 'Anchor'):
				key = Which + What
				if Which == 'Mark' and What in ('Count', 'Record', 'Anchor'):
					value = key
				else:
					value = getattr(self, Which) + What
				if value == 'LigatureRecord':
					value = 'LigatureAttach'
				setattr(self, key, value)
				if What != 'Count':
					klass = getattr(ot, value)
					setattr(self, key+'Class', klass)

class MarkToBaseHelper(MarkHelper):
	Mark = 'Mark'
	Base = 'Base'
	Type = ot.MarkBasePos
class MarkToMarkHelper(MarkHelper):
	Mark = 'Mark1'
	Base = 'Mark2'
	Type = ot.MarkMarkPos
class MarkToLigatureHelper(MarkHelper):
	Mark = 'Mark'
	Base = 'Ligature'
	Type = ot.MarkLigPos

def parseMarkToBase(lines, font, _lookupMap=None):
	return parseMarkToSomething(lines, font, MarkToBaseHelper())
def parseMarkToMark(lines, font, _lookupMap=None):
	return parseMarkToSomething(lines, font, MarkToMarkHelper())
def parseMarkToLigature(lines, font, _lookupMap=None):
	return parseMarkToSomething(lines, font, MarkToLigatureHelper())

def stripSplitComma(line):
	return [s.strip() for s in line.split(',')] if line else []

def intSplitComma(line):
	return [int(i) for i in line.split(',')] if line else []

# Copied from fontemon_blender_addon.fontTools.subset
class ContextHelper(object):
	def __init__(self, klassName, Format):
		if klassName.endswith('Subst'):
			Typ = 'Sub'
			Type = 'Subst'
		else:
			Typ = 'Pos'
			Type = 'Pos'
		if klassName.startswith('Chain'):
			Chain = 'Chain'
			InputIdx = 1
			DataLen = 3
		else:
			Chain = ''
			InputIdx = 0
			DataLen = 1
		ChainTyp = Chain+Typ

		self.Typ = Typ
		self.Type = Type
		self.Chain = Chain
		self.ChainTyp = ChainTyp
		self.InputIdx = InputIdx
		self.DataLen = DataLen

		self.LookupRecord = Type+'LookupRecord'

		if Format == 1:
			Coverage = lambda r: r.Coverage
			ChainCoverage = lambda r: r.Coverage
			ContextData = lambda r:(None,)
			ChainContextData = lambda r:(None, None, None)
			SetContextData = None
			SetChainContextData = None
			RuleData = lambda r:(r.Input,)
			ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
			def SetRuleData(r, d):
				(r.Input,) = d
				(r.GlyphCount,) = (len(x)+1 for x in d)
			def ChainSetRuleData(r, d):
				(r.Backtrack, r.Input, r.LookAhead) = d
				(r.BacktrackGlyphCount,r.InputGlyphCount,r.LookAheadGlyphCount,) = (len(d[0]),len(d[1])+1,len(d[2]))
		elif Format == 2:
			Coverage = lambda r: r.Coverage
			ChainCoverage = lambda r: r.Coverage
			ContextData = lambda r:(r.ClassDef,)
			ChainContextData = lambda r:(r.BacktrackClassDef,
						     r.InputClassDef,
						     r.LookAheadClassDef)
			def SetContextData(r, d):
				(r.ClassDef,) = d
			def SetChainContextData(r, d):
				(r.BacktrackClassDef,
				 r.InputClassDef,
				 r.LookAheadClassDef) = d
			RuleData = lambda r:(r.Class,)
			ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
			def SetRuleData(r, d):
				(r.Class,) = d
				(r.GlyphCount,) = (len(x)+1 for x in d)
			def ChainSetRuleData(r, d):
				(r.Backtrack, r.Input, r.LookAhead) = d
				(r.BacktrackGlyphCount,r.InputGlyphCount,r.LookAheadGlyphCount,) = (len(d[0]),len(d[1])+1,len(d[2]))
		elif Format == 3:
			Coverage = lambda r: r.Coverage[0]
			ChainCoverage = lambda r: r.InputCoverage[0]
			ContextData = None
			ChainContextData = None
			SetContextData = None
			SetChainContextData = None
			RuleData = lambda r: r.Coverage
			ChainRuleData = lambda r:(r.BacktrackCoverage +
						  r.InputCoverage +
						  r.LookAheadCoverage)
			def SetRuleData(r, d):
				(r.Coverage,) = d
				(r.GlyphCount,) = (len(x) for x in d)
			def ChainSetRuleData(r, d):
				(r.BacktrackCoverage, r.InputCoverage, r.LookAheadCoverage) = d
				(r.BacktrackGlyphCount,r.InputGlyphCount,r.LookAheadGlyphCount,) = (len(x) for x in d)
		else:
			assert 0, "unknown format: %s" % Format

		if Chain:
			self.Coverage = ChainCoverage
			self.ContextData = ChainContextData
			self.SetContextData = SetChainContextData
			self.RuleData = ChainRuleData
			self.SetRuleData = ChainSetRuleData
		else:
			self.Coverage = Coverage
			self.ContextData = ContextData
			self.SetContextData = SetContextData
			self.RuleData = RuleData
			self.SetRuleData = SetRuleData

		if Format == 1:
			self.Rule = ChainTyp+'Rule'
			self.RuleCount = ChainTyp+'RuleCount'
			self.RuleSet = ChainTyp+'RuleSet'
			self.RuleSetCount = ChainTyp+'RuleSetCount'
			self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
		elif Format == 2:
			self.Rule = ChainTyp+'ClassRule'
			self.RuleCount = ChainTyp+'ClassRuleCount'
			self.RuleSet = ChainTyp+'ClassSet'
			self.RuleSetCount = ChainTyp+'ClassSetCount'
			self.Intersect = lambda glyphs, c, r: (c.intersect_class(glyphs, r) if c
							       else (set(glyphs) if r == 0 else set()))

			self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
			self.ClassDefIndex = 1 if Chain else 0
			self.Input = 'Input' if Chain else 'Class'

def parseLookupRecords(items, klassName, lookupMap=None):
	klass = getattr(ot, klassName)
	lst = []
	for item in items:
		rec = klass()
		item = stripSplitComma(item)
		assert len(item) == 2, item
		idx = int(item[0])
		assert idx > 0, idx
		rec.SequenceIndex = idx - 1
		setReference(mapLookup, lookupMap, item[1], setattr, rec, 'LookupListIndex')
		lst.append(rec)
	return lst

def makeClassDef(classDefs, font, klass=ot.Coverage):
	if not classDefs: return None
	self = klass()
	self.classDefs = dict(classDefs)
	return self

def parseClassDef(lines, font, klass=ot.ClassDef):
	classDefs = {}
	with lines.between('class definition'):
		for line in lines:
			glyph = makeGlyph(line[0])
			assert glyph not in classDefs, glyph
			classDefs[glyph] = int(line[1])
	return makeClassDef(classDefs, font, klass)

def makeCoverage(glyphs, font, klass=ot.Coverage):
	if not glyphs: return None
	if isinstance(glyphs, set):
		glyphs = sorted(glyphs)
	coverage = klass()
	coverage.glyphs = sorted(set(glyphs), key=font.getGlyphID)
	return coverage

def parseCoverage(lines, font, klass=ot.Coverage):
	glyphs = []
	with lines.between('coverage definition'):
		for line in lines:
			glyphs.append(makeGlyph(line[0]))
	return makeCoverage(glyphs, font, klass)

def bucketizeRules(self, c, rules, bucketKeys):
	buckets = {}
	for seq,recs in rules:
		buckets.setdefault(seq[c.InputIdx][0], []).append((tuple(s[1 if i==c.InputIdx else 0:] for i,s in enumerate(seq)), recs))

	rulesets = []
	for firstGlyph in bucketKeys:
		if firstGlyph not in buckets:
			rulesets.append(None)
			continue
		thisRules = []
		for seq,recs in buckets[firstGlyph]:
			rule = getattr(ot, c.Rule)()
			c.SetRuleData(rule, seq)
			setattr(rule, c.Type+'Count', len(recs))
			setattr(rule, c.LookupRecord, recs)
			thisRules.append(rule)

		ruleset = getattr(ot, c.RuleSet)()
		setattr(ruleset, c.Rule, thisRules)
		setattr(ruleset, c.RuleCount, len(thisRules))
		rulesets.append(ruleset)

	setattr(self, c.RuleSet, rulesets)
	setattr(self, c.RuleSetCount, len(rulesets))

def parseContext(lines, font, Type, lookupMap=None):
	self = getattr(ot, Type)()
	typ = lines.peeks()[0].split()[0].lower()
	if typ == 'glyph':
		self.Format = 1
		log.debug("Parsing %s format %s", Type, self.Format)
		c = ContextHelper(Type, self.Format)
		rules = []
		for line in lines:
			assert line[0].lower() == 'glyph', line[0]
			while len(line) < 1+c.DataLen: line.append('')
			seq = tuple(makeGlyphs(stripSplitComma(i)) for i in line[1:1+c.DataLen])
			recs = parseLookupRecords(line[1+c.DataLen:], c.LookupRecord, lookupMap)
			rules.append((seq, recs))

		firstGlyphs = set(seq[c.InputIdx][0] for seq,recs in rules)
		self.Coverage = makeCoverage(firstGlyphs, font)
		bucketizeRules(self, c, rules, self.Coverage.glyphs)
	elif typ.endswith('class'):
		self.Format = 2
		log.debug("Parsing %s format %s", Type, self.Format)
		c = ContextHelper(Type, self.Format)
		classDefs = [None] * c.DataLen
		while lines.peeks()[0].endswith("class definition begin"):
			typ = lines.peek()[0][:-len("class definition begin")].lower()
			idx,klass = {
			1: {
				'':		(0,ot.ClassDef),
			},
			3: {
				'backtrack':	(0,ot.BacktrackClassDef),
				'':		(1,ot.InputClassDef),
				'lookahead':	(2,ot.LookAheadClassDef),
			},
			}[c.DataLen][typ]
			assert classDefs[idx] is None, idx
			classDefs[idx] = parseClassDef(lines, font, klass=klass)
		c.SetContextData(self, classDefs)
		rules = []
		for line in lines:
			assert line[0].lower().startswith('class'), line[0]
			while len(line) < 1+c.DataLen: line.append('')
			seq = tuple(intSplitComma(i) for i in line[1:1+c.DataLen])
			recs = parseLookupRecords(line[1+c.DataLen:], c.LookupRecord, lookupMap)
			rules.append((seq, recs))
		firstClasses = set(seq[c.InputIdx][0] for seq,recs in rules)
		firstGlyphs = set(g for g,c in classDefs[c.InputIdx].classDefs.items() if c in firstClasses)
		self.Coverage = makeCoverage(firstGlyphs, font)
		bucketizeRules(self, c, rules, range(max(firstClasses) + 1))
	elif typ.endswith('coverage'):
		self.Format = 3
		log.debug("Parsing %s format %s", Type, self.Format)
		c = ContextHelper(Type, self.Format)
		coverages = tuple([] for i in range(c.DataLen))
		while lines.peeks()[0].endswith("coverage definition begin"):
			typ = lines.peek()[0][:-len("coverage definition begin")].lower()
			idx,klass = {
			1: {
				'':		(0,ot.Coverage),
			},
			3: {
				'backtrack':	(0,ot.BacktrackCoverage),
				'input':	(1,ot.InputCoverage),
				'lookahead':	(2,ot.LookAheadCoverage),
			},
			}[c.DataLen][typ]
			coverages[idx].append(parseCoverage(lines, font, klass=klass))
		c.SetRuleData(self, coverages)
		lines = list(lines)
		assert len(lines) == 1
		line = lines[0]
		assert line[0].lower() == 'coverage', line[0]
		recs = parseLookupRecords(line[1:], c.LookupRecord, lookupMap)
		setattr(self, c.Type+'Count', len(recs))
		setattr(self, c.LookupRecord, recs)
	else:
		assert 0, typ
	return self

def parseContextSubst(lines, font, lookupMap=None):
	return parseContext(lines, font, "ContextSubst", lookupMap=lookupMap)
def parseContextPos(lines, font, lookupMap=None):
	return parseContext(lines, font, "ContextPos", lookupMap=lookupMap)
def parseChainedSubst(lines, font, lookupMap=None):
	return parseContext(lines, font, "ChainContextSubst", lookupMap=lookupMap)
def parseChainedPos(lines, font, lookupMap=None):
	return parseContext(lines, font, "ChainContextPos", lookupMap=lookupMap)

def parseReverseChainedSubst(lines, font, _lookupMap=None):
	self = ot.ReverseChainSingleSubst()
	self.Format = 1
	coverages = ([], [])
	while lines.peeks()[0].endswith("coverage definition begin"):
		typ = lines.peek()[0][:-len("coverage definition begin")].lower()
		idx,klass = {
			'backtrack':	(0,ot.BacktrackCoverage),
			'lookahead':	(1,ot.LookAheadCoverage),
		}[typ]
		coverages[idx].append(parseCoverage(lines, font, klass=klass))
	self.BacktrackCoverage = coverages[0]
	self.BacktrackGlyphCount = len(self.BacktrackCoverage)
	self.LookAheadCoverage = coverages[1]
	self.LookAheadGlyphCount = len(self.LookAheadCoverage)
	mapping = {}
	for line in lines:
		assert len(line) == 2, line
		line = makeGlyphs(line)
		mapping[line[0]] = line[1]
	self.Coverage = makeCoverage(set(mapping.keys()), font)
	self.Substitute = [mapping[k] for k in self.Coverage.glyphs]
	self.GlyphCount = len(self.Substitute)
	return self

def parseLookup(lines, tableTag, font, lookupMap=None):
	line = lines.expect('lookup')
	_, name, typ = line
	log.debug("Parsing lookup type %s %s", typ, name)
	lookup = ot.Lookup()
	lookup.LookupFlag,filterset = parseLookupFlags(lines)
	if filterset is not None:
		lookup.MarkFilteringSet = filterset
	lookup.LookupType, parseLookupSubTable = {
		'GSUB': {
			'single':	(1,	parseSingleSubst),
			'multiple':	(2,	parseMultiple),
			'alternate':	(3,	parseAlternate),
			'ligature':	(4,	parseLigature),
			'context':	(5,	parseContextSubst),
			'chained':	(6,	parseChainedSubst),
			'reversechained':(8,	parseReverseChainedSubst),
		},
		'GPOS': {
			'single':	(1,	parseSinglePos),
			'pair':		(2,	parsePair),
			'kernset':	(2,	parseKernset),
			'cursive':	(3,	parseCursive),
			'mark to base':	(4,	parseMarkToBase),
			'mark to ligature':(5,	parseMarkToLigature),
			'mark to mark':	(6,	parseMarkToMark),
			'context':	(7,	parseContextPos),
			'chained':	(8,	parseChainedPos),
		},
	}[tableTag][typ]

	with lines.until('lookup end'):
		subtables = []

		while lines.peek():
			with lines.until(('% subtable', 'subtable end')):
				while lines.peek():
					subtable = parseLookupSubTable(lines, font, lookupMap)
					assert lookup.LookupType == subtable.LookupType
					subtables.append(subtable)
			if lines.peeks()[0] in ('% subtable', 'subtable end'):
				next(lines)
	lines.expect('lookup end')

	lookup.SubTable = subtables
	lookup.SubTableCount = len(lookup.SubTable)
	if lookup.SubTableCount == 0:
		# Remove this return when following is fixed:
		# https://github.com/fontemon_blender_addon.fontTools/fontemon_blender_addon.fontTools/issues/789
		return None
	return lookup

def parseGSUBGPOS(lines, font, tableTag):
	container = ttLib.getTableClass(tableTag)()
	lookupMap = DeferredMapping()
	featureMap = DeferredMapping()
	assert tableTag in ('GSUB', 'GPOS')
	log.debug("Parsing %s", tableTag)
	self = getattr(ot, tableTag)()
	self.Version = 0x00010000
	fields = {
		'script table begin':
		('ScriptList',
		 lambda lines: parseScriptList (lines, featureMap)),
		'feature table begin':
		('FeatureList',
		 lambda lines: parseFeatureList (lines, lookupMap, featureMap)),
		'lookup':
		('LookupList',
		 None),
	}
	for attr,parser in fields.values():
		setattr(self, attr, None)
	while lines.peek() is not None:
		typ = lines.peek()[0].lower()
		if typ not in fields:
			log.debug('Skipping %s', lines.peek())
			next(lines)
			continue
		attr,parser = fields[typ]
		if typ == 'lookup':
			if self.LookupList is None:
				self.LookupList = ot.LookupList()
				self.LookupList.Lookup = []
			_, name, _ = lines.peek()
			lookup = parseLookup(lines, tableTag, font, lookupMap)
			if lookupMap is not None:
				assert name not in lookupMap, "Duplicate lookup name: %s" % name
				lookupMap[name] = len(self.LookupList.Lookup)
			else:
				assert int(name) == len(self.LookupList.Lookup), "%d %d" % (name, len(self.Lookup))
			self.LookupList.Lookup.append(lookup)
		else:
			assert getattr(self, attr) is None, attr
			setattr(self, attr, parser(lines))
	if self.LookupList:
		self.LookupList.LookupCount = len(self.LookupList.Lookup)
	if lookupMap is not None:
		lookupMap.applyDeferredMappings()
	if featureMap is not None:
		featureMap.applyDeferredMappings()
	container.table = self
	return container

def parseGSUB(lines, font):
	return parseGSUBGPOS(lines, font, 'GSUB')
def parseGPOS(lines, font):
	return parseGSUBGPOS(lines, font, 'GPOS')

def parseAttachList(lines, font):
	points = {}
	with lines.between('attachment list'):
		for line in lines:
			glyph = makeGlyph(line[0])
			assert glyph not in points, glyph
			points[glyph] = [int(i) for i in line[1:]]
	return otl.buildAttachList(points, font.getReverseGlyphMap())

def parseCaretList(lines, font):
	carets = {}
	with lines.between('carets'):
		for line in lines:
			glyph = makeGlyph(line[0])
			assert glyph not in carets, glyph
			num = int(line[1])
			thisCarets = [int(i) for i in line[2:]]
			assert num == len(thisCarets), line
			carets[glyph] = thisCarets
	return otl.buildLigCaretList(carets, {}, font.getReverseGlyphMap())

def makeMarkFilteringSets(sets, font):
	self = ot.MarkGlyphSetsDef()
	self.MarkSetTableFormat = 1
	self.MarkSetCount = 1 + max(sets.keys())
	self.Coverage = [None] * self.MarkSetCount
	for k,v in sorted(sets.items()):
		self.Coverage[k] = makeCoverage(set(v), font)
	return self

def parseMarkFilteringSets(lines, font):
	sets = {}
	with lines.between('set definition'):
		for line in lines:
			assert len(line) == 2, line
			glyph = makeGlyph(line[0])
			# TODO accept set names
			st = int(line[1])
			if st not in sets:
				sets[st] = []
			sets[st].append(glyph)
	return makeMarkFilteringSets(sets, font)

def parseGDEF(lines, font):
	container = ttLib.getTableClass('GDEF')()
	log.debug("Parsing GDEF")
	self = ot.GDEF()
	fields = {
		'class definition begin':
			('GlyphClassDef',
			 lambda lines, font: parseClassDef(lines, font, klass=ot.GlyphClassDef)),
		'attachment list begin':
			('AttachList', parseAttachList),
		'carets begin':
			('LigCaretList', parseCaretList),
		'mark attachment class definition begin':
			('MarkAttachClassDef',
			 lambda lines, font: parseClassDef(lines, font, klass=ot.MarkAttachClassDef)),
		'markfilter set definition begin':
			('MarkGlyphSetsDef', parseMarkFilteringSets),
	}
	for attr,parser in fields.values():
		setattr(self, attr, None)
	while lines.peek() is not None:
		typ = lines.peek()[0].lower()
		if typ not in fields:
			log.debug('Skipping %s', typ)
			next(lines)
			continue
		attr,parser = fields[typ]
		assert getattr(self, attr) is None, attr
		setattr(self, attr, parser(lines, font))
	self.Version = 0x00010000 if self.MarkGlyphSetsDef is None else 0x00010002
	container.table = self
	return container

def parseCmap(lines, font):
	container = ttLib.getTableClass('cmap')()
	log.debug("Parsing cmap")
	tables = []
	while lines.peek() is not None:
		lines.expect('cmap subtable %d' % len(tables))
		platId, encId, fmt, lang = [
			parseCmapId(lines, field)
			for field in ('platformID', 'encodingID', 'format', 'language')]
		table = cmap_classes[fmt](fmt)
		table.platformID = platId
		table.platEncID = encId
		table.language = lang
		table.cmap = {}
		line = next(lines)
		while line[0] != 'end subtable':
			table.cmap[int(line[0], 16)] = line[1]
			line = next(lines)
		tables.append(table)
	container.tableVersion = 0
	container.tables = tables
	return container

def parseCmapId(lines, field):
	line = next(lines)
	assert field == line[0]
	return int(line[1])

def parseTable(lines, font, tableTag=None):
	log.debug("Parsing table")
	line = lines.peeks()
	tag = None
	if line[0].split()[0] == 'FontDame':
		tag = line[0].split()[1]
	elif ' '.join(line[0].split()[:3]) == 'Font Chef Table':
		tag = line[0].split()[3]
	if tag is not None:
		next(lines)
		tag = tag.ljust(4)
		if tableTag is None:
			tableTag = tag
		else:
			assert tableTag == tag, (tableTag, tag)

	assert tableTag is not None, "Don't know what table to parse and data doesn't specify"

	return {
		'GSUB': parseGSUB,
		'GPOS': parseGPOS,
		'GDEF': parseGDEF,
		'cmap': parseCmap,
		}[tableTag](lines, font)

class Tokenizer(object):

	def __init__(self, f):
		# TODO BytesIO / StringIO as needed?  also, figure out whether we work on bytes or unicode
		lines = iter(f)
		try:
			self.filename = f.name
		except:
			self.filename = None
		self.lines = iter(lines)
		self.line = ''
		self.lineno = 0
		self.stoppers = []
		self.buffer = None

	def __iter__(self):
		return self

	def _next_line(self):
		self.lineno += 1
		line = self.line = next(self.lines)
		line = [s.strip() for s in line.split('\t')]
		if len(line) == 1 and not line[0]:
			del line[0]
		if line and not line[-1]:
			log.warning('trailing tab found on line %d: %s' % (self.lineno, self.line))
			while line and not line[-1]:
				del line[-1]
		return line

	def _next_nonempty(self):
		while True:
			line = self._next_line()
			# Skip comments and empty lines
			if line and line[0] and (line[0][0] != '%' or line[0] == '% subtable'):
				return line

	def _next_buffered(self):
		if self.buffer:
			ret = self.buffer
			self.buffer = None
			return ret
		else:
			return self._next_nonempty()

	def __next__(self):
		line = self._next_buffered()
		if line[0].lower() in self.stoppers:
			self.buffer = line
			raise StopIteration
		return line

	def next(self):
		return self.__next__()

	def peek(self):
		if not self.buffer:
			try:
				self.buffer = self._next_nonempty()
			except StopIteration:
				return None
		if self.buffer[0].lower() in self.stoppers:
			return None
		return self.buffer

	def peeks(self):
		ret = self.peek()
		return ret if ret is not None else ('',)

	@contextmanager
	def between(self, tag):
		start = tag + ' begin'
		end = tag + ' end'
		self.expectendswith(start)
		self.stoppers.append(end)
		yield
		del self.stoppers[-1]
		self.expect(tag + ' end')

	@contextmanager
	def until(self, tags):
		if type(tags) is not tuple:
			tags = (tags,)
		self.stoppers.extend(tags)
		yield
		del self.stoppers[-len(tags):]

	def expect(self, s):
		line = next(self)
		tag = line[0].lower()
		assert tag == s, "Expected '%s', got '%s'" % (s, tag)
		return line

	def expectendswith(self, s):
		line = next(self)
		tag = line[0].lower()
		assert tag.endswith(s), "Expected '*%s', got '%s'" % (s, tag)
		return line

def build(f, font, tableTag=None):
	"""Convert a Monotype font layout file to an OpenType layout object

	A font object must be passed, but this may be a "dummy" font; it is only
	used for sorting glyph sets when making coverage tables and to hold the
	OpenType layout table while it is being built.

	Args:
		f: A file object.
		font (TTFont): A font object.
		tableTag (string): If provided, asserts that the file contains data for the
			given OpenType table.

	Returns:
		An object representing the table. (e.g. ``table_G_S_U_B_``)
	"""
	lines = Tokenizer(f)
	return parseTable(lines, font, tableTag=tableTag)


def main(args=None, font=None):
	"""Convert a FontDame OTL file to TTX XML.

	Writes XML output to stdout.

	Args:
		args: Command line arguments (``--font``, ``--table``, input files).
	"""
	import sys
	from fontemon_blender_addon.fontTools import configLogger
	from fontemon_blender_addon.fontTools.misc.testTools import MockFont

	if args is None:
		args = sys.argv[1:]

	# configure the library logger (for >= WARNING)
	configLogger()
	# comment this out to enable debug messages from mtiLib's logger
	# log.setLevel(logging.DEBUG)

	import argparse
	parser = argparse.ArgumentParser(
		"fontemon_blender_addon.fontTools mtiLib",
		description=main.__doc__,
	)

	parser.add_argument('--font', '-f', metavar='FILE', dest="font",
		help="Input TTF files (used for glyph classes and sorting coverage tables)")
	parser.add_argument('--table', '-t', metavar='TABLE', dest="tableTag",
		help="Table to fill (sniffed from input file if not provided)")
	parser.add_argument('inputs', metavar='FILE', type=str, nargs='+',
		help="Input FontDame .txt files")

	args = parser.parse_args(args)

	if font is None:
		if args.font:
			font = ttLib.TTFont(args.font)
		else:
			font = MockFont()

	for f in args.inputs:
		log.debug("Processing %s", f)
		with open(f, 'rt', encoding="utf-8") as f:
			table = build(f, font, tableTag=args.tableTag)
		blob = table.compile(font) # Make sure it compiles
		decompiled = table.__class__()
		decompiled.decompile(blob, font) # Make sure it decompiles!

		#continue
		from fontemon_blender_addon.fontTools.misc import xmlWriter
		tag = table.tableTag
		writer = xmlWriter.XMLWriter(sys.stdout)
		writer.begintag(tag)
		writer.newline()
		#table.toXML(writer, font)
		decompiled.toXML(writer, font)
		writer.endtag(tag)
		writer.newline()


if __name__ == '__main__':
	import sys
	sys.exit(main())
