/***************************************************************************

    devintrf.c

    Device interface functions.

    Copyright Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.

***************************************************************************/

#include "driver.h"
#include "cpuintrf.h"



/***************************************************************************
    CONSTANTS
***************************************************************************/

#define TEMP_STRING_POOL_ENTRIES		16
#define MAX_STRING_LENGTH				256

// ->started states
//
// Only 0 and 1 are externally visible in practice, making it a
// boolean //for external users

enum {
	DEVICE_STOPPED = 0,
	DEVICE_STARTED = 1,
	DEVICE_STARTING = 2,
	DEVICE_DELAYED = 3
};


/***************************************************************************
    GLOBAL VARIABLES
***************************************************************************/

static char temp_string_pool[TEMP_STRING_POOL_ENTRIES][MAX_STRING_LENGTH];
static int temp_string_pool_index;



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/

static void device_list_stop(running_machine *machine);
static void device_list_reset(running_machine *machine);
static void set_default_string(UINT32 state, char *buffer);



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    get_temp_string_buffer - return a pointer to
    a temporary string buffer
-------------------------------------------------*/

static char *get_temp_string_buffer(void)
{
	char *string = &temp_string_pool[temp_string_pool_index++ % TEMP_STRING_POOL_ENTRIES][0];
	string[0] = 0;
	return string;
}


/*-------------------------------------------------
    device_matches_type - does a device match
    the provided type, taking wildcards into
    effect?
-------------------------------------------------*/

INLINE int device_matches_type(const device_config *device, device_type type)
{
	return (type == DEVICE_TYPE_WILDCARD) ? TRUE : (device->type == type);
}



/***************************************************************************
    DEVICE CONFIGURATION
***************************************************************************/

/*-------------------------------------------------
    device_list_add - add a new device to the
    end of a device list
-------------------------------------------------*/

device_config *device_list_add(device_config **listheadptr, const device_config *owner, device_type type, const char *tag, UINT32 clock)
{
	device_config **devptr, **tempdevptr;
	device_config *device, *tempdevice;
	UINT32 configlen;

	assert(listheadptr != NULL);
	assert(type != NULL);
	assert(tag != NULL);

	/* find the end of the list, and ensure no duplicates along the way */
	for (devptr = listheadptr; *devptr != NULL; devptr = &(*devptr)->next)
		if (strcmp(tag, (*devptr)->tag) == 0)
			fatalerror("Attempted to add duplicate device: type=%s tag=%s\n", device_get_name(*devptr), tag);

	/* get the size of the inline config */
	configlen = (UINT32)devtype_get_info_int(type, DEVINFO_INT_INLINE_CONFIG_BYTES);

	/* allocate a new device */
	device = (device_config *)malloc_or_die(sizeof(*device) + strlen(tag) + configlen);

	/* populate device relationships */
	device->next = NULL;
	device->owner = (device_config *)owner;
	device->typenext = NULL;
	device->classnext = NULL;

	/* populate device properties */
	device->type = type;
	device->devclass = (device_class)(INT32)devtype_get_info_int(type, DEVINFO_INT_CLASS);
	device->set_info = (device_set_info_func)devtype_get_info_fct(type, DEVINFO_FCT_SET_INFO);
	device->execute = NULL;

	/* populate device configuration */
	device->clock = clock;
	device->static_config = NULL;
	device->inline_config = (configlen == 0) ? NULL : (device->tag + strlen(tag) + 1);

	/* ensure live fields are all cleared */
	device->machine = NULL;
	device->started = DEVICE_STOPPED;
	device->token = NULL;
	device->tokenbytes = 0;
	device->region = NULL;
	device->regionbytes = 0;

	/* append the tag */
	strcpy(device->tag, tag);

	/* reset the inline_config to 0 */
	if (configlen > 0)
		memset(device->inline_config, 0, configlen);

	/* fetch function pointers to the core functions */

	/* before adding us to the global list, add us to the end of the type list */
	tempdevice = (device_config *)device_list_first(*listheadptr, type);
	for (tempdevptr = &tempdevice; *tempdevptr != NULL; tempdevptr = &(*tempdevptr)->typenext) ;
	*tempdevptr = device;

	/* and to the end of the class list */
	tempdevice = (device_config *)device_list_class_first(*listheadptr, device->devclass);
	for (tempdevptr = &tempdevice; *tempdevptr != NULL; tempdevptr = &(*tempdevptr)->classnext) ;
	*tempdevptr = device;

	/* link us to the end of the master list and return */
	*devptr = device;
	return device;
}


