from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from . import DefaultTable
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
import struct

VDMX_HeaderFmt = """
	>                 # big endian
	version:     H    # Version number (0 or 1)
	numRecs:     H    # Number of VDMX groups present
	numRatios:   H    # Number of aspect ratio groupings
"""
# the VMDX header is followed by an array of RatRange[numRatios] (i.e. aspect
# ratio ranges);
VDMX_RatRangeFmt = """
	>                 # big endian
	bCharSet:    B    # Character set
	xRatio:      B    # Value to use for x-Ratio
	yStartRatio: B    # Starting y-Ratio value
	yEndRatio:   B    # Ending y-Ratio value
"""
# followed by an array of offset[numRatios] from start of VDMX table to the
# VDMX Group for this ratio range (offsets will be re-calculated on compile);
# followed by an array of Group[numRecs] records;
VDMX_GroupFmt = """
	>                 # big endian
	recs:        H    # Number of height records in this group
	startsz:     B    # Starting yPelHeight
	endsz:       B    # Ending yPelHeight
"""
# followed by an array of vTable[recs] records.
VDMX_vTableFmt = """
	>                 # big endian
	yPelHeight:  H    # yPelHeight to which values apply
	yMax:        h    # Maximum value (in pels) for this yPelHeight
	yMin:        h    # Minimum value (in pels) for this yPelHeight
"""


