#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>

#include "v3dmh.h"

#ifdef MEMWATCH
# include "memwatch.h"
#endif


void *V3DMHCreate(int type);
void V3DMHDestroy(void *p);

void *V3DMHListGetPtr(void **list, int total, int i);
void *V3DMHListInsert(
	void ***list, int *total, int i,
	int type
);
void V3DMHListDelete(void ***list, int *total, int i);
void V3DMHListDeleteAll(void ***list, int *total);

int V3DMHTextureBaseDirectorySet(
	void ***list, int *total, const char *path
);
char *V3DMHTextureBaseDirectoryGet(void **list, int total);

int V3DMHHeightfieldBaseDirectorySet(
	void ***list, int *total, const char *path
);
char *V3DMHHeightfieldBaseDirectoryGet(void **list, int total);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))


/*
 *	Allocates a new model header item structure of the specified
 *	type and sets its type.
 *
 *	Can return NULL on error.
 */
void *V3DMHCreate(int type)
{
	int size = 0;
	void *p = NULL;

	switch(type)
	{
	  case V3DMH_TYPE_COMMENT:
	    size = sizeof(mh_comment_struct);
	    break;


	  case V3DMH_TYPE_VERSION:
	    size = sizeof(mh_version_struct);
	    break;

	  case V3DMH_TYPE_CREATOR:
	    size = sizeof(mh_creator_struct);
	    break;

	  case V3DMH_TYPE_AUTHOR:
	    size = sizeof(mh_author_struct);
	    break;


	  case V3DMH_TYPE_HEIGHTFIELD_BASE_DIRECTORY:
	    size = sizeof(mh_heightfield_base_directory_struct);
	    break;

	  case V3DMH_TYPE_TEXTURE_BASE_DIRECTORY:
	    size = sizeof(mh_texture_base_directory_struct);
	    break;

	  case V3DMH_TYPE_TEXTURE_LOAD:
	    size = sizeof(mh_texture_load_struct);
	    break;

	  case V3DMH_TYPE_COLOR_SPECIFICATION:
	    size = sizeof(mh_color_specification_struct);
	    break;

	  default:
	    fprintf(
		stderr,
		"V3DMHCreate(): Unsupported primitive type %i\n",
		type
	    );
	}

	if(size > 0)
	{
	    p = (void *)calloc(1, size);
	    (*(int *)p) = type;
	}

	return(p);
}

/*
 *	Deallocates the model header item structure p and any allocated
 *	sub resources.
 */
void V3DMHDestroy(void *p)
{
	int i, type;
	mh_comment_struct *comment;
	mh_creator_struct *creator;
	mh_author_struct *author;
	mh_heightfield_base_directory_struct *heightfield_base_directory;
	mh_texture_base_directory_struct *texture_base_directory;
	mh_texture_load_struct *texture_load;
	mh_color_specification_struct *color_specification;


	if(p == NULL)
	    return;
	else
	    type = (*(int *)p);

	/* Deallocate any substructures. */
	switch(type)
	{
	  case V3DMH_TYPE_COMMENT:
	    comment = p;
	    for(i = 0; i < comment->total_lines; i++)
		free(comment->line[i]);
	    free(comment->line);
	    break;

	  case V3DMH_TYPE_CREATOR:
	    creator = p;
	    free(creator->creator);
	    break;

	  case V3DMH_TYPE_AUTHOR:
	    author = p;
	    free(author->author);
	    break;


	  case V3DMH_TYPE_HEIGHTFIELD_BASE_DIRECTORY:
	    heightfield_base_directory = p;
	    free(heightfield_base_directory->path);
	    break;

	  case V3DMH_TYPE_TEXTURE_BASE_DIRECTORY:
	    texture_base_directory = p;
	    free(texture_base_directory->path);
	    break;

	  case V3DMH_TYPE_TEXTURE_LOAD:
	    texture_load = p;
	    free(texture_load->name);
	    free(texture_load->path);
	    break;

	  case V3DMH_TYPE_COLOR_SPECIFICATION:
	    color_specification = p;
	    free(color_specification->name);
	    break;
	}

	free(p);

	return;
}


/*
 *	Checks if item i exists in the list and returns its pointer
 *	if it exists or NULL if it does not.
 */