/*-------------------------------------------------
    device_list_remove - remove a device from a
    device list
-------------------------------------------------*/

void device_list_remove(device_config **listheadptr, const char *tag)
{
	device_config **devptr, **tempdevptr;
	device_config *device, *tempdevice;

	assert(listheadptr != NULL);
	assert(tag != NULL);

	/* find the device in the list */
	for (devptr = listheadptr; *devptr != NULL; devptr = &(*devptr)->next)
		if (strcmp(tag, (*devptr)->tag) == 0)
			break;
	device = *devptr;
	if (device == NULL)
		fatalerror("Attempted to remove non-existant device: tag=%s\n", tag);

	/* before removing us from the global list, remove us from the type list */
	tempdevice = (device_config *)device_list_first(*listheadptr, device->type);
	for (tempdevptr = &tempdevice; *tempdevptr != device; tempdevptr = &(*tempdevptr)->typenext) ;
	assert(*tempdevptr == device);
	*tempdevptr = device->typenext;

	/* and from the class list */
	tempdevice = (device_config *)device_list_class_first(*listheadptr, device->devclass);
	for (tempdevptr = &tempdevice; *tempdevptr != device; tempdevptr = &(*tempdevptr)->classnext) ;
	assert(*tempdevptr == device);
	*tempdevptr = device->classnext;

	/* remove the device from the list */
	*devptr = device->next;

	/* free the device object */
	free(device);
}


/*-------------------------------------------------
    device_build_tag - build a tag that combines
    the device's name and the given tag
-------------------------------------------------*/

const char *device_build_tag(astring *dest, const device_config *device, const char *tag)
{
	if (device != NULL)
	{
		astring_cpyc(dest, device->tag);
		astring_catc(dest, ":");
		astring_catc(dest, tag);
	}
	else
		astring_cpyc(dest, tag);
	return astring_c(dest);
}


/*-------------------------------------------------
    device_inherit_tag - build a tag with the same
    device prefix as the source tag
-------------------------------------------------*/

const char *device_inherit_tag(astring *dest, const char *sourcetag, const char *tag)
{
	const char *divider = strrchr(sourcetag, ':');
	if (divider != NULL)
	{
		astring_cpych(dest, sourcetag, divider + 1 - sourcetag);
		astring_catc(dest, tag);
	}
	else
		astring_cpyc(dest, tag);
	return astring_c(dest);
}



/***************************************************************************
    TYPE-BASED DEVICE ACCESS
***************************************************************************/

/*-------------------------------------------------
    device_list_items - return the number of
    items of a given type; DEVICE_TYPE_WILDCARD
    is allowed
-------------------------------------------------*/

int device_list_items(const device_config *listhead, device_type type)
{
	const device_config *curdev;
	int count = 0;

	/* locate all devices */
	if (type == DEVICE_TYPE_WILDCARD)
	{
		for (curdev = listhead; curdev != NULL; curdev = curdev->next)
			count++;
	}

	/* locate all devices of a given type */
	else
	{
		for (curdev = listhead; curdev != NULL && curdev->type != type; curdev = curdev->next) ;
		for ( ; curdev != NULL; curdev = curdev->typenext)
			count++;
	}

	return count;
}


/*-------------------------------------------------
    device_list_first - return the first device
    in the list of a given type;
    DEVICE_TYPE_WILDCARD is allowed
-------------------------------------------------*/

const device_config *device_list_first(const device_config *listhead, device_type type)
{
	const device_config *curdev;

	/* first of any device type */
	if (type == DEVICE_TYPE_WILDCARD)
		return listhead;

	/* first of a given type */
	for (curdev = listhead; curdev != NULL && curdev->type != type; curdev = curdev->next) ;
	return curdev;
}


