"""
Copyright 2008 Free Software Foundation, Inc.
This file is part of GNU Radio

GNU Radio Companion 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.

GNU Radio Companion 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 utils import expr_utils
from .. base.Param import Param as _Param
import Constants
import os

class Param(_Param):

	_init = False
	_hostage_cells = list()

	##possible param types
	TYPES = _Param.TYPES + [
		'complex', 'real', 'int',
		'complex_vector', 'real_vector', 'int_vector',
		'hex', 'string',
		'file_open', 'file_save',
		'id',
		'grid_pos', 'import',
	]

	def get_color(self):
		"""
		Get the color that represents this param's type.
		@return a hex color code.
		"""
		try:
			return {
				#number types
				'complex': Constants.COMPLEX_COLOR_SPEC,
				'real': Constants.FLOAT_COLOR_SPEC,
				'int': Constants.INT_COLOR_SPEC,
				#vector types
				'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
				'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
				'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
				#special
				'hex': Constants.INT_COLOR_SPEC,
				'string': Constants.BYTE_VECTOR_COLOR_SPEC,
				'id': '#DDDDDD',
				'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
			}[self.get_type()]
		except: return _Param.get_color(self)

	def get_hide(self):
		"""
		Get the hide value from the base class.
		Hide the ID parameter for most blocks. Exceptions below.
		If the parameter controls a port type, vlen, or nports, return part.
		These parameters are redundant to display in the flow graph view.
		@return hide the hide property string
		"""
		hide = _Param.get_hide(self)
		if hide: return hide
		#hide ID in non variable blocks
		if self.get_key() == 'id' and self.get_parent().get_key() not in (
			'variable', 'variable_slider', 'variable_chooser', 'variable_text_box', 'parameter', 'options'
		): return 'part'
		#hide port controllers
		if self.get_key() in ' '.join(map(
			lambda p: ' '.join([p._type, p._vlen, p._nports]), self.get_parent().get_ports())
		): return 'part'
		return hide

	def evaluate(self):
		"""
		Evaluate the value.
		@return evaluated type
		"""
		self._lisitify_flag = False
		self._stringify_flag = False
		self._hostage_cells = list()
		def eval_string(v):
			try:
				e = self.get_parent().get_parent().evaluate(v)
				assert isinstance(e, str)
				return e
			except:
				self._stringify_flag = True
				return v
		t = self.get_type()
		v = self.get_value()
		#########################
		# Enum Type
		#########################
		if self.is_enum(): return self.get_value()
		#########################
		# Numeric Types
		#########################
		elif t in ('raw', 'complex', 'real', 'int', 'complex_vector', 'real_vector', 'int_vector', 'hex'):
			#raise exception if python cannot evaluate this value
			try: e = self.get_parent().get_parent().evaluate(v)
			except:
				self._add_error_message('Value "%s" cannot be evaluated.'%v)
				raise Exception
			#raise an exception if the data is invalid
			if t == 'raw': return e
			elif t == 'complex':
				try: assert(isinstance(e, (complex, float, int, long)))
				except AssertionError:
					self._add_error_message('Expression "%s" is invalid for type complex.'%str(e))
					raise Exception
				return e
			elif t == 'real':
				try: assert(isinstance(e, (float, int, long)))
				except AssertionError:
					self._add_error_message('Expression "%s" is invalid for type real.'%str(e))
					raise Exception
				return e
			elif t == 'int':
				try: assert(isinstance(e, (int, long)))
				except AssertionError:
					self._add_error_message('Expression "%s" is invalid for type integer.'%str(e))
					raise Exception
				return e
			elif t == 'complex_vector':
				if not isinstance(e, (tuple, list, set)):
					self._lisitify_flag = True
					e = [e]
				try:
					for ei in e:
						assert(isinstance(ei, (complex, float, int, long)))
				except AssertionError:
					self._add_error_message('Expression "%s" is invalid for type complex vector.'%str(e))
					raise Exception
				return e
			elif t == 'real_vector':
				if not isinstance(e, (tuple, list, set)):
					self._lisitify_flag = True
					e = [e]
				try:
					for ei in e:
						assert(isinstance(ei, (float, int, long)))
				except AssertionError:
					self._add_error_message('Expression "%s" is invalid for type real vector.'%str(e))
					raise Exception
				return e
			elif t == 'int_vector':
				if not isinstance(e, (tuple, list, set)):
					self._lisitify_flag = True
					e = [e]
				try:
					for ei in e:
						assert(isinstance(ei, (int, long)))
				except AssertionError:
					self._add_error_message('Expression "%s" is invalid for type integer vector.'%str(e))
					raise Exception
				return e
			elif t == 'hex':
				return hex(e)
			else: raise TypeError, 'Type "%s" not handled'%t
		#########################
		# String Types
		#########################
		elif t in ('string', 'file_open', 'file_save'):
			#do not check if file/directory exists, that is a runtime issue
			e = eval_string(v)
			return str(e)
		#########################
		# Unique ID Type
		#########################
		elif t == 'id':
			#can python use this as a variable?
			try:
				assert(len(v) > 0)
				assert(v[0].isalpha())
				for c in v: assert(c.isalnum() or c in ('_',))
			except AssertionError:
				self._add_error_message('ID "%s" must be alpha-numeric or underscored, and begin with a letter.'%v)
				raise Exception
			params = self.get_all_params('id')
			keys = [param.get_value() for param in params]
			try: assert(len(keys) == len(set(keys)))
			except:
				self._add_error_message('ID "%s" is not unique.'%v)
				raise Exception
			return v
		#########################
		# Grid Position Type
		#########################
		elif t == 'grid_pos':
			if not v: return '' #allow for empty grid pos
			e = self.get_parent().get_parent().evaluate(v)
			try:
				assert(isinstance(e, (list, tuple)) and len(e) == 4)
				for ei in e: assert(isinstance(ei, int))
			except AssertionError:
				self._add_error_message('A grid position must be a list of 4 integers.')
				raise Exception
			row, col, row_span, col_span = e
			#check row, col
			try: assert(row >= 0 and col >= 0)
			except AssertionError:
				self._add_error_message('Row and column must be non-negative.')
				raise Exception
			#check row span, col span
			try: assert(row_span > 0 and col_span > 0)
			except AssertionError:
				self._add_error_message('Row and column span must be greater than zero.')
				raise Exception
			#calculate hostage cells
			for r in range(row_span):
				for c in range(col_span):
					self._hostage_cells.append((row+r, col+c))
			#avoid collisions
			params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
			for param in params:
				for cell in param._hostage_cells:
					if cell in self._hostage_cells:
						self._add_error_message('Another graphical element is using cell "%s".'%str(cell))
						raise Exception
			return e
		#########################
		# Import Type
		#########################
		elif t == 'import':
			n = dict() #new namespace
			try: exec v in n
			except ImportError:
				self._add_error_message('Import "%s" failed.'%v)
				raise Exception
			except Exception:
				self._add_error_message('Bad import syntax: "%s".'%v)
				raise Exception
			return filter(lambda k: str(k) != '__builtins__', n.keys())
		#########################
		else: raise TypeError, 'Type "%s" not handled'%t

	def to_code(self):
		"""
		Convert the value to code.
		@return a string representing the code
		"""
		#run init tasks in evaluate
		#such as setting flags
		if not self._init:
			self.evaluate()
			self._init = True
		v = self.get_value()
		t = self.get_type()
		if t in ('string', 'file_open', 'file_save'): #string types
			if self._stringify_flag:
				return '"%s"'%v.replace('"', '\"')
			else:
				return v
		elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
			if self._lisitify_flag:
				return '(%s, )'%v
			else:
				return '(%s)'%v
		else:
			return v

	def get_all_params(self, type):
		"""
		Get all the params from the flowgraph that have the given type.
		@param type the specified type
		@return a list of params
		"""
		return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_blocks()], [])