# -*- coding: utf-8 -*-
#
# AWL data types
#
# Copyright 2012-2014 Michael Buesch <m@bues.ch>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

from __future__ import division, absolute_import, print_function, unicode_literals
from awlsim.common.compat import *

from awlsim.common.datatypehelpers import *
from awlsim.common.wordpacker import *

from awlsim.core.util import *
from awlsim.core.timers import *
from awlsim.core.offset import *


class AwlDataType(object):
	# Data type IDs
	EnumGen.start
	TYPE_VOID	= EnumGen.item
	TYPE_BOOL	= EnumGen.item
	TYPE_BYTE	= EnumGen.item
	TYPE_WORD	= EnumGen.item
	TYPE_DWORD	= EnumGen.item
	TYPE_INT	= EnumGen.item
	TYPE_DINT	= EnumGen.item
	TYPE_REAL	= EnumGen.item
	TYPE_S5T	= EnumGen.item
	TYPE_TIME	= EnumGen.item
	TYPE_DATE	= EnumGen.item
	TYPE_DT		= EnumGen.item
	TYPE_TOD	= EnumGen.item
	TYPE_CHAR	= EnumGen.item
	TYPE_ARRAY	= EnumGen.item
	TYPE_TIMER	= EnumGen.item
	TYPE_COUNTER	= EnumGen.item
	TYPE_BLOCK_DB	= EnumGen.item # DB number type
	TYPE_BLOCK_FB	= EnumGen.item # FB number type
	TYPE_BLOCK_FC	= EnumGen.item # FC number type
	TYPE_DB_X	= EnumGen.item # DBx type
	TYPE_OB_X	= EnumGen.item # OBx type
	TYPE_FC_X	= EnumGen.item # FCx type
	TYPE_SFC_X	= EnumGen.item # SFCx type
	TYPE_FB_X	= EnumGen.item # FBx type
	TYPE_SFB_X	= EnumGen.item # SFBx type
	TYPE_UDT_X	= EnumGen.item # UDTx type
	TYPE_VAT_X	= EnumGen.item # VATx type
	EnumGen.end

	__name2id = {
		"VOID"		: TYPE_VOID,
		"BOOL"		: TYPE_BOOL,
		"BYTE"		: TYPE_BYTE,
		"WORD"		: TYPE_WORD,
		"DWORD"		: TYPE_DWORD,
		"INT"		: TYPE_INT,
		"DINT"		: TYPE_DINT,
		"REAL"		: TYPE_REAL,
		"S5TIME"	: TYPE_S5T,
		"TIME"		: TYPE_TIME,
		"DATE"		: TYPE_DATE,
		"DATE_AND_TIME"	: TYPE_DT,
		"TIME_OF_DAY"	: TYPE_TOD,
		"CHAR"		: TYPE_CHAR,
		"ARRAY"		: TYPE_ARRAY,
		"TIMER"		: TYPE_TIMER,
		"COUNTER"	: TYPE_COUNTER,
		"BLOCK_DB"	: TYPE_BLOCK_DB,
		"BLOCK_FB"	: TYPE_BLOCK_FB,
		"BLOCK_FC"	: TYPE_BLOCK_FC,
		"DB"		: TYPE_DB_X,
		"OB"		: TYPE_OB_X,
		"FC"		: TYPE_FC_X,
		"SFC"		: TYPE_SFC_X,
		"FB"		: TYPE_FB_X,
		"SFB"		: TYPE_SFB_X,
		"UDT"		: TYPE_UDT_X,
		"VAT"		: TYPE_VAT_X,
	}
	__id2name = pivotDict(__name2id)

	# Width table for types
	# -1 => Type width must be calculated
	type2width = {
		TYPE_VOID	: 0,
		TYPE_BOOL	: 1,
		TYPE_BYTE	: 8,
		TYPE_WORD	: 16,
		TYPE_DWORD	: 32,
		TYPE_INT	: 16,
		TYPE_DINT	: 32,
		TYPE_REAL	: 32,
		TYPE_S5T	: 16,
		TYPE_TIME	: 32,
		TYPE_DATE	: 16,
		TYPE_DT		: 64,
		TYPE_TOD	: 32,
		TYPE_CHAR	: 8,
		TYPE_ARRAY	: -1,
		TYPE_TIMER	: 16,
		TYPE_COUNTER	: 16,
		TYPE_BLOCK_DB	: 16,
		TYPE_BLOCK_FB	: 16,
		TYPE_BLOCK_FC	: 16,
		TYPE_DB_X	: -1,
		TYPE_OB_X	: 0,
		TYPE_FC_X	: 0,
		TYPE_SFC_X	: 0,
		TYPE_FB_X	: -1,
		TYPE_SFB_X	: -1,
		TYPE_UDT_X	: -1,
		TYPE_VAT_X	: 0,
	}

	# Table of trivial types with sign
	signedTypes = (
		TYPE_INT,
		TYPE_DINT,
		TYPE_REAL,
	)

	# Convert a list of array dimensions into a number of elements.
	@classmethod
	def arrayDimensionsToNrElements(cls, dimensions):
		assert(dimensions is not None)
		assert(len(dimensions) >= 1 and len(dimensions) <= 6)
		nrElems = reduce(lambda a, b: a * (b[1] - b[0] + 1),
				 dimensions, 1)
		return nrElems

	@classmethod
	def _name2typeid(cls, nameTokens):
		nameTokens = toList(nameTokens)
		try:
			return cls.__name2id[nameTokens[0].upper()]
		except (KeyError, IndexError) as e:
			raise AwlSimError("Invalid data type name: " +\
					  nameTokens[0] if len(nameTokens) else "None")

	@classmethod
	def makeByName(cls, nameTokens, arrayDimensions=None):
		type = cls._name2typeid(nameTokens)
		index = None
		if type == cls.TYPE_ARRAY:
			raise AwlSimError("Nested ARRAYs are not allowed")
		elif type in (cls.TYPE_DB_X,
			      cls.TYPE_OB_X,
			      cls.TYPE_FC_X,
			      cls.TYPE_SFC_X,
			      cls.TYPE_FB_X,
			      cls.TYPE_SFB_X,
			      cls.TYPE_UDT_X):
			if len(nameTokens) < 2:
				raise AwlSimError("Invalid '%s' block data type" %\
					nameTokens[0])
			blockNumber = cls.tryParseImmediate_INT(nameTokens[1])
			if blockNumber is None:
				raise AwlSimError("Invalid '%s' block data type "\
					"index" % nameTokens[0])
			index = blockNumber
		if arrayDimensions:
			# An ARRAY is to be constructed.
			nrArrayElements = cls.arrayDimensionsToNrElements(arrayDimensions)
			elementType = cls(type = type,
					  width = cls.type2width[type],
					  isSigned = (type in cls.signedTypes),
					  index = index)
			# Make a tuple of children types.
			# Each element is a ref to the same AwlDataType instance.
			children = tuple([ elementType, ] * nrArrayElements)
			return cls(type = cls.TYPE_ARRAY,
				   width = nrArrayElements * elementType.width,
				   isSigned = (type in cls.signedTypes),
				   index = index,
				   children = children,
				   arrayDimensions = arrayDimensions)
		else:
			return cls(type = type,
				   width = cls.type2width[type],
				   isSigned = (type in cls.signedTypes),
				   index = index)

	def __init__(self, type, width, isSigned,
		     index=None, children=None,
		     arrayDimensions=None):
		self.type = type		# The TYPE_... for this datatype
		self.width = width		# The width, in bits.
		self.isSigned = isSigned	# True, if this type is signed
		self.index = index		# The Index number, if any. May be None
		self.children = children	# The children AwlDataTypes, if ARRAY.
		self.arrayDimensions = arrayDimensions # The ARRAY's dimensions

	# Convert array indices into a one dimensional index for this array.
	# 'indices' is a list of index integers as written in the AWL operator
	# from left to right.
	def arrayIndicesCollapse(self, indices):
		if not indices:
			return None
		if len(indices) < 1 or len(indices) > 6:
			raise AwlSimError("Invalid number of array indices")
		assert(self.type == self.TYPE_ARRAY)
		signif = self.arrayIndexSignificances()
		assert(len(indices) == len(signif))
		assert(len(indices) == len(self.arrayDimensions))
		resIndex = 0
		for i, idx in enumerate(indices):
			startIdx, endIdx = self.arrayDimensions[i]
			if idx < startIdx or idx > endIdx:
				raise AwlSimError("Array index '%d' is out of bounds "
					"for array range '%d .. %d'." %\
					(idx, startIdx, endIdx))
			resIndex += (idx - startIdx) * signif[i]
		return resIndex

	# Get the array dimension sizes. Returns a tuple of integers.
	def arrayDimSizes(self):
		assert(self.type == self.TYPE_ARRAY)
		return tuple(end - start + 1
			     for start, end in self.arrayDimensions)

	# Get the array index significances. Returns a tuple of integers.
	def arrayIndexSignificances(self):
		assert(self.type == self.TYPE_ARRAY)
		sizes = self.arrayDimSizes()
		signif = [ 1, ]
		for size in sizes[:0:-1]:
			signif.append(size * signif[-1])
		return tuple(signif[::-1])

	# Get the number of array elements
	def arrayGetNrElements(self):
		assert(self.type == self.TYPE_ARRAY)
		return len(self.children)

	# Parse an immediate, constrained by our datatype.
	def parseMatchingImmediate(self, tokens):
		typeId = self.type
		if typeId == self.TYPE_ARRAY:
			typeId = self.children[0].type

		value = None
		if tokens is None:
			value = 0
		elif len(tokens) == 9:
			if typeId == self.TYPE_DWORD:
				value, fields = self.tryParseImmediate_ByteArray(
							tokens)
		elif len(tokens) == 5:
			if typeId == self.TYPE_WORD:
				value, fields = self.tryParseImmediate_ByteArray(
							tokens)
		elif len(tokens) == 2:
			if typeId == self.TYPE_TIMER:
				if tokens[0].upper() == "T":
					value = self.tryParseImmediate_INT(tokens[1])
			elif typeId == self.TYPE_COUNTER:
				if tokens[0].upper() in ("C", "Z"):
					value = self.tryParseImmediate_INT(tokens[1])
			elif typeId == self.TYPE_BLOCK_DB:
				if tokens[0].upper() == "DB":
					value = self.tryParseImmediate_INT(tokens[1])
			elif typeId == self.TYPE_BLOCK_FB:
				if tokens[0].upper() == "FB":
					value = self.tryParseImmediate_INT(tokens[1])
			elif typeId == self.TYPE_BLOCK_FC:
				if tokens[0].upper() == "FC":
					value = self.tryParseImmediate_INT(tokens[1])
		elif len(tokens) == 1:
			if typeId == self.TYPE_BOOL:
				value = self.tryParseImmediate_BOOL(
						tokens[0])
			elif typeId == self.TYPE_BYTE:
				value = self.tryParseImmediate_HexByte(
						tokens[0])
			elif typeId == self.TYPE_WORD:
				value = self.tryParseImmediate_Bin(
						tokens[0])
				if value is None:
					value = self.tryParseImmediate_HexWord(
							tokens[0])
				if value is None:
					value = self.tryParseImmediate_BCD(
							tokens[0])
			elif typeId == self.TYPE_DWORD:
				value = self.tryParseImmediate_Bin(
						tokens[0])
				if value is None:
					value = self.tryParseImmediate_HexDWord(
							tokens[0])
			elif typeId == self.TYPE_INT:
				value = self.tryParseImmediate_INT(
						tokens[0])
			elif typeId == self.TYPE_DINT:
				value = self.tryParseImmediate_DINT(
						tokens[0])
			elif typeId == self.TYPE_REAL:
				value = self.tryParseImmediate_REAL(
						tokens[0])
			elif typeId == self.TYPE_S5T:
				value = self.tryParseImmediate_S5T(
						tokens[0])
			elif typeId == self.TYPE_TIME:
				value = self.tryParseImmediate_TIME(
						tokens[0])
			elif typeId == self.TYPE_DATE:
				pass#TODO
			elif typeId == self.TYPE_DT:
				pass#TODO
			elif typeId == self.TYPE_TOD:
				pass#TODO
			elif typeId == self.TYPE_CHAR:
				value = self.tryParseImmediate_CHAR(
						tokens[0])
		if value is None:
			raise AwlSimError("Immediate value '%s' does "
				"not match data type '%s'" %\
				(" ".join(tokens), str(self)))
		return value

	def __repr__(self):
		if self.type == self.TYPE_ARRAY:
			return "ARRAY" #TODO
		elif self.type in (self.TYPE_DB_X,
				   self.TYPE_OB_X,
				   self.TYPE_FC_X,
				   self.TYPE_SFC_X,
				   self.TYPE_FB_X,
				   self.TYPE_SFB_X,
				   self.TYPE_UDT_X):
			return "%s %d" % (
				self.__id2name[self.type],
				self.index,
			)
		try:
			return self.__id2name[self.type]
		except KeyError:
			raise AwlSimError("Invalid data type: " +\
					  str(type))

	@classmethod
	def tryParseImmediate_BOOL(cls, token):
		token = token.upper().strip()
		if token == "TRUE":
			return 1
		elif token == "FALSE":
			return 0
		return None

	@classmethod
	def tryParseImmediate_INT(cls, token):
		try:
			immediate = int(token, 10)
			if immediate > 32767 or immediate < -32768:
				raise AwlSimError("16-bit immediate overflow")
		except ValueError:
			return None
		return immediate

	@classmethod
	def tryParseImmediate_DINT(cls, token):
		token = token.upper()
		if not token.startswith("L#"):
			return None
		try:
			immediate = int(token[2:], 10)
			if immediate > 2147483647 or\
			   immediate < -2147483648:
				raise AwlSimError("32-bit immediate overflow")
			immediate &= 0xFFFFFFFF
		except ValueError as e:
			raise AwlSimError("Invalid immediate")
		return immediate

	@classmethod
	def tryParseImmediate_BCD_word(cls, token):
		token = token.upper()
		if not token.startswith("C#"):
			return None
		try:
			cnt = token[2:]
			if len(cnt) < 1 or len(cnt) > 3:
				raise ValueError
			a, b, c = 0, 0, 0
			if cnt:
				a = int(cnt[-1], 10)
				cnt = cnt[:-1]
			if cnt:
				b = int(cnt[-1], 10)
				cnt = cnt[:-1]
			if cnt:
				c = int(cnt[-1], 10)
				cnt = cnt[:-1]
			immediate = a | (b << 4) | (c << 8)
		except ValueError as e:
			raise AwlSimError("Invalid C# immediate")
		return immediate

	@classmethod
	def tryParseImmediate_REAL(cls, token):
		try:
			immediate = float(token)
			immediate = pyFloatToDWord(immediate)
		except ValueError:
			return None
		return immediate

	@classmethod
	def __parseGenericTime(cls, token):
		# Parse T# or S5T# time formats.
		# The prefix is already stripped.
		if not token:
			raise AwlSimError("Invalid time")
		token = token.upper()
		p = token
		seconds = 0.0
		while p:
			if p.endswith("MS"):
				mult = 0.001
				p = p[:-2]
			elif p.endswith("S"):
				mult = 1.0
				p = p[:-1]
			elif p.endswith("M"):
				mult = 60.0
				p = p[:-1]
			elif p.endswith("H"):
				mult = 3600.0
				p = p[:-1]
			elif p.endswith("D"):
				mult = 86400.0
				p = p[:-1]
			else:
				raise AwlSimError("Invalid time")
			if not p:
				raise AwlSimError("Invalid time")
			num = ""
			while p and p[-1] in "0123456789":
				num = p[-1] + num
				p = p[:-1]
			if not num:
				raise AwlSimError("Invalid time")
			num = int(num, 10)
			seconds += num * mult
		return seconds

	@classmethod
	def formatTime(cls, seconds, leadingZeros=False):
		# Format a seconds value into time format.
		d = int(seconds // 86400)
		seconds -= d * 86400
		h = int(seconds // 3600)
		seconds -= h * 3600
		m = int(seconds // 60)
		seconds -= m * 60
		s = int(seconds)
		seconds -= s
		ms = int(seconds * 1000.0)
		ret = []
		for v, b, d in ((d, "d", 1), (h, "h", 2), (m, "m", 2),
				(s, "s", 2), (ms, "ms", 3)):
			if not v:
				continue
			if leadingZeros:
				fmt = "%0" + str(d) + "d%s"
				ret.append(fmt % (v, b))
			else:
				ret.append("%d%s" % (v, b))
		if not ret:
			return "0ms"
		return "".join(ret)

	@classmethod
	def __tryParseImmediate_STRING(cls, token, maxLen):
		if not token.startswith("'") or\
		   not token.endswith("'"):
			return None
		token = token[1:-1]
		if len(token) > maxLen:
			raise AwlSimError("String too long (>%d characters)" % maxLen)
		value = 0
		for c in token:
			value <<= 8
			value |= ord(c)
		return value

	@classmethod
	def tryParseImmediate_STRING(cls, token):
		return cls.__tryParseImmediate_STRING(token, 4)

	@classmethod
	def tryParseImmediate_CHAR(cls, token):
		return cls.__tryParseImmediate_STRING(token, 1)

	@classmethod
	def tryParseImmediate_S5T(cls, token):
		token = token.upper()
		if not token.startswith("S5T#"):
			return None
		seconds = cls.__parseGenericTime(token[4:])
		s5t = Timer.seconds_to_s5t(seconds)
		return s5t

	@classmethod
	def tryParseImmediate_TIME(cls, token):
		token = token.upper()
		if not token.startswith("T#"):
			return None
		seconds = cls.__parseGenericTime(token[2:])
		msec = int(seconds * 1000)
		if msec > 0x7FFFFFFF:
			raise AwlSimError("T# time too big")
		return msec

	@classmethod
	def tryParseImmediate_TOD(cls, token):
		token = token.upper()
		if not token.startswith("TOD#") and\
		   not token.startswith("TIME_OF_DAY#"):
			return None
		raise AwlSimError("TIME_OF_DAY# not implemented, yet")#TODO

	@classmethod
	def tryParseImmediate_Date(cls, token):
		token = token.upper()
		if not token.startswith("D#"):
			return None
		raise AwlSimError("D# not implemented, yet")#TODO

	@classmethod
	def tryParseImmediate_DT(cls, token):
		token = token.upper()
		if not token.startswith("DT#") and\
		   not token.startswith("DATE_AND_TIME#"):
			return None
		raise AwlSimError("DATE_AND_TIME# not implemented, yet")#TODO

	@classmethod
	def __parsePtrOffset(cls, string):
		try:
			values = string.split(".")
			if len(values) != 2:
				raise ValueError
			byteOffset = int(values[0], 10)
			bitOffset = int(values[1], 10)
			if bitOffset < 0 or bitOffset > 7 or\
			   byteOffset < 0 or byteOffset > 0x1FFFFF:
				raise ValueError
			return (byteOffset << 3) | bitOffset
		except ValueError:
			raise AwlSimError("Invalid pointer offset")

	@classmethod
	def tryParseImmediate_Pointer(cls, tokens):
		prefix = tokens[0].upper()
		if not prefix.startswith("P#"):
			return None, None
		prefix = prefix[2:] # Strip P#
		try:
			if prefix == "P":
				ptr = cls.__parsePtrOffset(tokens[1]) |\
					0x80000000
				return ptr, 2
			elif prefix in ("E", "I"):
				ptr = cls.__parsePtrOffset(tokens[1]) |\
					0x81000000
				return ptr, 2
			elif prefix in ("A", "Q"):
				ptr = cls.__parsePtrOffset(tokens[1]) |\
					0x82000000
				return ptr, 2
			elif prefix == "M":
				ptr = cls.__parsePtrOffset(tokens[1]) |\
					0x83000000
				return ptr, 2
			elif prefix == "DBX":
				ptr = cls.__parsePtrOffset(tokens[1]) |\
					0x84000000
				return ptr, 2
			elif prefix == "DIX":
				ptr = cls.__parsePtrOffset(tokens[1]) |\
					0x85000000
				return ptr, 2
			elif prefix == "L":
				ptr = cls.__parsePtrOffset(tokens[1]) |\
					0x86000000
				return ptr, 2
			else:
				# Area-internal pointer
				ptr = cls.__parsePtrOffset(prefix)
				return ptr, 1
		except IndexError:
			raise AwlSimError("Invalid pointer immediate")

	@classmethod
	def tryParseImmediate_Bin(cls, token):
		token = token.upper()
		if not token.startswith("2#"):
			return None
		try:
			string = token[2:].replace('_', '')
			immediate = int(string, 2)
			if immediate > 0xFFFFFFFF:
				raise ValueError
		except ValueError as e:
			raise AwlSimError("Invalid immediate")
		return immediate

	@classmethod
	def tryParseImmediate_ByteArray(cls, tokens):
		tokens = [ t.upper() for t in tokens ]
		if not tokens[0].startswith("B#("):
			return None, None
		try:
			if len(tokens) >= 5 and\
			   tokens[2] == ',' and\
			   tokens[4] == ')':
				fields = 5
				a, b = int(tokens[1], 10),\
				       int(tokens[3], 10)
				if a < 0 or a > 0xFF or\
				   b < 0 or b > 0xFF:
					raise ValueError
				immediate = (a << 8) | b
			elif len(tokens) >= 9 and\
			     tokens[2] == ',' and\
			     tokens[4] == ',' and\
			     tokens[6] == ',' and\
			     tokens[8] == ')':
				fields = 9
				a, b, c, d = int(tokens[1], 10),\
					     int(tokens[3], 10),\
					     int(tokens[5], 10),\
					     int(tokens[7], 10)
				if a < 0 or a > 0xFF or\
				   b < 0 or b > 0xFF or\
				   c < 0 or c > 0xFF or\
				   d < 0 or d > 0xFF:
					raise ValueError
				immediate = (a << 24) | (b << 16) |\
					    (c << 8) | d
			else:
				raise ValueError
		except ValueError as e:
			raise AwlSimError("Invalid immediate")
		return immediate, fields

	@classmethod
	def tryParseImmediate_HexByte(cls, token):
		token = token.upper()
		if not token.startswith("B#16#"):
			return None
		try:
			immediate = int(token[5:], 16)
			if immediate > 0xFF:
				raise ValueError
		except ValueError as e:
			raise AwlSimError("Invalid immediate")
		return immediate

	@classmethod
	def tryParseImmediate_HexWord(cls, token):
		token = token.upper()
		if not token.startswith("W#16#"):
			return None
		try:
			immediate = int(token[5:], 16)
			if immediate > 0xFFFF:
				raise ValueError
		except ValueError as e:
			raise AwlSimError("Invalid immediate")
		return immediate

	@classmethod
	def tryParseImmediate_HexDWord(cls, token):
		token = token.upper()
		if not token.startswith("DW#16#"):
			return None
		try:
			immediate = int(token[6:], 16)
			if immediate > 0xFFFFFFFF:
				raise ValueError
		except ValueError as e:
			raise AwlSimError("Invalid immediate")
		return immediate

class GenericInteger(object): #+cdef

#@cy	cdef public uint32_t value
#@cy	cdef public uint32_t mask

	def __init__(self, value, width): #@nocy
#@cy	def __init__(self, int64_t value, uint8_t width):
#@cy		cdef uint64_t one

		assert(width > 0 and width <= 32)
		self.value = value
		one = 1
		self.mask = int(((one << width) - 1) & 0xFFFFFFFF)

	def copyFrom(self, other): #@nocy
#@cy	cpdef copyFrom(self, GenericInteger other):
		self.value = other.value & self.mask

	def reset(self): #@nocy
#@cy	cpdef reset(self):
		self.value = 0

	def set(self, value): #@nocy
#@cy	cpdef set(self, int64_t value):
		self.value = value & self.mask

	def setByte(self, value): #@nocy
#@cy	cpdef setByte(self, int64_t value):
		self.value = ((self.value & 0xFFFFFF00) |\
			      (value & 0xFF)) &\
			     self.mask

	def setWord(self, value): #@nocy
#@cy	cpdef setWord(self, int64_t value):
		self.value = ((self.value & 0xFFFF0000) |\
			      (value & 0xFFFF)) &\
			     self.mask

	def setDWord(self, value): #@nocy
#@cy	cpdef setDWord(self, int64_t value):
		self.value = value & 0xFFFFFFFF & self.mask

	def setPyFloat(self, pyfl): #@nocy
#@cy	cpdef setPyFloat(self, pyfl):
		self.value = pyFloatToDWord(pyfl)

	def get(self): #@nocy
#@cy	cpdef uint32_t get(self):
		return self.value

	def getByte(self): #@nocy
#@cy	cpdef uint8_t getByte(self):
		return self.value & 0xFF

	def getWord(self): #@nocy
#@cy	cpdef uint16_t getWord(self):
		return self.value & 0xFFFF

	def getDWord(self): #@nocy
#@cy	cpdef uint32_t getDWord(self):
		return self.value & 0xFFFFFFFF

	def getSignedByte(self): #@nocy
#@cy	cpdef int8_t getSignedByte(self):
		return byteToSignedPyInt(self.value)

	def getSignedWord(self): #@nocy
#@cy	cpdef int16_t getSignedWord(self):
		return wordToSignedPyInt(self.value)

	def getSignedDWord(self): #@nocy
#@cy	cpdef int32_t getSignedDWord(self):
		return dwordToSignedPyInt(self.value)

	def getPyFloat(self): #@nocy
#@cy	cpdef getPyFloat(self):
		return dwordToPyFloat(self.value)

	def setBit(self, bitNumber): #@nocy
#@cy	cpdef setBit(self, uint8_t bitNumber):
		self.value = (self.value | (1 << bitNumber)) & self.mask

	def clearBit(self, bitNumber): #@nocy
#@cy	cpdef clearBit(self, uint8_t bitNumber):
		self.value &= ~(1 << bitNumber)

	def setBitValue(self, bitNumber, value): #@nocy
#@cy	cpdef setBitValue(self, uint8_t bitNumber, uint8_t value):
		if value:
			self.setBit(bitNumber)
		else:
			self.clearBit(bitNumber)

	def getBit(self, bitNumber): #@nocy
#@cy	cpdef unsigned char getBit(self, uint8_t bitNumber):
		return (self.value >> bitNumber) & 1

	def toHex(self):
		if self.mask == 0xFF:
			return "%02X" % self.value
		elif self.mask == 0xFFFF:
			return "%04X" % self.value
		elif self.mask == 0xFFFFFFFF:
			return "%08X" % self.value
		else:
			assert(0)

class GenericWord(GenericInteger): #+cdef
	def __init__(self, value=0):
		GenericInteger.__init__(self, value, 16)

class GenericDWord(GenericInteger): #+cdef
	def __init__(self, value=0):
		GenericInteger.__init__(self, value, 32)

class ByteArray(bytearray):
	"""Generic memory representation."""

	# "Natural" memory fetch operation.
	# This method returns the natural data (byte, word, dword) for
	# a given memory region of this byte array.
	# offset => An AwlOffset() that specifies the region to fetch from.
	# width => An integer specifying the width (in bits) to fetch.
	def fetch(self, offset, width): #@nocy
#@cy	def fetch(self, object offset, uint32_t width):
#@cy		cdef uint32_t byteOffset

		byteOffset = offset.byteOffset
		try:
			if width == 1:
				return (self[byteOffset] >> offset.bitOffset) & 1
			elif width == 8:
				return self[byteOffset]
			elif width == 16:
				return (self[byteOffset] << 8) |\
				       self[byteOffset + 1]
			elif width == 32:
				return (self[byteOffset] << 24) |\
				       (self[byteOffset + 1] << 16) |\
				       (self[byteOffset + 2] << 8) |\
				       self[byteOffset + 3]
			else:
				assert(not offset.bitOffset)
				nrBytes = intDivRoundUp(width, 8)
				if byteOffset + nrBytes > len(self):
					raise IndexError
				return memoryview(self)[byteOffset : byteOffset + nrBytes]
		except IndexError as e:
			raise AwlSimError("fetch: Operator offset '%s' out of range" %\
					  str(offset))

	# Fetch bytes from this memory region.
	# Returns a view of the memory region bytes (not a copy!).
	# offset => An AwlOffset() that specifies the region to fetch from.
	# width => An integer specifying the width (in bits) to fetch.
	def fetchBytes(self, offset, width):
		#TODO optimize me!
		bytesBuf = ByteArray(intDivRoundUp(width, 8))
		WordPacker.toBytes(bytesBuf, width, 0, self.fetch(offset, width))
		return bytesBuf

	# "Natural" memory store operation.
	# This method stores natural data (byte, word, dword) to
	# a given memory region of this byte array.
	# offset => An AwlOffset() that specifies the region to store to.
	# width => An integer specifying the width (in bits) to store.
	# value => The value to store. May be an integer (byte, word, dword) or
	#          a byte array for large types (>32 bit).
	def store(self, offset, width, value): #@nocy
#@cy	def store(self, object offset, uint32_t width, object value):
#@cy		cdef uint32_t byteOffset

		byteOffset = offset.byteOffset
		try:
			if width == 1:
				if value:
					self[byteOffset] |= 1 << offset.bitOffset
				else:
					self[byteOffset] &= ~(1 << offset.bitOffset)
			elif width == 8:
				self[byteOffset] = value & 0xFF
			elif width == 16:
				self[byteOffset] = (value >> 8) & 0xFF
				self[byteOffset + 1] = value & 0xFF
			elif width == 32:
				self[byteOffset] = (value >> 24) & 0xFF
				self[byteOffset + 1] = (value >> 16) & 0xFF
				self[byteOffset + 2] = (value >> 8) & 0xFF
				self[byteOffset + 3] = value & 0xFF
			else:
				assert(not offset.bitOffset)
				nrBytes = intDivRoundUp(width, 8)
				assert(nrBytes == len(value))
				if byteOffset + nrBytes > len(self):
					raise IndexError
				self[byteOffset : byteOffset + len(value)] = value
		except IndexError as e:
			raise AwlSimError("store: Operator offset '%s' out of range" %\
					  str(offset))

	# Store bytes to this memory region.
	# offset => An AwlOffset() that specifies the region to store to.
	# width => An integer specifying the width (in bits) to store.
	# valueBytes => The bytes to store.
	def storeBytes(self, offset, width, valueBytes):
		#TODO optimize me!
		self.store(offset, width, WordPacker.fromBytes(valueBytes, width))

	def __repr__(self):
		ret = [ 'ByteArray(b"', ]
		for b in self:
			ret.append("\\x%02X" % b)
		ret.append('")')
		return "".join(ret)

	def __str__(self):
		return self.__repr__()

class Accu(GenericDWord): #+cdef
	"Accumulator register"

	def __init__(self):
		GenericDWord.__init__(self)

class Adressregister(GenericDWord): #+cdef
	"Address register"

	def __init__(self):
		GenericDWord.__init__(self)

	def toPointerString(self):
		value = self.getDWord()
		area = (value >> 24) & 0xFF
		if area:
			if area == 0x80:
				prefix = "P"
			elif area == 0x81:
				prefix = "E"
			elif area == 0x82:
				prefix = "A"
			elif area == 0x83:
				prefix = "M"
			elif area == 0x84:
				prefix = "DBX"
			elif area == 0x85:
				prefix = "DIX"
			elif area == 0x86:
				prefix = "L"
			elif area == 0x87:
				prefix = "V"
			else:
				prefix = "(%02X)" % area
			prefix += " "
		else:
			prefix = ""
		byteOffset = (value & 0x00FFFFFF) >> 3
		bitOffset = value & 7
		return "P#%s%d.%d" % (prefix, byteOffset, bitOffset)