/*-------------------------------------------------
    device_list_next - return the next device
    in the list of a given type;
    DEVICE_TYPE_WILDCARD is allowed
-------------------------------------------------*/

const device_config *device_list_next(const device_config *prevdevice, device_type type)
{
	assert(prevdevice != NULL);
	return (type == DEVICE_TYPE_WILDCARD) ? prevdevice->next : prevdevice->typenext;
}


/*-------------------------------------------------
    device_list_find_by_tag - retrieve a device
    configuration based on a type and tag;
    DEVICE_TYPE_WILDCARD is allowed
-------------------------------------------------*/

const device_config *device_list_find_by_tag(const device_config *listhead, const char *tag)
{
	const device_config *curdev;

	assert(tag != NULL);

	/* locate among all devices */
	for (curdev = listhead; curdev != NULL; curdev = curdev->next)
		if (strcmp(tag, curdev->tag) == 0)
			return curdev;

	/* fail */
	return NULL;
}


/*-------------------------------------------------
    device_list_index - return the index of a
    device based on its type and tag;
    DEVICE_TYPE_WILDCARD is allowed
-------------------------------------------------*/

int device_list_index(const device_config *listhead, device_type type, const char *tag)
{
	const device_config *curdev;
	int index = 0;

	assert(tag != NULL);

	/* locate among all devices */
	if (type == DEVICE_TYPE_WILDCARD)
	{
		for (curdev = listhead; curdev != NULL; curdev = curdev->next)
		{
			if (strcmp(tag, curdev->tag) == 0)
				return index;
			index++;
		}
	}

	/* locate among all devices of a given type */
	else
	{
		for (curdev = listhead; curdev != NULL && curdev->type != type; curdev = curdev->next) ;
		for ( ; curdev != NULL; curdev = curdev->typenext)
		{
			if (strcmp(tag, curdev->tag) == 0)
				return index;
			index++;
		}
	}

	return -1;
}


/*-------------------------------------------------
    device_list_find_by_index - retrieve a device
    configuration based on a type and index
-------------------------------------------------*/

const device_config *device_list_find_by_index(const device_config *listhead, device_type type, int index)
{
	const device_config *curdev;

	/* locate among all devices */
	if (type == DEVICE_TYPE_WILDCARD)
	{
		for (curdev = listhead; curdev != NULL; curdev = curdev->next)
			if (index-- == 0)
				return curdev;
	}

	/* locate among all devices of a given type */
	else
	{
		for (curdev = listhead; curdev != NULL && curdev->type != type; curdev = curdev->next) ;
		for ( ; curdev != NULL; curdev = curdev->typenext)
			if (index-- == 0)
				return curdev;
	}

	/* fail */
	return NULL;
}



/***************************************************************************
    CLASS-BASED DEVICE ACCESS
***************************************************************************/

/*-------------------------------------------------
    device_list_class_items - return the number of
    items of a given class
-------------------------------------------------*/

int device_list_class_items(const device_config *listhead, device_class devclass)
{
	const device_config *curdev;
	int count = 0;

	/* locate all devices of a given class */
	for (curdev = listhead; curdev != NULL && curdev->devclass != devclass; curdev = curdev->next) ;
	for ( ; curdev != NULL; curdev = curdev->classnext)
		count++;

	return count;
}


/*-------------------------------------------------
    device_list_class_first - return the first
    device in the list of a given class
-------------------------------------------------*/

const device_config *device_list_class_first(const device_config *listhead, device_class devclass)
{
	const device_config *curdev;

	/* first of a given class */
	for (curdev = listhead; curdev != NULL && curdev->devclass != devclass; curdev = curdev->next) ;
	return curdev;
}


/*-------------------------------------------------
    device_list_class_next - return the next
    device in the list of a given class
-------------------------------------------------*/

const device_config *device_list_class_next(const device_config *prevdevice, device_class devclass)
{
	assert(prevdevice != NULL);
	return prevdevice->classnext;
}


/*-------------------------------------------------
    device_list_class_index - return the index of a
    device based on its class and tag
-------------------------------------------------*/

