_accessstrings = {0: "", 1: "readonly", 2: "executeonly", 3: "noaccess"}


class ps_object(object):

	literal = 1
	access = 0
	value = None

	def __init__(self, value):
		self.value = value
		self.type = self.__class__.__name__[3:] + "type"

	def __repr__(self):
		return "<%s %s>" % (self.__class__.__name__[3:], repr(self.value))


class ps_operator(ps_object):

	literal = 0

	def __init__(self, name, function):
		self.name = name
		self.function = function
		self.type = self.__class__.__name__[3:] + "type"
	def __repr__(self):
		return "<operator %s>" % self.name

class ps_procedure(ps_object):
	literal = 0
	def __repr__(self):
		return "<procedure>"
	def __str__(self):
		psstring = '{'
		for i in range(len(self.value)):
			if i:
				psstring = psstring + ' ' + str(self.value[i])
			else:
				psstring = psstring + str(self.value[i])
		return psstring + '}'

class ps_name(ps_object):
	literal = 0
	def __str__(self):
		if self.literal:
			return '/' + self.value
		else:
			return self.value

class ps_literal(ps_object):
	def __str__(self):
		return '/' + self.value

class ps_array(ps_object):
	def __str__(self):
		psstring = '['
		for i in range(len(self.value)):
			item = self.value[i]
			access = _accessstrings[item.access]
			if access:
				access = ' ' + access
			if i:
				psstring = psstring + ' ' + str(item) + access
			else:
				psstring = psstring + str(item) + access
		return psstring + ']'
	def __repr__(self):
		return "<array>"

_type1_pre_eexec_order = [
		"FontInfo",
		"FontName",
		"Encoding",
		"PaintType",
		"FontType",
		"FontMatrix",
		"FontBBox",
		"UniqueID",
		"Metrics",
		"StrokeWidth"
	]

_type1_fontinfo_order = [
		"version",
		"Notice",
		"FullName",
		"FamilyName",
		"Weight",
		"ItalicAngle",
		"isFixedPitch",
		"UnderlinePosition",
		"UnderlineThickness"
	]

_type1_post_eexec_order = [
		"Private",
		"CharStrings",
		"FID"
	]

def _type1_item_repr(key, value):
	psstring = ""
	access = _accessstrings[value.access]
	if access:
		access = access + ' '
	if key == 'CharStrings':
		psstring = psstring + "/%s %s def\n" % (key, _type1_CharString_repr(value.value))
	elif key == 'Encoding':
		psstring = psstring + _type1_Encoding_repr(value, access)
	else:
		psstring = psstring + "/%s %s %sdef\n" % (str(key), str(value), access)
	return psstring

def _type1_Encoding_repr(encoding, access):
	encoding = encoding.value
	psstring = "/Encoding 256 array\n0 1 255 {1 index exch /.notdef put} for\n"
	for i in range(256):
		name = encoding[i].value
		if name != '.notdef':
			psstring = psstring + "dup %d /%s put\n" % (i, name)
	return psstring + access + "def\n"

def _type1_CharString_repr(charstrings):
	items = sorted(charstrings.items())
	return 'xxx'

class ps_font(ps_object):
	def __str__(self):
		psstring = "%d dict dup begin\n" % len(self.value)
		for key in _type1_pre_eexec_order:
			try:
				value = self.value[key]
			except KeyError:
				pass
			else:
				psstring = psstring + _type1_item_repr(key, value)
		items = sorted(self.value.items())
		for key, value in items:
			if key not in _type1_pre_eexec_order + _type1_post_eexec_order:
				psstring = psstring + _type1_item_repr(key, value)
		psstring = psstring + "currentdict end\ncurrentfile eexec\ndup "
		for key in _type1_post_eexec_order:
			try:
				value = self.value[key]
			except KeyError:
				pass
			else:
				psstring = psstring + _type1_item_repr(key, value)
		return psstring + 'dup/FontName get exch definefont pop\nmark currentfile closefile\n' + \
				8 * (64 * '0' + '\n') + 'cleartomark' + '\n'
	def __repr__(self):
		return '<font>'

class ps_file(ps_object):
	pass

class ps_dict(ps_object):
	def __str__(self):
		psstring = "%d dict dup begin\n" % len(self.value)
		items = sorted(self.value.items())
		for key, value in items:
			access = _accessstrings[value.access]
			if access:
				access = access + ' '
			psstring = psstring + "/%s %s %sdef\n" % (str(key), str(value), access)
		return psstring + 'end '
	def __repr__(self):
		return "<dict>"

class ps_mark(ps_object):
	def __init__(self):
		self.value = 'mark'
		self.type = self.__class__.__name__[3:] + "type"

class ps_procmark(ps_object):
	def __init__(self):
		self.value = 'procmark'
		self.type = self.__class__.__name__[3:] + "type"

class ps_null(ps_object):
	def __init__(self):
		self.type = self.__class__.__name__[3:] + "type"

class ps_boolean(ps_object):
	def __str__(self):
		if self.value:
			return 'true'
		else:
			return 'false'

