/************************************************************************
* array.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "array.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>


/* initialises an array - useful for those created on the stack */
void array_init(array_t *a, uint32_t type)
{
	a->type = type;
	a->length = 0;
	a->size = 0;
	a->data = NULL;
}

/* create a new array of type */
array_t *array_create(uint32_t type)
{
	array_t *a;

	a = malloc(sizeof(array_t));
	a->type = type;
	a->length = 0;
	a->size = 0;
	a->data = NULL;

	return a;
}

/* create a new array as a copy of a */
array_t *array_copy(array_t *a)
{
	array_t *r = array_create(ARRAY_TYPE_STRING);

	if (a) {
		int i;
		r->type = a->type;
		switch (a->type) {
		case ARRAY_TYPE_STRING:
			for (i=0; i<a->length; i++) {
				array_push_string(r,((char**)(a->data))[i]);
			}
			break;
		case ARRAY_TYPE_FLOAT:
			for (i=0; i<a->length; i++) {
				array_push_float(r,((float*)(a->data))[i]);
			}
			break;
		case ARRAY_TYPE_INT:
			for (i=0; i<a->length; i++) {
				array_push_int(r,((uint32_t*)(a->data))[i]);
			}
			break;
		case ARRAY_TYPE_PTR:
			for (i=0; i<a->length; i++) {
				array_push_ptr(r,((unsigned char**)(a->data))[i]);
			}
			break;
		default:;
		}
	}

	return r;
}

/* compare two arrays */
int array_cmp(array_t *a1, array_t *a2)
{
	int i;
	char** c1;
	char** c2;
	int *i1;
	int *i2;
	float *f1;
	float *f2;
	if (!a1 || !a2)
		return -1;
	if (a1->length != a2->length)
		return -1;

	if (a1->type == ARRAY_TYPE_STRING && a2->type == ARRAY_TYPE_STRING) {
		c1 = a1->data;
		c2 = a2->data;
		for (i=0; i<a1->length; i++) {
			if (!c1[i] || !c2[i] || strcmp(c1[i],c2[i]))
				return 1;
		}
	}else if (a1->type == ARRAY_TYPE_STRING && a2->type == ARRAY_TYPE_INT) {
		c1 = a1->data;
		i2 = a2->data;
		for (i=0; i<a1->length; i++) {
			if (!c1[i] || i2[i] != strtol(c1[i],NULL,10))
				return 1;
		}
	}else if (a1->type == ARRAY_TYPE_STRING && a2->type == ARRAY_TYPE_FLOAT) {
		c1 = a1->data;
		f2 = a2->data;
		for (i=0; i<a1->length; i++) {
			if (!c1[i] || f2[i] != strtof(c1[i],NULL))
				return 1;
		}
	}else if (a1->type == ARRAY_TYPE_INT && a2->type == ARRAY_TYPE_STRING) {
		i1 = a1->data;
		c2 = a2->data;
		for (i=0; i<a1->length; i++) {
			if (!c2[i] || i1[i] != strtol(c2[i],NULL,10))
				return 1;
		}
	}else if (a1->type == ARRAY_TYPE_FLOAT && a2->type == ARRAY_TYPE_STRING) {
		f1 = a1->data;
		c2 = a2->data;
		for (i=0; i<a1->length; i++) {
			if (!c2[i] || f1[i] != strtof(c2[i],NULL))
				return 1;
		}
	}else if (a1->type == ARRAY_TYPE_INT && a2->type == ARRAY_TYPE_INT) {
		i1 = a1->data;
		i2 = a2->data;
		for (i=0; i<a1->length; i++) {
			if (i1[i] != i2[i])
				return 1;
		}
	}else if (a1->type == ARRAY_TYPE_FLOAT && a2->type == ARRAY_TYPE_FLOAT) {
		f1 = a1->data;
		f2 = a2->data;
		for (i=0; i<a1->length; i++) {
			if (f1[i] != f2[i])
				return 1;
		}
	}else if (a1->type == ARRAY_TYPE_INT && a2->type == ARRAY_TYPE_FLOAT) {
		i1 = a1->data;
		f2 = a2->data;
		for (i=0; i<a1->length; i++) {
			if (i1[i] != (int)f2[i])
				return 1;
		}
	}else if (a1->type == ARRAY_TYPE_FLOAT && a2->type == ARRAY_TYPE_INT) {
		f1 = a1->data;
		i2 = a2->data;
		for (i=0; i<a1->length; i++) {
			if ((int)f1[i] != i2[i])
				return 1;
		}
	}else if (a1->type == ARRAY_TYPE_PTR && a2->type == ARRAY_TYPE_PTR) {
		c1 = a1->data;
		c2 = a2->data;
		for (i=0; i<a1->length; i++) {
			if (c1[i] != c2[i])
				return 1;
		}
	}

	return 0;
}

