#! /usr/bin/env python

"""
Tool to find wront contour order between different masters, and
other interpolatability (or lack thereof) issues.
"""

from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *

from fontTools.pens.basePen import BasePen
from symfont import GlyphStatistics
import itertools

class PerContourOrComponentPen(BasePen):
	def __init__(self, Pen, glyphset=None):
		BasePen.__init__(self, glyphset)
		self._glyphset = glyphset
		self._Pen = Pen
		self.value = []
	def _moveTo(self, p0):
		self._newItem()
		self.value[-1].moveTo(p0)
	def _lineTo(self, p1):
		self.value[-1].lineTo(p1)
	def _qCurveToOne(self, p1, p2):
		self.value[-1].qCurveTo(p1, p2)
	def _curveToOne(self, p1, p2, p3):
		self.value[-1].curveTo(p1, p2, p3)
	def _closePath(self):
		self.value[-1].closePath()
	def _endPath(self):
		self.value[-1].endPath()
	def addComponent(self, glyphName, transformation):
		self._newItem()
		self.value[-1].addComponent(glyphName, transformation)

	def _newItem(self):
		self.value.append(self._Pen(glyphset=self._glyphset))

class RecordingPen(BasePen):
	def __init__(self, glyphset):
		BasePen.__init__(self, glyphset)
		self._glyphset = glyphset
		self.value = []
	def _moveTo(self, p0):
		self.value.append(('moveTo', (p0,)))
	def _lineTo(self, p1):
		self.value.append(('lineTo', (p1,)))
	def _qCurveToOne(self, p1, p2):
		self.value.append(('qCurveTo', (p1,p2)))
	def _curveToOne(self, p1, p2, p3):
		self.value.append(('curveTo', (p1,p2,p3)))
	def _closePath(self):
		self.value.append(('closePath', ()))
	def _endPath(self):
		self.value.append(('endPath', ()))
	# Humm, adding the following method slows things down some 20%.
	# We don't have as much control as we like currently.
	#def addComponent(self, glyphName, transformation):
	#	self.value.append(('addComponent', (glyphName, transformation)))

	def draw(self, pen):
		for operator,operands in self.value:
			getattr(pen, operator)(*operands)

def _vdiff(v0, v1):
	return tuple(b-a for a,b in zip(v0,v1))
def _vlen(vec):
	v = 0
	for x in vec:
		v += x*x
	return v

def _matching_cost(G, matching):
	return sum(G[i][j] for i,j in enumerate(matching))

def min_cost_perfect_bipartite_matching(G):
	n = len(G)
	try:
		from scipy.optimize import linear_sum_assignment
		rows, cols = linear_sum_assignment(G)
		assert (rows == list(range(n))).all()
		return list(cols), _matching_cost(G, cols)
	except ImportError:
		pass

	try:
		from munkres import Munkres
		cols = [None] * n
		for row,col in Munkres().compute(G):
			cols[row] = col
		return cols, _matching_cost(G, cols)
	except ImportError:
		pass

	if n > 6:
		raise Exception("Install Python module 'munkres' or 'scipy >= 0.17.0'")

	# Otherwise just brute-force
	permutations = itertools.permutations(range(n))
	best = list(next(permutations))
	best_cost = _matching_cost(G, best)
	for p in permutations:
		cost = _matching_cost(G, p)
		if cost < best_cost:
			best, best_cost = list(p), cost
	return best, best_cost


def test(glyphsets, glyphs=None, names=None):

	if names is None:
		names = glyphsets
	if glyphs is None:
		glyphs = glyphsets[0].keys()

	hist = []
	for glyph_name in glyphs:
		#print()
		#print(glyph_name)

		try:
			allVectors = []
			for glyphset in glyphsets:
				#print('.', end='')
				#print()
				glyph = glyphset[glyph_name]

				perContourPen = PerContourOrComponentPen(RecordingPen, glyphset=glyphset)
				glyph.draw(perContourPen)
				contourPens = perContourPen.value
				del perContourPen

				contourVectors = []
				allVectors.append(contourVectors)
				for contour in contourPens:
					stats = GlyphStatistics(contour, glyphset=glyphset)
					vector = (
						int(stats.Perimeter * .125),
						int(abs(stats.Area) ** .5 * .5),
						int(stats.MeanX),
						int(stats.MeanY),
						int(stats.StdDevX * 2),
						int(stats.StdDevY * 2),
						int(stats.Covariance/(stats.StdDevX*stats.StdDevY)**.5),
					)
					contourVectors.append(vector)
					#print(vector)

			# Check each master against the next one in the list.
			for i,(m0,m1) in enumerate(zip(allVectors[:-1],allVectors[1:])):
				if len(m0) != len(m1):
					print('%s: %s+%s: Glyphs not compatible!!!!!' % (glyph_name, names[i], names[i+1]))
					continue
				if not m0:
					continue
				costs = [[_vlen(_vdiff(v0,v1)) for v1 in m1] for v0 in m0]
				matching, matching_cost = min_cost_perfect_bipartite_matching(costs)
				if matching != list(range(len(m0))):
					print('%s: %s+%s: Glyph has wrong contour/component order: %s' % (glyph_name, names[i], names[i+1], matching)) #, m0, m1)
					break
				upem = 2048
				item_cost = int(round((matching_cost / len(m0) / len(m0[0])) ** .5 / upem * 100))
				hist.append(item_cost)
				threshold = 7
				if item_cost >= threshold:
					print('%s: %s+%s: Glyph has very high cost: %d%%' % (glyph_name, names[i], names[i+1], item_cost))


		except ValueError as e:
			print('%s: math error %s; skipping glyph' % (glyph_name, e))
			#raise
	#for x in hist:
	#	print(x)

def main(args):
	filenames = args
	glyphs = None
	#glyphs = ['uni08DB', 'uniFD76']
	#glyphs = ['uni08DE', 'uni0034']
	#glyphs = ['uni08DE', 'uni0034', 'uni0751', 'uni0753', 'uni0754', 'uni08A4', 'uni08A4.fina', 'uni08A5.fina']

	from os.path import basename
	names = [basename(filename).rsplit('.', 1)[0] for filename in filenames]

	from fontTools.ttLib import TTFont
	fonts = [TTFont(filename) for filename in filenames]

	glyphsets = [font.getGlyphSet() for font in fonts]
	test(glyphsets, glyphs=glyphs, names=names)

if __name__ == '__main__':
	import sys
	main(sys.argv[1:])