int device_list_class_index(const device_config *listhead, device_class devclass, const char *tag)
{
	const device_config *curdev;
	int index = 0;

	assert(tag != NULL);

	/* locate among all devices of a given class */
	for (curdev = listhead; curdev != NULL && curdev->devclass != devclass; curdev = curdev->next) ;
	for ( ; curdev != NULL; curdev = curdev->classnext)
	{
		if (strcmp(tag, curdev->tag) == 0)
			return index;
		index++;
	}

	return -1;
}


/*-------------------------------------------------
    device_list_class_find_by_index - retrieve a
    device configuration based on a class and
    index
-------------------------------------------------*/

const device_config *device_list_class_find_by_index(const device_config *listhead, device_class devclass, int index)
{
	const device_config *curdev;

	/* locate among all devices of a given class */
	for (curdev = listhead; curdev != NULL && curdev->devclass != devclass; curdev = curdev->next) ;
	for ( ; curdev != NULL; curdev = curdev->classnext)
		if (index-- == 0)
			return curdev;

	/* fail */
	return NULL;
}



/***************************************************************************
    LIVE DEVICE MANAGEMENT
***************************************************************************/

/*-------------------------------------------------
    device_list_attach_machine - "attach" a
    running_machine to its list of devices
-------------------------------------------------*/

void device_list_attach_machine(running_machine *machine)
{
	device_config *device;

	assert(machine != NULL);

	/* iterate over devices and assign the machine to them */
	for (device = (device_config *)machine->config->devicelist; device != NULL; device = device->next)
		device->machine = machine;
}


/*-------------------------------------------------
    device_list_start - start the configured list
    of devices for a machine
-------------------------------------------------*/

void device_list_start(running_machine *machine)
{
	device_config *device;
	int numstarted = 0;
	int devcount = 0;

	assert(machine != NULL);

	/* add an exit callback for cleanup */
	add_reset_callback(machine, device_list_reset);
	add_exit_callback(machine, device_list_stop);

	/* iterate over devices and allocate memory for them */
	for (device = (device_config *)machine->config->devicelist; device != NULL; device = device->next)
	{
		assert(!device->started);
		assert(device->machine == machine);
		assert(device->token == NULL);
		assert(device->type != NULL);

		devcount++;

		/* get the size of the token data; we do it directly because we can't call device_get_info_* with no token */
		device->tokenbytes = device_get_info_int(device, DEVINFO_INT_TOKEN_BYTES);
		if (device->tokenbytes == 0)
			fatalerror("Device %s specifies a 0 token length!\n", device_get_name(device));

		/* allocate memory for the token */
		device->token = malloc_or_die(device->tokenbytes);
		memset(device->token, 0, device->tokenbytes);

		/* fill in the remaining runtime fields */
		device->execute = (device_execute_func)device_get_info_fct(device, DEVINFO_FCT_EXECUTE);
		device->machine = machine;
		device->region = memory_region(machine, device->tag);
		device->regionbytes = memory_region_length(machine, device->tag);
	}

	/* iterate until we've started everything */
	while (numstarted < devcount)
	{
		int prevstarted = numstarted;

		/* iterate over devices and start them */
		for (device = (device_config *)machine->config->devicelist; device != NULL; device = device->next)
		{
			device_start_func start = (device_start_func)device_get_info_fct(device, DEVINFO_FCT_START);
			assert(start != NULL);

			if (!device->started)
			{
				device->started = DEVICE_STARTING;
				(*start)(device);

				/* if the start was delayed, move back to the stopped state, otherwise count it */
				if (device->started == DEVICE_DELAYED)
					device->started = DEVICE_STOPPED;
				else
				{
					device->started = DEVICE_STARTED;
					numstarted++;
				}
			}
		}

		/* if we didn't start anything new, we're in trouble */
		if (numstarted == prevstarted)
			fatalerror("Circular dependency in device startup; unable to start %d/%d devices\n", devcount - numstarted, devcount);
	}
}


/*-------------------------------------------------
    device_delay_init - delay the startup of a
    given device for dependency reasons
-------------------------------------------------*/

void device_delay_init(const device_config *device)
{
	if (device->started != DEVICE_STARTING && device->started != DEVICE_DELAYED)
		fatalerror("Error: Calling device_delay_init on a device not in the process of starting.");
	((device_config *)device)->started = DEVICE_DELAYED;
}