class ps_string(ps_object):
	def __str__(self):
		return "(%s)" % repr(self.value)[1:-1]

class ps_integer(ps_object):
	def __str__(self):
		return repr(self.value)

class ps_real(ps_object):
	def __str__(self):
		return repr(self.value)


class PSOperators(object):

	def ps_def(self):
		obj = self.pop()
		name = self.pop()
		self.dictstack[-1][name.value] = obj

	def ps_bind(self):
		proc = self.pop('proceduretype')
		self.proc_bind(proc)
		self.push(proc)

	def proc_bind(self, proc):
		for i in range(len(proc.value)):
			item = proc.value[i]
			if item.type == 'proceduretype':
				self.proc_bind(item)
			else:
				if not item.literal:
					try:
						obj = self.resolve_name(item.value)
					except:
						pass
					else:
						if obj.type == 'operatortype':
							proc.value[i] = obj

	def ps_exch(self):
		if len(self.stack) < 2:
			raise RuntimeError('stack underflow')
		obj1 = self.pop()
		obj2 = self.pop()
		self.push(obj1)
		self.push(obj2)

	def ps_dup(self):
		if not self.stack:
			raise RuntimeError('stack underflow')
		self.push(self.stack[-1])

	def ps_exec(self):
		obj = self.pop()
		if obj.type == 'proceduretype':
			self.call_procedure(obj)
		else:
			self.handle_object(obj)

	def ps_count(self):
		self.push(ps_integer(len(self.stack)))

	def ps_eq(self):
		any1 = self.pop()
		any2 = self.pop()
		self.push(ps_boolean(any1.value == any2.value))

	def ps_ne(self):
		any1 = self.pop()
		any2 = self.pop()
		self.push(ps_boolean(any1.value != any2.value))

	def ps_cvx(self):
		obj = self.pop()
		obj.literal = 0
		self.push(obj)

	def ps_matrix(self):
		matrix = [ps_real(1.0), ps_integer(0), ps_integer(0), ps_real(1.0), ps_integer(0), ps_integer(0)]
		self.push(ps_array(matrix))

	def ps_string(self):
		num = self.pop('integertype').value
		self.push(ps_string('\0' * num))

	def ps_type(self):
		obj = self.pop()
		self.push(ps_string(obj.type))

	def ps_store(self):
		value = self.pop()
		key = self.pop()
		name = key.value
		for i in range(len(self.dictstack)-1, -1, -1):
			if name in self.dictstack[i]:
				self.dictstack[i][name] = value
				break
		self.dictstack[-1][name] = value

	def ps_where(self):
		name = self.pop()
		# XXX
		self.push(ps_boolean(0))

	def ps_systemdict(self):
		self.push(ps_dict(self.dictstack[0]))

	def ps_userdict(self):
		self.push(ps_dict(self.dictstack[1]))

	def ps_currentdict(self):
		self.push(ps_dict(self.dictstack[-1]))

	def ps_currentfile(self):
		self.push(ps_file(self.tokenizer))

	def ps_eexec(self):
		f = self.pop('filetype').value
		f.starteexec()

	def ps_closefile(self):
		f = self.pop('filetype').value
		f.skipwhite()
		f.stopeexec()

	def ps_cleartomark(self):
		obj = self.pop()
		while obj != self.mark:
			obj = self.pop()

	def ps_readstring(self,
			ps_boolean=ps_boolean,
			len=len):
		s = self.pop('stringtype')
		oldstr = s.value
		f = self.pop('filetype')
		#pad = file.value.read(1)
		# for StringIO, this is faster
		f.value.pos = f.value.pos + 1
		newstr = f.value.read(len(oldstr))
		s.value = newstr
		self.push(s)
		self.push(ps_boolean(len(oldstr) == len(newstr)))

	def ps_known(self):
		key = self.pop()
		d = self.pop('dicttype', 'fonttype')
		self.push(ps_boolean(key.value in d.value))

	def ps_if(self):
		proc = self.pop('proceduretype')
		if self.pop('booleantype').value:
			self.call_procedure(proc)

	def ps_ifelse(self):
		proc2 = self.pop('proceduretype')
		proc1 = self.pop('proceduretype')
		if self.pop('booleantype').value:
			self.call_procedure(proc1)
		else:
			self.call_procedure(proc2)

	def ps_readonly(self):
		obj = self.pop()
		if obj.access < 1:
			obj.access = 1
		self.push(obj)

	def ps_executeonly(self):
		obj = self.pop()
		if obj.access < 2:
			obj.access = 2
		self.push(obj)

	def ps_noaccess(self):
		obj = self.pop()
		if obj.access < 3:
			obj.access = 3
		self.push(obj)

	def ps_not(self):
		obj = self.pop('booleantype', 'integertype')
		if obj.type == 'booleantype':
			self.push(ps_boolean(not obj.value))
		else:
			self.push(ps_integer(~obj.value))

	def ps_print(self):
		str = self.pop('stringtype')
		print('PS output --->', str.value)

	def ps_anchorsearch(self):
		seek = self.pop('stringtype')
		s = self.pop('stringtype')
		seeklen = len(seek.value)
		if s.value[:seeklen] == seek.value:
			self.push(ps_string(s.value[seeklen:]))
			self.push(seek)
			self.push(ps_boolean(1))
		else:
			self.push(s)
			self.push(ps_boolean(0))

	def ps_array(self):
		num = self.pop('integertype')
		array = ps_array([None] * num.value)
		self.push(array)

	def ps_astore(self):
		array = self.pop('arraytype')
		for i in range(len(array.value)-1, -1, -1):
			array.value[i] = self.pop()
		self.push(array)

	def ps_load(self):
		name = self.pop()
		self.push(self.resolve_name(name.value))

	def ps_put(self):
		obj1 = self.pop()
		obj2 = self.pop()
		obj3 = self.pop('arraytype', 'dicttype', 'stringtype', 'proceduretype')
		tp = obj3.type
		if tp == 'arraytype' or tp == 'proceduretype':
			obj3.value[obj2.value] = obj1
		elif tp == 'dicttype':
			obj3.value[obj2.value] = obj1
		elif tp == 'stringtype':
			index = obj2.value
			obj3.value = obj3.value[:index] + chr(obj1.value) + obj3.value[index+1:]

	def ps_get(self):
		obj1 = self.pop()
		if obj1.value == "Encoding":
			pass
		obj2 = self.pop('arraytype', 'dicttype', 'stringtype', 'proceduretype', 'fonttype')
		tp = obj2.type
		if tp in ('arraytype', 'proceduretype'):
			self.push(obj2.value[obj1.value])
		elif tp in ('dicttype', 'fonttype'):
			self.push(obj2.value[obj1.value])
		elif tp == 'stringtype':
			self.push(ps_integer(ord(obj2.value[obj1.value])))
		else:
			assert False, "shouldn't get here"

	def ps_getinterval(self):
		obj1 = self.pop('integertype')
		obj2 = self.pop('integertype')
		obj3 = self.pop('arraytype', 'stringtype')
		tp = obj3.type
		if tp == 'arraytype':
			self.push(ps_array(obj3.value[obj2.value:obj2.value + obj1.value]))
		elif tp == 'stringtype':
			self.push(ps_string(obj3.value[obj2.value:obj2.value + obj1.value]))

	def ps_putinterval(self):
		obj1 = self.pop('arraytype', 'stringtype')
		obj2 = self.pop('integertype')
		obj3 = self.pop('arraytype', 'stringtype')
		tp = obj3.type
		if tp == 'arraytype':
			obj3.value[obj2.value:obj2.value + len(obj1.value)] = obj1.value
		elif tp == 'stringtype':
			newstr = obj3.value[:obj2.value]
			newstr = newstr + obj1.value
			newstr = newstr + obj3.value[obj2.value + len(obj1.value):]
			obj3.value = newstr

	def ps_cvn(self):
		self.push(ps_name(self.pop('stringtype').value))

	def ps_index(self):
		n = self.pop('integertype').value
		if n < 0:
			raise RuntimeError('index may not be negative')
		self.push(self.stack[-1-n])

	def ps_for(self):
		proc = self.pop('proceduretype')
		limit = self.pop('integertype', 'realtype').value
		increment = self.pop('integertype', 'realtype').value
		i = self.pop('integertype', 'realtype').value
		while 1:
			if increment > 0:
				if i > limit:
					break
			else:
				if i < limit:
					break
			if type(i) == type(0.0):
				self.push(ps_real(i))
			else:
				self.push(ps_integer(i))
			self.call_procedure(proc)
			i = i + increment

	def ps_forall(self):
		proc = self.pop('proceduretype')
		obj = self.pop('arraytype', 'stringtype', 'dicttype')
		tp = obj.type
		if tp == 'arraytype':
			for item in obj.value:
				self.push(item)
				self.call_procedure(proc)
		elif tp == 'stringtype':
			for item in obj.value:
				self.push(ps_integer(ord(item)))
				self.call_procedure(proc)
		elif tp == 'dicttype':
			for key, value in obj.value.items():
				self.push(ps_name(key))
				self.push(value)
				self.call_procedure(proc)

	def ps_definefont(self):
		font = self.pop('dicttype')
		name = self.pop()
		font = ps_font(font.value)
		self.dictstack[0]['FontDirectory'].value[name.value] = font
		self.push(font)

	def ps_findfont(self):
		name = self.pop()
		font = self.dictstack[0]['FontDirectory'].value[name.value]
		self.push(font)

	def ps_pop(self):
		self.pop()

	def ps_dict(self):
		self.pop('integertype')
		self.push(ps_dict({}))

	def ps_begin(self):
		self.dictstack.append(self.pop('dicttype').value)

	def ps_end(self):
		if len(self.dictstack) > 2:
			del self.dictstack[-1]
		else:
			raise RuntimeError('dictstack underflow')

notdef = '.notdef'
from fontTools.encodings.StandardEncoding import StandardEncoding
ps_StandardEncoding = list(map(ps_name, StandardEncoding))