/* destroy an array */
void array_free(array_t *a, int freestruct)
{
	int i;
	if (!a)
		return;
	if (a->type == ARRAY_TYPE_STRING) {
		char** p = a->data;
		for (i=0; i<a->length; i++ ) {
			if (p[i])
				free(p[i]);
		}
	}
	free(a->data);
	if (freestruct)
		free(a);
}

/* push an int onto an array */
int array_push_int(array_t *a, uint32_t v)
{
	uint32_t *p;
	if (a->type == ARRAY_TYPE_STRING) {
		char sv[20];
		sprintf(sv,"%u",v);
		return array_push_string(a,sv);
	}else if (a->type == ARRAY_TYPE_FLOAT) {
		return array_push_float(a,(float)v);
	}else if (a->type != ARRAY_TYPE_INT) {
		return 1;
	}

	a->length++;

	p = a->data;

	if (a->size < a->length) {
		p = realloc(a->data,sizeof(uint32_t)*a->length);
		if (!p) {
			a->length--;
			return 1;
		}
		a->size = a->length;
	}

	p[a->length-1] = v;
	a->data = p;

	return 0;
}

/* push a float onto an array */
int array_push_float(array_t *a, float v)
{
	float *p;
	if (a->type == ARRAY_TYPE_STRING) {
		char sv[20];
		sprintf(sv,"%f",v);
		return array_push_string(a,sv);
	}else if (a->type == ARRAY_TYPE_INT) {
		return array_push_int(a,(int)v);
	}else if (a->type != ARRAY_TYPE_FLOAT) {
		return 1;
	}

	a->length++;

	p = a->data;

	if (a->size < a->length) {
		p = realloc(a->data,sizeof(float)*a->length);
		if (!p) {
			a->length--;
			return 1;
		}
		a->size = a->length;
	}

	p[a->length-1] = v;
	a->data = p;

	return 0;
}

/* push a string onto an array */
int array_push_string(array_t *a, char* v)
{
	char** p;
	if (a->type != ARRAY_TYPE_STRING)
		return 1;

	a->length++;

	p = a->data;

	if (a->size < a->length) {
		p = realloc(a->data,sizeof(char*)*a->length);
		if (!p) {
			a->length--;
			return 1;
		}
		a->size = a->length;
	}

	if (v) {
		p[a->length-1] = strdup(v);
	}else{
		p[a->length-1] = NULL;
	}
	a->data = p;

	return 0;
}

/* push a pointer onto an array */
int array_push_ptr(array_t *a, void *v)
{
	char** p;
	if (a->type != ARRAY_TYPE_PTR)
		return 1;

	a->length++;

	p = a->data;

	if (a->size < a->length) {
		p = realloc(a->data,sizeof(char*)*a->length);
		if (!p)
			return 1;
		a->size = a->length;
	}

	p[a->length-1] = v;

	a->data = p;

	return 0;
}

/* push a colour onto an array */
int array_push_colour(array_t *a, colour_t *c)
{
	int r = 0;
	r += array_push_float(a,((float)c->r)/255.0);
	r += array_push_float(a,((float)c->g)/255.0);
	r += array_push_float(a,((float)c->b)/255.0);
	r += array_push_float(a,((float)c->a)/255.0);
	return r;
}

/* push a v3_t onto an array */
int array_push_v3t(array_t *a, v3_t *v)
{
	int r = 0;
	r += array_push_float(a,v->x);
	r += array_push_float(a,v->y);
	r += array_push_float(a,v->z);
	return r;
}

/* push a v2_t onto an array */
int array_push_v2t(array_t *a, v2_t *v)
{
	int r = 0;
	r += array_push_float(a,v->x);
	r += array_push_float(a,v->y);
	return r;
}

/* set the value of array index i to an int value */
int array_set_int(array_t *a, uint32_t v, int i)
{
	uint32_t *p = a->data;
	if (a->type == ARRAY_TYPE_STRING) {
		char sv[20];
		sprintf(sv,"%u",v);
		return array_set_string(a,sv,i);
	}else if (a->type == ARRAY_TYPE_FLOAT) {
		return array_set_float(a,(float)v,i);
	}else if (a->type != ARRAY_TYPE_INT) {
		return 1;
	}

	if (a->size <= i) {
		int l = i+1;

		p = realloc(a->data,sizeof(uint32_t)*l);
		if (!p)
			return 1;
		a->data = p;
		a->size = l;
	}

	if (a->length <= i)
		a->length = i+1;

	p[i] = v;

	return 0;
}

