from fontTools.misc.roundTools import otRound
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import sys
import struct
import array
import logging


log = logging.getLogger(__name__)


class table__h_m_t_x(DefaultTable.DefaultTable):

	headerTag = 'hhea'
	advanceName = 'width'
	sideBearingName = 'lsb'
	numberOfMetricsName = 'numberOfHMetrics'
	longMetricFormat = 'Hh'

	def decompile(self, data, ttFont):
		numGlyphs = ttFont['maxp'].numGlyphs
		headerTable = ttFont.get(self.headerTag)
		if headerTable is not None:
			numberOfMetrics = int(getattr(headerTable, self.numberOfMetricsName))
		else:
			numberOfMetrics = numGlyphs
		if numberOfMetrics > numGlyphs:
			log.warning("The %s.%s exceeds the maxp.numGlyphs" % (
				self.headerTag, self.numberOfMetricsName))
			numberOfMetrics = numGlyphs
		if len(data) < 4 * numberOfMetrics:
			raise ttLib.TTLibError("not enough '%s' table data" % self.tableTag)
		# Note: advanceWidth is unsigned, but some font editors might
		# read/write as signed. We can't be sure whether it was a mistake
		# or not, so we read as unsigned but also issue a warning...
		metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
		metrics = struct.unpack(metricsFmt, data[:4 * numberOfMetrics])
		data = data[4 * numberOfMetrics:]
		numberOfSideBearings = numGlyphs - numberOfMetrics
		sideBearings = array.array("h", data[:2 * numberOfSideBearings])
		data = data[2 * numberOfSideBearings:]

		if sys.byteorder != "big": sideBearings.byteswap()
		if data:
			log.warning("too much '%s' table data" % self.tableTag)
		self.metrics = {}
		glyphOrder = ttFont.getGlyphOrder()
		for i in range(numberOfMetrics):
			glyphName = glyphOrder[i]
			advanceWidth, lsb = metrics[i*2:i*2+2]
			if advanceWidth > 32767:
				log.warning(
					"Glyph %r has a huge advance %s (%d); is it intentional or "
					"an (invalid) negative value?", glyphName, self.advanceName,
					advanceWidth)
			self.metrics[glyphName] = (advanceWidth, lsb)
		lastAdvance = metrics[-2]
		for i in range(numberOfSideBearings):
			glyphName = glyphOrder[i + numberOfMetrics]
			self.metrics[glyphName] = (lastAdvance, sideBearings[i])

	def compile(self, ttFont):
		metrics = []
		hasNegativeAdvances = False
		for glyphName in ttFont.getGlyphOrder():
			advanceWidth, sideBearing = self.metrics[glyphName]
			if advanceWidth < 0:
				log.error("Glyph %r has negative advance %s" % (
					glyphName, self.advanceName))
				hasNegativeAdvances = True
			metrics.append([advanceWidth, sideBearing])

		headerTable = ttFont.get(self.headerTag)
		if headerTable is not None:
			lastAdvance = metrics[-1][0]
			lastIndex = len(metrics)
			while metrics[lastIndex-2][0] == lastAdvance:
				lastIndex -= 1
				if lastIndex <= 1:
					# all advances are equal
					lastIndex = 1
					break
			additionalMetrics = metrics[lastIndex:]
			additionalMetrics = [otRound(sb) for _, sb in additionalMetrics]
			metrics = metrics[:lastIndex]
			numberOfMetrics = len(metrics)
			setattr(headerTable, self.numberOfMetricsName, numberOfMetrics)
		else:
			# no hhea/vhea, can't store numberOfMetrics; assume == numGlyphs
			numberOfMetrics = ttFont["maxp"].numGlyphs
			additionalMetrics = []

		allMetrics = []
		for advance, sb in metrics:
			allMetrics.extend([otRound(advance), otRound(sb)])
		metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
		try:
			data = struct.pack(metricsFmt, *allMetrics)
		except struct.error as e:
			if "out of range" in str(e) and hasNegativeAdvances:
				raise ttLib.TTLibError(
					"'%s' table can't contain negative advance %ss"
					% (self.tableTag, self.advanceName))
			else:
				raise
		additionalMetrics = array.array("h", additionalMetrics)
		if sys.byteorder != "big": additionalMetrics.byteswap()
		data = data + additionalMetrics.tobytes()
		return data

	def toXML(self, writer, ttFont):
		names = sorted(self.metrics.keys())
		for glyphName in names:
			advance, sb = self.metrics[glyphName]
			writer.simpletag("mtx", [
					("name", glyphName),
					(self.advanceName, advance),
					(self.sideBearingName, sb),
					])
			writer.newline()

	def fromXML(self, name, attrs, content, ttFont):
		if not hasattr(self, "metrics"):
			self.metrics = {}
		if name == "mtx":
			self.metrics[attrs["name"]] = (safeEval(attrs[self.advanceName]),
					safeEval(attrs[self.sideBearingName]))

	def __delitem__(self, glyphName):
		del self.metrics[glyphName]

	def __getitem__(self, glyphName):
		return self.metrics[glyphName]

	def __setitem__(self, glyphName, advance_sb_pair):
		self.metrics[glyphName] = tuple(advance_sb_pair)
