#include "variable_frequency_digital_source.h"

/*
 * variable_frequency_digital_source_input_specs
 *
 *	This is a NULL-terminated list of the device's input buses and their widths.
 *
 *	Values are of the form "busname,width".
 *
 *	Buses without a width are assumed to be pins, identical to a bus of width 1.
 */
static char* variable_frequency_digital_source_input_specs[]  = {
	SMX_DLL_CREATE_BUS_SPEC(BASE,32)
	,
	SMX_DLL_CREATE_BUS_SPEC(EXPONENT,4)
	,
	SMX_DLL_CREATE_BUS_SPEC(READY,1)
	,
	NULL
};

/*
 * variable_frequency_digital_source_output_specs
 *
 *	This is a NULL-terminated list of the device's output buses and their widths.
 *
 *	Values are of the form "busname,width".
 *
 *	Buses without a width are assumed to be pins, identical to a bus of width 1.
 */
static char* variable_frequency_digital_source_output_specs[] = {
	SMX_DLL_CREATE_BUS_SPEC(OUT,1)
	,
	NULL
};

/*
 * parameter specifications for the variable_frequency_digital_source device.
 */
SMX_DLL_PARAMETER_SPEC( variable_frequency_digital_source, IC, SMX_DLL_PARAMETER_TYPE_INTEGER_UNSIGNED, 0 );
SMX_DLL_PARAMETER_SPEC( variable_frequency_digital_source, DELAY, SMX_DLL_PARAMETER_TYPE_DOUBLE, 2p );
SMX_DLL_PARAMETER_SPEC( variable_frequency_digital_source, DUTY_CYCLE, SMX_DLL_PARAMETER_TYPE_DOUBLE, .5 );

/*
 * variable_frequency_digital_source_parameters
 *
 *	This is a NULL-terminated list of the device's parameters.
 *
 *	Each entry should be a pointer to a struct of type s_smx_dll_parameter_spec.
 */
static p_smx_dll_parameter_spec variable_frequency_digital_source_parameters[]   = {
	&( SMX_DLL_PARAMETER_SPEC_NAME( variable_frequency_digital_source, IC ) )
	,
	&( SMX_DLL_PARAMETER_SPEC_NAME( variable_frequency_digital_source, DELAY ) )
	,
	&( SMX_DLL_PARAMETER_SPEC_NAME( variable_frequency_digital_source, DUTY_CYCLE ) )
	,
	NULL
};

static s_smx_dll_device_spec variable_frequency_digital_source_device_spec = {
	.name = "VARIABLE_FREQUENCY_DIGITAL_SOURCE"
	,
	.version = 2
	,
	.input_specs = (char**)&variable_frequency_digital_source_input_specs
	,
	.output_specs = (char**)&variable_frequency_digital_source_output_specs
	,
	.parameter_specs = (s_smx_dll_parameter_spec**)variable_frequency_digital_source_parameters
	,
	.setup = variable_frequency_digital_source_setup
	,
	.set_initial_condition = variable_frequency_digital_source_set_initial_condition
	,
	.action = variable_frequency_digital_source_action
	,
	.teardown = variable_frequency_digital_source_teardown
};

/*
 * supported_devices
 *
 *	This array should contain all of the latest versions of the device
 *		specifications that the DLL supports.
 *
 *	This array will populate the list of devices from which the user can
 *		select when creating a new instance of a DLL-defined digital device
 *		on a schematic.
 */
static p_smx_dll_device_spec supported_devices[] = {
	&variable_frequency_digital_source_device_spec
	,
	NULL
};

/*
 * smx_dll_get_supported_devices
 *
 *	This function is declared in variable_frequency_digital_source.h
 */
p_smx_dll_device_spec* smx_dll_get_supported_devices(void) {
	return supported_devices;
}

/*
 * smx_dll_get_device_spec
 *
 *	This function is declared in variable_frequency_digital_source.h
 */
p_smx_dll_device_spec smx_dll_get_device_spec(
	char* name
	,
	SMX_DLL_UINT32 version
) {

	p_smx_dll_device_spec
		device_spec_p = NULL;

	/*
	 * This automatically generated code is written to insist on an exact
	 *	match of both device name and version in order to return a specification.
	 */
	if(
		0 == _stricmp( "variable_frequency_digital_source", name )
		&&
		version == variable_frequency_digital_source_device_spec.version
	) {
		device_spec_p = &variable_frequency_digital_source_device_spec;
	}

	return device_spec_p;
}

/*
 * variable_frequency_digital_source_assign_default_pointers
 *
 *	This function calls variable_frequency_digital_source_instantiate_default_pointers and
 *		assigns the result to the device's unmanaged user storage.
 *
 *	There is no reason to call this function more than once, and it is
 *		recommended to be called during variable_frequency_digital_source_setup,
 *		not during variable_frequency_digital_source_set_initial_condition or variable_frequency_digital_source_action.
 */
