###################################################################
#	How to create a signal block definition - Josh Blum
###################################################################
#	Signal blocks in GRC are defined in python code. Each block gets its own method:
def MySignalBlockDef(sb):
	#	import any packages here and equate the actual gr constructor
	#
	#	This is good practice:
	#	If a package or a block is not implemented in your version of GNU Radio, 
	#	an ImportError or AttributeError will be caught,
	#	and GRC can ignore this block. 
	from gnuradio import blks
	fcn = blks.some_hier_block
	#	add input and output sockets
	sb.add_input_socket('in', Float())
	sb.add_output_socket('out', Float())
	#	add signal block parameters
	sb.add_param('Sample Rate', Float(default_samp_rate))
	sb.add_param('Decimation', Int(default_decim))
	#	set a documentation string (optional)
	sb.set_docs('''This is my block.''')
	#	create a method to build the actual GNU Radio block.
	#	The first parameter of this method must be the flow graph.
	#	The proceeding parmeters coorespond to the parameters above.
	def make(fg, samp_rate, decim):
		#	create the signal block using fcn, and call every parameter's parse method
		block = fcn(samp_rate.parse(), decim.parse())
		#	add callbacks: callbacks are special methods of the signal block
		#	that let us change parameters after the block is created.
		#	If one of the parameters was based on a variable and had a callback method,
		#	one could dynamically change the block's parameter(s) by changing the variable.
		#	The first argument is the method, the second is the unparsed data type:
		fg.add_callback(block.set_samp_rate, samp_rate)
		return block #	return the block
	#	end the definition by returning a tuple: graphical block, the make method
	return sb, make
#	This is a very basic framework. You can do anything with your signal block definition!
#	Just make sure all methods return the right things and that the number of parameters matches up.

###################################################################
#	Add your block to the tree:
###################################################################
	Edit SignalBlockDefs/SignalBlockTree.py. 
	Find your desired category and add the tuple to the tree:
	("My Signal Block's Name", MySignalBlockDef),
	
###################################################################
#	Basic and Vector Data Types
###################################################################

Complex
Float
Int
Short
Byte

ComplexVector
FloatVector
IntVector
ShortVector
ByteVector

'''
All of these data types share the same contructor, like so:
Float(default_value, min, max)
The default_value can be anything, it will be stored in the data type as a string.
Preferably, the default value will be a numeric python object, 
or a string representing a vector or a expression. 
For the basic data types, the min and max parameters determine what values are considered valid.
For the vector data types, the min and max parameters determine what vector lengths are considered valid.
The min and the max must be floats or ints. 
Also, do not use min and max for Complex, as this does not make sence and will always be invalid.
'''


###################################################################
#	Enum and Bool Data Types
###################################################################

'''
The Enum data types takes a finite list of choices. 
The data type stores the index of the choice. 
Parsing an Enum data type will return the choice at the stored index.
'''

example_enum = Enum([
		('Name of choice0', data0),
		('Name of choice1', data1),
		('Name of choice2', data2),
	], default_index)
	
'''
The Bool data type is a special type of Enum, 
with a choice for True and a choice for false.
'''

example_bool = Bool(true='Choose Yes', false='Choose No', default=True)


###################################################################
#	Variable Data Type
###################################################################

'''
The variable data type can represent any of the basic or vector data types.
By using this data type, one can change the data type of a socket or a parameter.
A variable data type takes an Enum as its parameter. 
This Enum must parse to one of the basic or vector data types.
'''

example_enum_of_data_types = Enum([
		('Float Out', Float()),
		('Complex Out', Complex()),
	])
sb.add_param('Choose Output Type', example_enum_of_data_types)	#allow the user to choose the data type
sb.add_output_socket('out', Variable(example_enum_of_data_types))#the output socket is now variable

'''
Now to do something more complicated: 
Suppose we want to use an Enum to control more than one variable, 
but the variables need to be different types.
The Enum can hold a tuple or a list for each choice.
The Variable's index parameter specifies the index of this tuple.
Essentially, the tuple can be filled with anything; however,
the indecies that the Variable references must be basic or vector data types.
'''

example_enum_of_data_types = Enum([
		('Float -> Complex', (gr.block_fc, Float(), Complex())),
		('Short -> Float', (gr.block_sf, Short(), Float())),
	])
sb.add_param('Choose IO Types', example_enum_of_data_types)	#allow the user to choose the data type
sb.add_input_socket('out', Variable(example_enum_of_data_types, index=1))#the input socket uses 1st index
sb.add_output_socket('out', Variable(example_enum_of_data_types, index=2))#the output socket uses 2nd index

'''
In the case above, the 0th index of the tuple holds a specific gr block.
Again, specifying the actual gr block is good practice:
If a package or a block is not implemented in your version of GNU Radio, 
an ImportError or AttributeError will be caught,
and GRC can ignore this block. 
The gr block can be retrieved in the make method by calling type.parse()[0],
(since parse will return the tuple, and [0] gets the 0th element).
'''

###################################################################
#	Misc Data Types
###################################################################

String
FileOpen
FileSave

'''
A String data type can take on any value and will always be valid. 
The String data type is good for specifying names and titles.

The FileOpen data type stores a file path as a string. 
It will only be valid if its string represents a file path to an existing file.

The FileSave data type stores a file path as a string. 
It will only be valid if its string represents a file path with existing directories.
'''

###################################################################
#	Special socket options
###################################################################

'''
The methods add_input_socket and add_output_socket have two additional parameters: optional and vlen.
The "optional" parameter is a boolean value which is "False" by default. 
If the value is "True", the socket will pass validation even if it is left unconnected.

The "vlen" parameter is an Int data type which is None by default. 
If the value is an Int, the socket's data type will be vectorized when the Int is greater than 1.
(Vectorized means that a Complex data type will become ComplexVector and so on...)
'''

#Usage
sb.add_input_socket(cname, data_type, optional=False, vlen=None)

###################################################################
#	Special param options
###################################################################

'''
The method add param has four additional parameters: show_label, type, I/O controllers

The "show_label" parameter is a boolean value which is "True" by default.
If the value is "False", this block's parameter will not be drawn into the block's label.
It saves space on the flow graph by not drawing redundant parameters.
A parameter is redundant when its effect is seen on the block.
Ex: A Enum controlling the output data type changes the color of the output socket. 

The "type" parameter is a boolean value which is "False" by default.
If the value is "True", Up and Down hotkeys will increment and decrement the value.
Warning: The "type" parameter should only be used with the Enum data type.
Useful for changing a block's data type with hotkeys.

The "I/O_socket_controller" parameter is a boolean value which is "False" by default.
If the value is "True", this block's parameter will control the number of input or output sockets (respectivly).
Warning: The "I/O_socket_controller" parameter should only be used with the Integer data types (Int, Short, Byte).
Useful for block with a dynamic number of inputs, like Add and Interleave.
'''

#Usage
add_param(cname, data_type, show_label=True, type=False, output_sockets_controller=False, input_sockets_controller=False)

###################################################################
#	Final Notes
###################################################################

Please look through the src/SignalBlockDefs/*.py to see how all these components work together...
