#include "ringset_control.h"

/*
 * ringset_control_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* ringset_control_input_specs[]  = {
	SMX_DLL_CREATE_BUS_SPEC(CLK,1)
	,
	SMX_DLL_CREATE_BUS_SPEC(FBTH,4)
	,
	SMX_DLL_CREATE_BUS_SPEC(VINTH,1)
	,
	NULL
};

/*
 * ringset_control_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* ringset_control_output_specs[] = {
	SMX_DLL_CREATE_BUS_SPEC(RSET,4)
	,
	NULL
};

/*
 * parameter specifications for the ringset_control device.
 */
SMX_DLL_PARAMETER_SPEC( ringset_control, IC, SMX_DLL_PARAMETER_TYPE_INTEGER_UNSIGNED, 1 );
SMX_DLL_PARAMETER_SPEC( ringset_control, RSET_MIN_LL, SMX_DLL_PARAMETER_TYPE_INTEGER_UNSIGNED, 1 );
SMX_DLL_PARAMETER_SPEC( ringset_control, RSET_MAX_LL, SMX_DLL_PARAMETER_TYPE_INTEGER_UNSIGNED, 7 );
SMX_DLL_PARAMETER_SPEC( ringset_control, RSET_MIN_HL, SMX_DLL_PARAMETER_TYPE_INTEGER_UNSIGNED, 3 );
SMX_DLL_PARAMETER_SPEC( ringset_control, RSET_MAX_HL, SMX_DLL_PARAMETER_TYPE_INTEGER_UNSIGNED, 9 );
SMX_DLL_PARAMETER_SPEC( ringset_control, WAKE_INTERVAL, SMX_DLL_PARAMETER_TYPE_DOUBLE, 10m );
SMX_DLL_PARAMETER_SPEC( ringset_control, OUTPUT_DELAY, SMX_DLL_PARAMETER_TYPE_DOUBLE, 50n );

/*
 * ringset_control_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 ringset_control_parameters[]   = {
	&( SMX_DLL_PARAMETER_SPEC_NAME( ringset_control, IC ) )
	,
	&( SMX_DLL_PARAMETER_SPEC_NAME( ringset_control, RSET_MIN_LL ) )
	,
	&( SMX_DLL_PARAMETER_SPEC_NAME( ringset_control, RSET_MAX_LL ) )
	,
	&( SMX_DLL_PARAMETER_SPEC_NAME( ringset_control, RSET_MIN_HL ) )
	,
	&( SMX_DLL_PARAMETER_SPEC_NAME( ringset_control, RSET_MAX_HL ) )
	,
	&( SMX_DLL_PARAMETER_SPEC_NAME( ringset_control, WAKE_INTERVAL ) )
	,
	&( SMX_DLL_PARAMETER_SPEC_NAME( ringset_control, OUTPUT_DELAY ) )
	,
	NULL
};

static s_smx_dll_device_spec ringset_control_device_spec = {
	.name = "RINGSET_CONTROL"
	,
	.version = 1
	,
	.input_specs = (char**)&ringset_control_input_specs
	,
	.output_specs = (char**)&ringset_control_output_specs
	,
	.parameter_specs = (s_smx_dll_parameter_spec**)ringset_control_parameters
	,
	.setup = ringset_control_setup
	,
	.set_initial_condition = ringset_control_set_initial_condition
	,
	.action = ringset_control_action
	,
	.teardown = ringset_control_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[] = {
	&ringset_control_device_spec
	,
	NULL
};

/*
 * smx_dll_get_supported_devices
 *
 *	This function is declared in ringset_control.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 ringset_control.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( "RINGSET_CONTROL", name )
		&&
		version == ringset_control_device_spec.version
	) {
		device_spec_p = &ringset_control_device_spec;
	}

	return device_spec_p;
}

/*
 * ringset_control_assign_default_pointers
 *
 *	This function calls ringset_control_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 ringset_control_setup,
 *		not during ringset_control_set_initial_condition or ringset_control_action.
 */
