#!/usr/bin/python2

# FontDame-to-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 __future__ import print_function, division, absolute_import
from __future__ import unicode_literals
from fontTools.misc.py23 import *
from fontTools import ttLib
from fontTools.ttLib.tables._c_m_a_p import cmap_classes
from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
from fontTools.otlLib import builder as otl
from contextlib import contextmanager
from operator import setitem
import logging
import warnings

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


log = logging.getLogger("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 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 is 0:
		# Remove this return when following is fixed:
		# https://github.com/fonttools/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]:
			warnings.warn('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):
	lines = Tokenizer(f)
	return parseTable(lines, font, tableTag=tableTag)


def main(args=None, font=None):
	import sys
	from fontTools import configLogger
	from 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)

	if font is None:
		font = MockFont()

	tableTag = None
	if args[0].startswith('-t'):
		tableTag = args[0][2:]
		del args[0]
	for f in args:
		log.debug("Processing %s", f)
		table = build(open(f, 'rt', encoding="utf-8"), font, tableTag=tableTag)
		blob = table.compile(font) # Make sure it compiles
		decompiled = table.__class__()
		decompiled.decompile(blob, font) # Make sure it decompiles!

		#continue
		from 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())