/*-------------------------------------------------
    device_list_stop - stop the configured list
    of devices for a machine
-------------------------------------------------*/

static void device_list_stop(running_machine *machine)
{
	device_config *device;

	assert(machine != NULL);

	/* iterate over devices and stop them */
	for (device = (device_config *)machine->config->devicelist; device != NULL; device = device->next)
	{
		device_stop_func stop = (device_stop_func)device_get_info_fct(device, DEVINFO_FCT_STOP);

		assert(device->token != NULL);
		assert(device->type != NULL);

		/* if we have a stop function, call it */
		if (stop != NULL)
			(*stop)(device);

		/* free allocated memory for the token */
		if (device->token != NULL)
			free(device->token);

		/* reset all runtime fields */
		device->token = NULL;
		device->tokenbytes = 0;
		device->machine = NULL;
		device->region = NULL;
		device->regionbytes = 0;
	}
}


/*-------------------------------------------------
    device_list_reset - reset the configured list
    of devices for a machine
-------------------------------------------------*/

static void device_list_reset(running_machine *machine)
{
	const device_config *device;

	assert(machine != NULL);

	/* iterate over devices and stop them */
	for (device = (device_config *)machine->config->devicelist; device != NULL; device = device->next)
		device_reset(device);
}


/*-------------------------------------------------
    device_reset - reset a device based on an
    allocated device_config
-------------------------------------------------*/

void device_reset(const device_config *device)
{
	device_reset_func reset;

	assert(device != NULL);
	assert(device->token != NULL);
	assert(device->type != NULL);

	/* if we have a reset function, call it */
	reset = (device_reset_func)device_get_info_fct(device, DEVINFO_FCT_RESET);
	if (reset != NULL)
		(*reset)(device);
}


/*-------------------------------------------------
    device_set_clock - change the clock on a
    device
-------------------------------------------------*/

void device_set_clock(const device_config *device, UINT32 clock)
{
	device_config *devicerw = (device_config *)device;

	/* not much for now */
	devicerw->clock = clock;
}



/***************************************************************************
    DEVICE INFORMATION GETTERS
***************************************************************************/

/*-------------------------------------------------
    device_get_info_int - return an integer state
    value from an allocated device
-------------------------------------------------*/

INT64 device_get_info_int(const device_config *device, UINT32 state)
{
	deviceinfo info;

	assert(device != NULL);
	assert(device->type != NULL);
	assert(state >= DEVINFO_INT_FIRST && state <= DEVINFO_INT_LAST);

	/* retrieve the value */
	info.i = 0;
	(*device->type)(device, state, &info);
	return info.i;
}


/*-------------------------------------------------
    device_get_info_ptr - return a pointer state
    value from an allocated device
-------------------------------------------------*/

void *device_get_info_ptr(const device_config *device, UINT32 state)
{
	deviceinfo info;

	assert(device != NULL);
	assert(device->type != NULL);
	assert(state >= DEVINFO_PTR_FIRST && state <= DEVINFO_PTR_LAST);

	/* retrieve the value */
	info.p = NULL;
	(*device->type)(device, state, &info);
	return info.p;
}


/*-------------------------------------------------
    device_get_info_fct - return a function
    pointer state value from an allocated device
-------------------------------------------------*/

genf *device_get_info_fct(const device_config *device, UINT32 state)
{
	deviceinfo info;

	assert(device != NULL);
	assert(device->type != NULL);
	assert(state >= DEVINFO_FCT_FIRST && state <= DEVINFO_FCT_LAST);

	/* retrieve the value */
	info.f = 0;
	(*device->type)(device, state, &info);
	return info.f;
}


/*-------------------------------------------------
    device_get_info_string - return a string value
    from an allocated device
-------------------------------------------------*/

const char *device_get_info_string(const device_config *device, UINT32 state)
{
	deviceinfo info;

	assert(device != NULL);
	assert(device->type != NULL);
	assert(state >= DEVINFO_STR_FIRST && state <= DEVINFO_STR_LAST);

	/* retrieve the value */
	info.s = get_temp_string_buffer();
	(*device->type)(device, state, &info);
	if (info.s[0] == 0)
		set_default_string(state, info.s);
	return info.s;
}