/* set the value of array index i to a float value */
int array_set_float(array_t *a, float v, int i)
{
	float *p = a->data;
	if (a->type == ARRAY_TYPE_STRING) {
		char sv[20];
		sprintf(sv,"%f",v);
		return array_set_string(a,sv,i);
	}else if (a->type == ARRAY_TYPE_INT) {
		return array_set_float(a,(uint32_t)v,i);
	}else if (a->type != ARRAY_TYPE_FLOAT) {
		return 1;
	}

	if (a->size <= i) {
		int l = i+1;

		p = realloc(a->data,sizeof(float)*l);
		if (!p)
			return 1;
		a->data = p;
		a->size = l;
	}
	if (a->length <= i)
		a->length = i+1;

	p[i] = v;

	return 0;
}

/* set the value of array index i to a string value */
int array_set_string(array_t *a, char* v, int i)
{
	char** p = a->data;
	if (a->type != ARRAY_TYPE_STRING)
		return 1;

	if (a->size <= i) {
		int l = i+1;

		p = realloc(a->data,sizeof(char*)*l);
		if (!p)
			return 1;
		a->data = p;
		a->size = l;
	}
	if (a->length <= i)
		a->length = i+1;

	if (v) {
		p[i] = strdup(v);
	}else{
		p[i] = NULL;
	}

	return 0;
}

/* set the value of array index i to a ponter value */
int array_set_ptr(array_t *a, void* v, int i)
{
	char** p = a->data;
	if (a->type != ARRAY_TYPE_PTR)
		return 1;

	if (a->size <= i) {
		int l = i+1;

		p = realloc(a->data,sizeof(char*)*l);
		if (!p)
			return 1;
		a->data = p;
		a->size = l;
	}
	if (a->length <= i)
		a->length = i+1;

	p[i] = v;

	return 0;
}

/* pop an int off the top of an array */
uint32_t array_pop_int(array_t *a)
{
	if (a->type == ARRAY_TYPE_INT) {
		if (!a->length)
			return 0;
		a->length--;
		return ((uint32_t*)(a->data))[a->length];
	}else if (a->type == ARRAY_TYPE_FLOAT) {
		float v = array_pop_float(a);
		return (uint32_t)v;
	}else if (a->type == ARRAY_TYPE_STRING) {
		uint32_t v;
		char* s = array_pop_string(a);
		v = strtol(s,NULL,10);
		free(s);
		return v;
	}
	return 0;
}

/* pop a float off the top of an array */
float array_pop_float(array_t *a)
{
	if (a->type == ARRAY_TYPE_FLOAT) {
		if (!a->length)
			return 0;
		a->length--;
		return ((float*)(a->data))[a->length];
	}else if (a->type == ARRAY_TYPE_INT) {
		uint32_t v = array_pop_int(a);
		return (float)v;
	}else if (a->type == ARRAY_TYPE_STRING) {
		float v;
		char* s = array_pop_string(a);
		v = strtof(s,NULL);
		free(s);
		return v;
	}
	return 0.0;
}

/* pop a string off the top of an array */
char* array_pop_string(array_t *a)
{
	if (a->type == ARRAY_TYPE_FLOAT) {
		float v = array_pop_float(a);
		char sv[20];
		if (!a->length)
			return NULL;
		sprintf(sv,"%f",v);
		return strdup(sv);
	}else if (a->type == ARRAY_TYPE_INT) {
		uint32_t v = array_pop_int(a);
		char sv[20];
		if (!a->length)
			return NULL;
		sprintf(sv,"%u",v);
		return strdup(sv);
	}else if (a->type == ARRAY_TYPE_STRING) {
		if (!a->length)
			return NULL;
		a->length--;
		return ((char**)(a->data))[a->length];
	}
	return NULL;
}

/* pop a pointer off the top of an array */
void *array_pop_ptr(array_t *a)
{
	if (a->type == ARRAY_TYPE_PTR && a->length) {
		a->length--;
		return ((char**)(a->data))[a->length];
	}
	return NULL;
}

/* get an int value from an array */
uint32_t array_get_int(array_t *a, int i)
{
	if (a->type == ARRAY_TYPE_INT) {
		if (a->length <= i)
			return 0;
		return ((uint32_t*)(a->data))[i];
	}else if (a->type == ARRAY_TYPE_FLOAT) {
		float v = array_get_float(a,i);
		return (uint32_t)v;
	}else if (a->type == ARRAY_TYPE_STRING) {
		uint32_t v;
		char* s = array_get_string(a,i);
		v = strtol(s,NULL,10);
		free(s);
		return v;
	}
	return 0;
}