void *V3DMHListGetPtr(void **list, int total, int i)
{
	if((list == NULL) ||
	   (i < 0) ||
	   (i >= total)
	)
	    return(NULL);
	else
	    return(list[i]);
}

/*
 *	Inserts a new model header item at list position i and
 *	returns the pointer to the model primitive or NULL on failure.
 *
 *	If i is negative then the item will be appended to the list.
 *
 *	If i is greater or equal to (*total) + 1 then i will
 *	be set to the value of (*total).
 *
 *	Both list and total need to be non NULL.
 */
void *V3DMHListInsert(
	void ***list, int *total, int i,
	int type
)
{
	if((list == NULL) ||
	   (total == NULL)
	)
	    return(NULL);

	if((*total) < 0)
	    (*total) = 0;

	/* Allocate more pointers for the list. */
	(*total) = (*total) + 1;
	(*list) = (void **)realloc(
	    *list,
	    (*total) * sizeof(void *)
	);
	if((*list) == NULL)
	{
	    (*total) = 0;
	    return(NULL);
	}

	/* Append or insert? */
	if(i < 0)
	{
	    /* Append. */

	    i = (*total) - 1;

	    (*list)[i] = V3DMHCreate(type);
	}
	else
	{
	    /* Insert. */
	    int n;

	    if(i >= (*total))
		i = (*total) - 1;

	    /* Shift pointers. */
	    for(n = (*total) - 1; n > i; n--)
		(*list)[n] = (*list)[n - 1];

	    (*list)[i] = V3DMHCreate(type);
	}

	return((*list)[i]);
}

/*
 *	Deletes the item i from the list.  Item pointer i on the list
 *	will then be set to NULL.
 */
void V3DMHListDelete(void ***list, int *total, int i)
{
	void *p;

	if((list == NULL) ||
	   (total == NULL)
	)
	    return;

	/* Item exists and is allocated? */
	p = V3DMHListGetPtr(*list, *total, i);
	if(p != NULL)
	{
	    V3DMHDestroy(p);
	    (*list)[i] = NULL;	/* Reset item pointer to NULL. */
	}

	return;
}

/*
 *	Deletes the entire list and resets the pointers list
 *	and total to NULL.
 */
void V3DMHListDeleteAll(void ***list, int *total)
{
	int i;

	if((list == NULL) ||
	   (total == NULL)
	)
	    return;

	for(i = 0; i < (*total); i++)
	    V3DMHListDelete(list, total, i);

	free(*list);
	*list = NULL;

	*total = 0;

	return;
}

/*
 *	Sets the texture base directory in the given list of model header
 *	items.
 *
 *	A new item will be prepended if no existing 
 *	mh_texture_base_directory_struct is found in the list.
 *
 *	Returns the newly added item index or -1 on failure.
 */
int V3DMHTextureBaseDirectorySet(
	void ***list, int *total, const char *path
)
{
	int i, n;
	void *h;
	mh_texture_base_directory_struct *h_texture_base_dir;

	if((list == NULL) || (total == NULL))
	    return(-1);

	/* Check for existing item and set it if found. */
	for(i = 0, n = -1; i < (*total); i++)
	{
	    h = (*list)[i];
	    if(h == NULL)
		continue;

	    if((*(int *)h) == V3DMH_TYPE_TEXTURE_BASE_DIRECTORY)
	    {
		h_texture_base_dir = (mh_texture_base_directory_struct *)h;

		free(h_texture_base_dir->path);
		h_texture_base_dir->path = STRDUP(path);

		n = i;
	    }
	}
	/* Set any above? */
	if(n > -1)
	    return(n);

	/* Allocate more pointers and shift. */
	(*total) = (*total) + 1;
	(*list) = (void **)realloc(
	    *list, (*total) * sizeof(void *)
	);
	if((*list) == NULL)
	{
	    (*total) = 0;
	    return(-1);
	}

	for(n = (*total) - 1; n > 0; n--)
	    (*list)[n] = (*list)[n - 1];

	n = 0;

	/* Prepend a new item. */
	h_texture_base_dir = (mh_texture_base_directory_struct *)V3DMHCreate(
	    V3DMH_TYPE_TEXTURE_BASE_DIRECTORY
	);
	if(h_texture_base_dir == NULL)
	    return(-1);

	/* Set new path. */
	free(h_texture_base_dir->path);
	h_texture_base_dir->path = STRDUP(path);

	(*list)[n] = h_texture_base_dir;

	return(n);
}