class table_V_D_M_X_(DefaultTable.DefaultTable):

	def decompile(self, data, ttFont):
		pos = 0  # track current position from to start of VDMX table
		dummy, data = sstruct.unpack2(VDMX_HeaderFmt, data, self)
		pos += sstruct.calcsize(VDMX_HeaderFmt)
		self.ratRanges = []
		for i in range(self.numRatios):
			ratio, data = sstruct.unpack2(VDMX_RatRangeFmt, data)
			pos += sstruct.calcsize(VDMX_RatRangeFmt)
			# the mapping between a ratio and a group is defined further below
			ratio['groupIndex'] = None
			self.ratRanges.append(ratio)
		lenOffset = struct.calcsize('>H')
		_offsets = []  # temporarily store offsets to groups
		for i in range(self.numRatios):
			offset = struct.unpack('>H', data[0:lenOffset])[0]
			data = data[lenOffset:]
			pos += lenOffset
			_offsets.append(offset)
		self.groups = []
		for groupIndex in range(self.numRecs):
			# the offset to this group from beginning of the VDMX table
			currOffset = pos
			group, data = sstruct.unpack2(VDMX_GroupFmt, data)
			# the group lenght and bounding sizes are re-calculated on compile
			recs = group.pop('recs')
			startsz = group.pop('startsz')
			endsz = group.pop('endsz')
			pos += sstruct.calcsize(VDMX_GroupFmt)
			for j in range(recs):
				vTable, data = sstruct.unpack2(VDMX_vTableFmt, data)
				vTableLength = sstruct.calcsize(VDMX_vTableFmt)
				pos += vTableLength
				# group is a dict of (yMax, yMin) tuples keyed by yPelHeight
				group[vTable['yPelHeight']] = (vTable['yMax'], vTable['yMin'])
			# make sure startsz and endsz match the calculated values
			minSize = min(group.keys())
			maxSize = max(group.keys())
			assert startsz == minSize, \
				"startsz (%s) must equal min yPelHeight (%s): group %d" % \
				(group.startsz, minSize, groupIndex)
			assert endsz == maxSize, \
				"endsz (%s) must equal max yPelHeight (%s): group %d" % \
				(group.endsz, maxSize, groupIndex)
			self.groups.append(group)
			# match the defined offsets with the current group's offset
			for offsetIndex, offsetValue in enumerate(_offsets):
				# when numRecs < numRatios there can more than one ratio range
				# sharing the same VDMX group
				if currOffset == offsetValue:
					# map the group with the ratio range thas has the same
					# index as the offset to that group (it took me a while..)
					self.ratRanges[offsetIndex]['groupIndex'] = groupIndex
		# check that all ratio ranges have a group
		for i in range(self.numRatios):
			ratio = self.ratRanges[i]
			if ratio['groupIndex'] is None:
				from fontTools import ttLib
				raise ttLib.TTLibError(
					"no group defined for ratRange %d" % i)

	def _getOffsets(self):
		"""
		Calculate offsets to VDMX_Group records.
		For each ratRange return a list of offset values from the beginning of
		the VDMX table to a VDMX_Group.
		"""
		lenHeader = sstruct.calcsize(VDMX_HeaderFmt)
		lenRatRange = sstruct.calcsize(VDMX_RatRangeFmt)
		lenOffset = struct.calcsize('>H')
		lenGroupHeader = sstruct.calcsize(VDMX_GroupFmt)
		lenVTable = sstruct.calcsize(VDMX_vTableFmt)
		# offset to the first group
		pos = lenHeader + self.numRatios*lenRatRange + self.numRatios*lenOffset
		groupOffsets = []
		for group in self.groups:
			groupOffsets.append(pos)
			lenGroup = lenGroupHeader + len(group) * lenVTable
			pos += lenGroup  # offset to next group
		offsets = []
		for ratio in self.ratRanges:
			groupIndex = ratio['groupIndex']
			offsets.append(groupOffsets[groupIndex])
		return offsets

	def compile(self, ttFont):
		if not(self.version == 0 or self.version == 1):
			from fontTools import ttLib
			raise ttLib.TTLibError(
				"unknown format for VDMX table: version %s" % self.version)
		data = sstruct.pack(VDMX_HeaderFmt, self)
		for ratio in self.ratRanges:
			data += sstruct.pack(VDMX_RatRangeFmt, ratio)
		# recalculate offsets to VDMX groups
		for offset in self._getOffsets():
			data += struct.pack('>H', offset)
		for group in self.groups:
			recs = len(group)
			startsz = min(group.keys())
			endsz = max(group.keys())
			gHeader = {'recs': recs, 'startsz': startsz, 'endsz': endsz}
			data += sstruct.pack(VDMX_GroupFmt, gHeader)
			for yPelHeight, (yMax, yMin) in sorted(group.items()):
				vTable = {'yPelHeight': yPelHeight, 'yMax': yMax, 'yMin': yMin}
				data += sstruct.pack(VDMX_vTableFmt, vTable)
		return data

	def toXML(self, writer, ttFont):
		writer.simpletag("version", value=self.version)
		writer.newline()
		writer.begintag("ratRanges")
		writer.newline()
		for ratio in self.ratRanges:
			groupIndex = ratio['groupIndex']
			writer.simpletag(
				"ratRange",
				bCharSet=ratio['bCharSet'],
				xRatio=ratio['xRatio'],
				yStartRatio=ratio['yStartRatio'],
				yEndRatio=ratio['yEndRatio'],
				groupIndex=groupIndex
				)
			writer.newline()
		writer.endtag("ratRanges")
		writer.newline()
		writer.begintag("groups")
		writer.newline()
		for groupIndex in range(self.numRecs):
			group = self.groups[groupIndex]
			recs = len(group)
			startsz = min(group.keys())
			endsz = max(group.keys())
			writer.begintag("group", index=groupIndex)
			writer.newline()
			writer.comment("recs=%d, startsz=%d, endsz=%d" %
							(recs, startsz, endsz))
			writer.newline()
			for yPelHeight, (yMax, yMin) in sorted(group.items()):
				writer.simpletag(
					"record",
					[('yPelHeight', yPelHeight), ('yMax', yMax), ('yMin', yMin)])
				writer.newline()
			writer.endtag("group")
			writer.newline()
		writer.endtag("groups")
		writer.newline()

	def fromXML(self, name, attrs, content, ttFont):
		if name == "version":
			self.version = safeEval(attrs["value"])
		elif name == "ratRanges":
			if not hasattr(self, "ratRanges"):
				self.ratRanges = []
			for element in content:
				if not isinstance(element, tuple):
					continue
				name, attrs, content = element
				if name == "ratRange":
					if not hasattr(self, "numRatios"):
						self.numRatios = 1
					else:
						self.numRatios += 1
					ratio = {
						"bCharSet": safeEval(attrs["bCharSet"]),
						"xRatio": safeEval(attrs["xRatio"]),
						"yStartRatio": safeEval(attrs["yStartRatio"]),
						"yEndRatio": safeEval(attrs["yEndRatio"]),
						"groupIndex": safeEval(attrs["groupIndex"])
						}
					self.ratRanges.append(ratio)
		elif name == "groups":
			if not hasattr(self, "groups"):
				self.groups = []
			for element in content:
				if not isinstance(element, tuple):
					continue
				name, attrs, content = element
				if name == "group":
					if not hasattr(self, "numRecs"):
						self.numRecs = 1
					else:
						self.numRecs += 1
					group = {}
					for element in content:
						if not isinstance(element, tuple):
							continue
						name, attrs, content = element
						if name == "record":
							yPelHeight = safeEval(attrs["yPelHeight"])
							yMax = safeEval(attrs["yMax"])
							yMin = safeEval(attrs["yMin"])
							group[yPelHeight] = (yMax, yMin)
					self.groups.append(group)