SMX_DLL_ERROR ringset_control_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_ringset_control_default_pointers
		default_pointers_p = NULL;

	// instantiate default pointers
	if(
		SMX_DLL_NO_ERROR != ( rv = ringset_control_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_ringset_control_default_pointers );

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

	return rv;

}

/*
 * ringset_control_instantiate_default_pointers
 *
 *	This function will populate a struct of type s_ringset_control_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 ringset_control_setup,
 *		not during ringset_control_set_initial_condition or ringset_control_action.
 *
 *	This function is called by ringset_control_assign_default_pointers, it is
 *		not necessary to call them both.
 */
SMX_DLL_ERROR ringset_control_instantiate_default_pointers(
	p_smx_dll_simulation_context context_p
	,
	p_smx_dll_device device_p
	,
	p_ringset_control_default_pointers *result
) {

	SMX_DLL_ERROR
		rv = SMX_DLL_NO_ERROR;

	p_ringset_control_default_pointers
		default_pointers_p = NULL;

	p_ringset_control_input_bus_pointers
		input_bus_pointers_p = NULL;

	p_ringset_control_output_bus_pointers
		output_bus_pointers_p = NULL;

	p_ringset_control_parameter_values
		parameter_values_p = NULL;

	p_smx_dll_parameter
		parameter_p = NULL;

	// instantiate structs
	if( NULL == ( default_pointers_p    = (s_ringset_control_default_pointers*   )malloc( sizeof( s_ringset_control_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_ringset_control_input_bus_pointers* )malloc( sizeof( s_ringset_control_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_ringset_control_output_bus_pointers*)malloc( sizeof( s_ringset_control_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_ringset_control_parameter_values*   )malloc( sizeof( s_ringset_control_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, "CLK", SMX_DLL_DIRECTION_INPUT, &( input_bus_pointers_p->CLK_bus_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate INPUT bus by name: CLK" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_bus_by_name(device_p, "FBTH", SMX_DLL_DIRECTION_INPUT, &( input_bus_pointers_p->FBTH_bus_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate INPUT bus by name: FBTH" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_bus_by_name(device_p, "VINTH", SMX_DLL_DIRECTION_INPUT, &( input_bus_pointers_p->VINTH_bus_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate INPUT bus by name: VINTH" );
		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, "RSET", SMX_DLL_DIRECTION_OUTPUT, &( output_bus_pointers_p->RSET_bus_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate OUTPUT bus by name: RSET" );
		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, "RSET_MIN_LL", &( parameter_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate parameter by name: RSET_MIN_LL" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	parameter_values_p->RSET_MIN_LL = parameter_p->value.uint_value;
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_parameter_by_name(device_p, "RSET_MAX_LL", &( parameter_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate parameter by name: RSET_MAX_LL" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	parameter_values_p->RSET_MAX_LL = parameter_p->value.uint_value;
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_parameter_by_name(device_p, "RSET_MIN_HL", &( parameter_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate parameter by name: RSET_MIN_HL" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	parameter_values_p->RSET_MIN_HL = parameter_p->value.uint_value;
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_parameter_by_name(device_p, "RSET_MAX_HL", &( parameter_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate parameter by name: RSET_MAX_HL" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	parameter_values_p->RSET_MAX_HL = parameter_p->value.uint_value;
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_parameter_by_name(device_p, "WAKE_INTERVAL", &( parameter_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate parameter by name: WAKE_INTERVAL" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	parameter_values_p->WAKE_INTERVAL = parameter_p->value.double_value;
	if( SMX_DLL_NO_ERROR != context_p->funcs->get_parameter_by_name(device_p, "OUTPUT_DELAY", &( parameter_p ) ) ) {
		context_p->funcs->fatal_error( device_p, "Unable to locate parameter by name: OUTPUT_DELAY" );
		return SMX_DLL_ERROR_RESULT_NOT_FOUND;
	}
	parameter_values_p->OUTPUT_DELAY = parameter_p->value.double_value;

	// assign result
	*result = default_pointers_p;

	// pointer and value assignment complete
	return rv;

}