/*
 *	Returns the texture base directory if specified as the first
 *	mh_texture_base_directory_struct structure found in the
 *	given list.
 *
 *	Can return NULL on error, the returned pointer should not be
 *	deallocated as it comes from the header item.
 */
char *V3DMHTextureBaseDirectoryGet(void **list, int total)
{
	int i;
	void *h;
	mh_texture_base_directory_struct *h_texture_base_dir;

	if(list == NULL)
	    return(NULL);

	/* Check for existing item. */
	for(i = 0; i < total; i++)
	{
	    h = list[i];
	    if(h == NULL)
		continue;

	    if((*(int *)h) == V3DMH_TYPE_TEXTURE_BASE_DIRECTORY)
	    {
		h_texture_base_dir = (mh_texture_base_directory_struct *)h;

		return(h_texture_base_dir->path);
	    }
	}

	return(NULL);
}

/*
 *      Sets the heightfield base directory in the given list of model
 *	header items.
 *
 *      A new item will be prepended if no existing
 *      mh_heightfield_base_directory_struct is found in the list.
 *
 *      Returns the newly added item index or -1 on failure.
 */
int V3DMHHeightfieldBaseDirectorySet(
	void ***list, int *total, const char *path
)
{
	int i, n;
	void *h;
	mh_heightfield_base_directory_struct *h_heightfield_base_dir;

	if((list == NULL) || (total == NULL))
	    return(-1);

	/* Check for existing item and set it if found. */
	for(i = 0, n = -1; i < (*total); i++)
	{
	    h = (*list)[i];
	    if(h == NULL)
		continue;

	    if((*(int *)h) == V3DMH_TYPE_HEIGHTFIELD_BASE_DIRECTORY)
	    {
		h_heightfield_base_dir = (mh_heightfield_base_directory_struct *)h;

		free(h_heightfield_base_dir->path);
		h_heightfield_base_dir->path = STRDUP(path);

		n = i;
	    }
	}
	/* Set any above? */
	if(n > -1)
	    return(n);

	/* Allocate more pointers and shift. */
	(*total) = (*total) + 1;
	(*list) = (void **)realloc(
	    *list, (*total) * sizeof(void *)
	);
	if((*list) == NULL)
	{
	    (*total) = 0;
	    return(-1);
	}

	for(n = (*total) - 1; n > 0; n--)
	    (*list)[n] = (*list)[n - 1];

	n = 0;

	/* Prepend a new item. */
	h_heightfield_base_dir = (mh_heightfield_base_directory_struct *)V3DMHCreate(
	    V3DMH_TYPE_HEIGHTFIELD_BASE_DIRECTORY
	);
	if(h_heightfield_base_dir == NULL)
	    return(-1);

	/* Set new path. */
	free(h_heightfield_base_dir->path);
	h_heightfield_base_dir->path = STRDUP(path);

	(*list)[n] = h_heightfield_base_dir;

	return(n);
}       

/*
 *      Returns the heightfield base directory if specified as the first
 *      mh_heightfield_base_directory_struct structure found in the
 *      given list.
 *
 *      Can return NULL on error, the returned pointer should not be
 *      deallocated as it comes from the header item.
 */
char *V3DMHHeightfieldBaseDirectoryGet(void **list, int total)
{
	int i;
	void *h;
	mh_heightfield_base_directory_struct *h_heightfield_base_dir;

	if(list == NULL)
	    return(NULL);

	/* Check for existing item. */
	for(i = 0; i < total; i++)
	{
	    h = list[i];
	    if(h == NULL)
		continue;

	    if((*(int *)h) == V3DMH_TYPE_HEIGHTFIELD_BASE_DIRECTORY)
	    {
		h_heightfield_base_dir = (mh_heightfield_base_directory_struct *)h;

		return(h_heightfield_base_dir->path);
	    }
	}

	return(NULL);
}

