#include "l4array.h"

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

LbsArray* lbs_array_new_with_max (size_t size, size_t max) {
	if (size <= 0) {
		return NULL;
	}

	LbsArray* array = malloc (sizeof (LbsArray));
	if (array == NULL) {
		return NULL;
	}

	if (max > 0) {
		void* data = malloc (size * max);
		if (data == NULL) {
			free (array);
			return NULL;
		}
		array->data = data;
	} else {
		array->data = NULL;
	}

	array->free_func = NULL;
	array->size = size;
	array->len = max;
	array->max = max;
	array->ref_count = 1;
	array->is_alloc = true;

	return array;
}

int lbs_array_init_with_max (LbsArray* array, size_t size, size_t max) {
	if (size <= 0) {
		return -1;
	}

	if (max > 0) {
		void* data = malloc (size * max);
		if (data == NULL) {
			return -1;
		}
		array->data = data;
	} else {
		array->data = NULL;
	}

	array->free_func = NULL;
	array->size = size;
	array->len = max;
	array->max = max;
	array->ref_count = 1;
	array->is_alloc = false;

	return 0;
}

LbsArray* lbs_array_copy (LbsArray* dest, const LbsArray* src) {
	if (dest == NULL) {
		dest = lbs_array_new_with_max (src->size, src->max);
		if (dest == NULL) {
			return NULL;
		}
	} else {
		if (lbs_array_init_with_max (dest, src->size, src->max) < 0) {
			return NULL;
		}
	}

	dest->len = src->len;
	dest->free_func = src->free_func;
	memcpy (dest->data, src->data, src->size * src->len);
	return dest;
}

LbsArray* lbs_array_cat (LbsArray* dest, const LbsArray* more) {
	if (dest == NULL) {
		return lbs_array_copy (dest, more);
	}

	if (dest->size != more->size) {
		return NULL;
	}

	int oldlen = dest->len;
	if (lbs_array_set_len (dest, dest->len + more->len) < 0) {
		return NULL;
	}

	memcpy (lbs_array_vp (dest, oldlen), more->data, more->size * more->len);
	return dest;
}

void* lbs_array_ref_generic (void* array_generic) {
	LbsArray* array = LBS_ARRAY (array_generic);
	int oldref = array->ref_count;
	int newref = oldref + 1;
	if (newref <= oldref) {
		return NULL;
	}

	array->ref_count = newref;
	return array;
}

void lbs_array_unref_generic (void* array_generic) {
	LbsArray* array = LBS_ARRAY (array_generic);
	array->ref_count--;
	if (array->ref_count <= 0) {
		lbs_array_free (array);
	}
}

void lbs_array_free_generic (void* array_generic) {
	LbsArray* array = LBS_ARRAY (array_generic);
	if (array->free_func != NULL) {
		int i = 0;
		char* d = array->data;
		for (; i < array->len; i++, d += array->size) {
			(*(array->free_func)) (*((void**)d));
		}
	}
	free (array->data);
	if (array->is_alloc) {
		free (array);
	}
}

void* lbs_array_drop_struct (LbsArray* array) {
	if (!array->is_alloc) {
		return array->data;
	}

	void* data = array->data;
	free (array);
	return data;
}

LbsArray* lbs_array_make_struct (LbsArray* array,
	size_t size, size_t len, size_t max, void* data) {

	if (array == NULL) {
		array = lbs_array_new (size);
		if (array == NULL) {
			return NULL;
		}
	} else {
		if (lbs_array_init (array, size) < 0) {
			return NULL;
		}
	}

	array->len = len;
	array->max = max;
	array->data = data;
	return array;
}

int lbs_array_set_len (LbsArray* array, size_t len) {
	if (len > (array->max)){
		if (lbs_array_set_max (array, len) < 0) {
			return -1;
		} else {
			array->len = len;
		}
	} else {
		array->len = len;
		return 0;
	}
	return 0;
}

int lbs_array_set_max (LbsArray* array, size_t max) {
	void* ptr = realloc (array->data, array->size * max);
	if (ptr == NULL) {
		return -1;
	}

	array->max = max;
	array->data = ptr;
	return 0;
}

int lbs_array_append_ptr (LbsArray* array, const void* ptr) {
	return lbs_array_append_data (array, &ptr);
}

int lbs_array_append_data (LbsArray* array, const void* data) {
	if (array->max < array->len + 1) {
		if (array->max > 0){
			if (lbs_array_set_max (array, array->max * 2) < 0) {
				return -1;
			}
		} else {
			if (lbs_array_set_max (array, 1) < 0){
				return -1;
			}
		}
	}

	memcpy (lbs_array_vp (array, array->len), data, array->size);
	array->len++;
	return 0;
}

int lbs_array_remove (LbsArray* array) {
	if (array->len <= 0) {
		return -1;
	}

	array->len--;
	if (array->len < array->max * 2) {
		lbs_array_minimize (array);
	}
	return 0;
}

int lbs_array_minimize (LbsArray* array) {
	if (array->max > array->len) {
		void* ptr = realloc (array->data, array->size * array->len);
		if (ptr == NULL) {
			return -1;
		}
		array->max = array->len;
		array->data = ptr;
	}
	return 0;
}