/***************************************************************************
    DEVICE TYPE INFORMATION SETTERS
***************************************************************************/

/*-------------------------------------------------
    devtype_get_info_int - return an integer value
    from a device type (does not need to be
    allocated)
-------------------------------------------------*/

INT64 devtype_get_info_int(device_type type, UINT32 state)
{
	deviceinfo info;

	assert(type != NULL);
	assert(state >= DEVINFO_INT_FIRST && state <= DEVINFO_INT_LAST);

	/* retrieve the value */
	info.i = 0;
	(*type)(NULL, state, &info);
	return info.i;
}


/*-------------------------------------------------
    devtype_get_info_int - return a function
    pointer from a device type (does not need to
    be allocated)
-------------------------------------------------*/

genf *devtype_get_info_fct(device_type type, UINT32 state)
{
	deviceinfo info;

	assert(type != NULL);
	assert(state >= DEVINFO_FCT_FIRST && state <= DEVINFO_FCT_LAST);

	/* retrieve the value */
	info.f = 0;
	(*type)(NULL, state, &info);
	return info.f;
}


/*-------------------------------------------------
    devtype_get_info_string - return a string value
    from a device type (does not need to be
    allocated)
-------------------------------------------------*/

const char *devtype_get_info_string(device_type type, UINT32 state)
{
	deviceinfo info;

	assert(type != NULL);
	assert(state >= DEVINFO_STR_FIRST && state <= DEVINFO_STR_LAST);

	/* retrieve the value */
	info.s = get_temp_string_buffer();
	(*type)(NULL, state, &info);
	if (info.s[0] == 0)
		set_default_string(state, info.s);
	return info.s;
}



/***************************************************************************
    DEVICE INFORMATION SETTERS
***************************************************************************/

/*-------------------------------------------------
    device_set_info_int - set an integer state
    value for an allocated device
-------------------------------------------------*/

void device_set_info_int(const device_config *device, UINT32 state, INT64 data)
{
	deviceinfo info;

	assert(device != NULL);
	assert(device->token != NULL);
	assert(device->type != NULL);
	assert(state >= DEVINFO_INT_FIRST && state <= DEVINFO_INT_LAST);

	/* set the value */
	info.i = data;
	(*device->set_info)(device, state, &info);
}


/*-------------------------------------------------
    device_set_info_ptr - set a pointer state
    value for an allocated device
-------------------------------------------------*/

void device_set_info_ptr(const device_config *device, UINT32 state, void *data)
{
	deviceinfo info;

	assert(device != NULL);
	assert(device->token != NULL);
	assert(device->type != NULL);
	assert(state >= DEVINFO_PTR_FIRST && state <= DEVINFO_PTR_LAST);

	/* set the value */
	info.p = data;
	(*device->set_info)(device, state, &info);
}


/*-------------------------------------------------
    device_set_info_fct - set a function pointer
    state value for an allocated device
-------------------------------------------------*/

void device_set_info_fct(const device_config *device, UINT32 state, genf *data)
{
	deviceinfo info;

	assert(device != NULL);
	assert(device->token != NULL);
	assert(device->type != NULL);
	assert(state >= DEVINFO_FCT_FIRST && state <= DEVINFO_FCT_LAST);

	/* set the value */
	info.f = data;
	(*device->set_info)(device, state, &info);
}



/***************************************************************************
    DEVICE INFORMATION SETTERS
***************************************************************************/

/*-------------------------------------------------
    set_default_string - compute a default string
    if none is provided
-------------------------------------------------*/

static void set_default_string(UINT32 state, char *buffer)
{
	switch (state)
	{
		case DEVINFO_STR_NAME:			strcpy(buffer, "Custom");						break;
		case DEVINFO_STR_FAMILY:		strcpy(buffer, "Custom");						break;
		case DEVINFO_STR_VERSION:		strcpy(buffer, "1.0");							break;
		case DEVINFO_STR_SOURCE_FILE:	strcpy(buffer, __FILE__);						break;
		case DEVINFO_STR_CREDITS:		strcpy(buffer, "Copyright Nicola Salmoria and the MAME Team"); break;
	}
}