SMX_DLL_ERROR variable_frequency_digital_source_assign_default_pointers(
	p_smx_dll_simulation_context context_p
	,
	p_smx_dll_device device_p
) {

	SMX_DLL_ERROR
		rv = SMX_DLL_NO_ERROR;

	p_variable_frequency_digital_source_default_pointers
		default_pointers_p = NULL;

	// instantiate default pointers
	if(
		SMX_DLL_NO_ERROR != ( rv = variable_frequency_digital_source_instantiate_default_pointers( context_p, device_p, &default_pointers_p ) )
		||
		NULL == default_pointers_p
	) {

		// an error occurred instantiating the pointers
		context_p->funcs->fatal_error( device_p, "Unable to instantiate default pointers." );

		return rv;

	}

	// assign unmanaged user storage size
	device_p->unmanaged_user_storage_size =
		sizeof( s_variable_frequency_digital_source_default_pointers );

	// assign default pointers to unmanaged user storage
	device_p->unmanaged_user_storage =
		default_pointers_p;

	return rv;

}

/*
 * variable_frequency_digital_source_instantiate_default_pointers
 *
 *	This function will populate a struct of type s_variable_frequency_digital_source_default_pointers.
 *
 *	A pointer will be retrieved for each input bus and output bus.
 *
 *	The value will be retrieved for each parameter.
 *
 *	There is no reason to call this function more than once, and it is
 *		recommended to be called during variable_frequency_digital_source_setup,
 *		not during variable_frequency_digital_source_set_initial_condition or variable_frequency_digital_source_action.
 *
 *	This function is called by variable_frequency_digital_source_assign_default_pointers, it is
 *		not necessary to call them both.
 */
SMX_DLL_ERROR variable_frequency_digital_source_instantiate_default_pointers(
	p_smx_dll_simulation_context context_p
	,
	p_smx_dll_device device_p
	,
	p_variable_frequency_digital_source_default_pointers *result
) {

	SMX_DLL_ERROR
		rv = SMX_DLL_NO_ERROR;

	p_variable_frequency_digital_source_default_pointers
		default_pointers_p = NULL;

	p_variable_frequency_digital_source_input_bus_pointers
		input_bus_pointers_p = NULL;

	p_variable_frequency_digital_source_output_bus_pointers
		output_bus_pointers_p = NULL;

	p_variable_frequency_digital_source_parameter_values
		parameter_values_p = NULL;

	p_smx_dll_parameter
		parameter_p = NULL;

	// instantiate structs
	if( NULL == ( default_pointers_p    = (s_variable_frequency_digital_source_default_pointers*   )malloc( sizeof( s_variable_frequency_digital_source_default_pointers    ) ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to malloc for default pointers." );
		return SMX_DLL_ERROR_UNABLE_TO_ALLOCATE_MEMORY;
	}
	if( NULL == ( input_bus_pointers_p  = (s_variable_frequency_digital_source_input_bus_pointers* )malloc( sizeof( s_variable_frequency_digital_source_input_bus_pointers  ) ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to malloc for input bus pointers." );
		return SMX_DLL_ERROR_UNABLE_TO_ALLOCATE_MEMORY;
	}
	if( NULL == ( output_bus_pointers_p = (s_variable_frequency_digital_source_output_bus_pointers*)malloc( sizeof( s_variable_frequency_digital_source_output_bus_pointers ) ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to malloc for output bus pointers." );
		return SMX_DLL_ERROR_UNABLE_TO_ALLOCATE_MEMORY;
	}
	if( NULL == ( parameter_values_p    = (s_variable_frequency_digital_source_parameter_values*   )malloc( sizeof( s_variable_frequency_digital_source_parameter_values    ) ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to malloc for parameter_values." );
		return SMX_DLL_ERROR_UNABLE_TO_ALLOCATE_MEMORY;
	}

	// assign pointers
	default_pointers_p->input_bus_pointers_p  = input_bus_pointers_p;
	default_pointers_p->output_bus_pointers_p = output_bus_pointers_p;
	default_pointers_p->parameter_values_p    = parameter_values_p;

	// retrieve input bus pointers, if necessary
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_bus_by_name(device_p, "BASE", SMX_DLL_DIRECTION_INPUT, &( input_bus_pointers_p->BASE_bus_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate INPUT bus by name: BASE" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_bus_by_name(device_p, "EXPONENT", SMX_DLL_DIRECTION_INPUT, &( input_bus_pointers_p->EXPONENT_bus_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate INPUT bus by name: EXPONENT" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_bus_by_name(device_p, "READY", SMX_DLL_DIRECTION_INPUT, &( input_bus_pointers_p->READY_bus_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate INPUT bus by name: READY" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}

	// retrieve output bus pointers
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_bus_by_name(device_p, "OUT", SMX_DLL_DIRECTION_OUTPUT, &( output_bus_pointers_p->OUT_bus_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate OUTPUT bus by name: OUT" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}

	// retrieve parameter values
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_parameter_by_name(device_p, "IC", &( parameter_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate parameter by name: IC" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	parameter_values_p->IC = parameter_p->value.uint_value;
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_parameter_by_name(device_p, "DELAY", &( parameter_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate parameter by name: DELAY" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	parameter_values_p->DELAY = parameter_p->value.double_value;
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_parameter_by_name(device_p, "DUTY_CYCLE", &( parameter_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate parameter by name: DUTY_CYCLE" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	parameter_values_p->DUTY_CYCLE = parameter_p->value.double_value;

	// assign result
	*result = default_pointers_p;

	// pointer and value assignment complete
	return rv;

}