/* get a float value from an array */
float array_get_float(array_t *a, int i)
{
	if (a->type == ARRAY_TYPE_FLOAT) {
		if (a->length <= i)
			return 0.0;
		return ((float*)(a->data))[i];
	}else if (a->type == ARRAY_TYPE_INT) {
		uint32_t v = array_get_int(a,i);
		return (float)v;
	}else if (a->type == ARRAY_TYPE_STRING) {
		float v;
		char* s = array_get_string(a,i);
		v = strtof(s,NULL);
		free(s);
		return v;
	}
	return 0.0;
}

/* get a string value from an array */
char* array_get_string(array_t *a, int i)
{
	if (a->type == ARRAY_TYPE_FLOAT) {
		float v = array_get_float(a,i);
		char sv[20];
		sprintf(sv,"%f",v);
		return strdup(sv);
	}else if (a->type == ARRAY_TYPE_INT) {
		uint32_t v = array_get_int(a,i);
		char sv[20];
		sprintf(sv,"%u",v);
		return strdup(sv);
	}else if (a->type == ARRAY_TYPE_STRING) {
		if (a->length <= i)
			return NULL;
		return ((char**)(a->data))[i];
	}
	return NULL;
}

/* get a pointer from an array */
void *array_get_ptr(array_t *a, int i)
{
	if (a->type == ARRAY_TYPE_PTR && a->length > i) {
		return ((char**)(a->data))[i];
	}
	return NULL;
}

/* find the index of an int value in an array */
int array_find_int(array_t *a, uint32_t v)
{
	if (a->type == ARRAY_TYPE_INT) {
		int i;
		uint32_t *p = a->data;
		for (i=0; i<a->length; i++) {
			if (p[i] == v)
				return i;
		}
	}
	return -1;
}

/* find the index of a float value in an array */
int array_find_float(array_t *a, float v)
{
	if (a->type == ARRAY_TYPE_FLOAT) {
		int i;
		float *p = a->data;
		for (i=0; i<a->length; i++) {
			if (p[i] == v)
				return i;
		}
	}
	return -1;
}

/* find the index of a string value in an array */
int array_find_string(array_t *a, char* v)
{
	if (a->type == ARRAY_TYPE_STRING) {
		int i;
		char** p = a->data;
		for (i=0; i<a->length; i++) {
			if (!strcmp(p[i],v))
				return i;
		}
	}
	return -1;
}

/* find the index of a pointer in an array */
int array_find_ptr(array_t *a, void *v)
{
	if (a->type == ARRAY_TYPE_PTR) {
		int i;
		unsigned char* cv = v;
		unsigned char** p = a->data;
		for (i=0; i<a->length; i++) {
			if (p[i] == cv)
				return i;
		}
	}
	return -1;
}

/* remove a string value from an array */
int array_remove_string(array_t *a, char* v)
{
	if (a->type == ARRAY_TYPE_STRING) {
		int i;
		char** p = a->data;
		for (i=0; i<a->length; i++) {
			if (!strcmp(p[i],v)) {
				free(p[i]);
				break;
			}
		}
		a->length--;
		for (; i<a->length; i++) {
			p[i] = p[i+1];
		}
		return 0;
	}
	return 1;
}

/* split a string into an array, at a separator character */
array_t *array_split(char* str, char* s)
{
	char buff[1024];
	int i;
	int o = 0;
	int l;
	array_t *r = array_create(ARRAY_TYPE_STRING);

	if (str) {
		l = strlen(s);

		for (i=0; str[i]; i++) {
			if (!strncmp(str+i,s,l)) {
				buff[o] = 0;
				array_push_string(r,buff);
				o = 0;
				i += l-1;
				continue;
			}
			buff[o++] = str[i];
		}
		buff[o] = 0;
		array_push_string(r,buff);
	}

	return r;
}

/* join an array into a string, using a glue character */
char* array_join(array_t *a, char* glue, int start)
{
	char* str = NULL;
	int l = 0;
	if (!glue)
		glue = " ";
	if (a->type == ARRAY_TYPE_STRING) {
		int i;
		int gl = strlen(glue);
		for (i=start; i<a->length; i++) {
			l += strlen(((char**)(a->data))[i])+gl;
		}
		if (l) {
			str = malloc(l+1);
			str[0] = 0;
			for (i=start; i<a->length; i++) {
				if (i > start)
					strcat(str,glue);
				strcat(str,((char**)(a->data))[i]);
			}
		}
	}

	return str;
}
