#if ! defined(PROG_DISABLE_EXTENDED)

#include "declarations.h"
#include "dsl_errno.h"
#include "dsl_helper.h"
#include "dsl_shr.h"
#include "dsl_array.h"



struct dsl_var *dsl_array_get_item(struct dsl_var *v, ssize_t d, int n, struct dsl_ctx *c) {
	if(v->array->p != NULL) {
		/* This is range of something... */
		return(dsl_array_get_item_num(v, d, n));
	}
	else if(v->array->v.v != NULL) {
		/* ...and this is array of variables */
		return(dsl_array_get_item_var(v, d, n, c));
	}

	return(NULL);
}

static struct dsl_var *dsl_array_get_item_num(struct dsl_var *v, ssize_t d, int n) {
	size_t u;

	/* Handle negative offset */
	if(d < 0) {
		u = v->array->c + d;
	}
	else {
		u = (size_t) d;
	}

	if(u >= v->array->c) {
		/* Item is off limits, extend the array if requested to do
		   so */
		if(n != IS_NO) {
			if(dsl_array_get_item_num_op(v->array, u + 1) != 0) {
				return(NULL);
			}
		}
		else {
			(void) dsl_error_set(ERROR_FATAL,
				DSL_ERROR_ARRAY_OFF_LIMITS);

			return(NULL);
		}
	}

	/* Return value borrowing array variable */
	switch(v->array->type) {
		case DSL_PARAM_TYPE_NUMBER:
			switch(v->array->subt) {
				case DSL_NUMBER_TYPE_DOUBLE:
					/* Falls through */
				case DSL_NUMBER_TYPE_DEFAULT:
					v->number->number.n =
						((double *) v->array->p)[u];

					break;
				default:
					(void) dsl_error_set(ERROR_FATAL,
						DSL_ERROR_PARAM_TYPE_INCOMPATIBLE);

					return(NULL);
			}

			(void) dsl_num_set_type(v->number, v->array->subt);

			break;
		default:
			(void) dsl_error_set(ERROR_FATAL,
				DSL_ERROR_PARAM_TYPE_INCOMPATIBLE);

			return(NULL);
	}

	return(v);
}

static int dsl_array_get_item_num_op(struct dsl_arr *a, size_t c) {
	size_t i;

	double *t;

	if((t = realloc(a->p, sizeof(double) * c)) == NULL) {
		(void) dsl_error_set(ERROR_FATAL,
			DSL_ERROR_GENERAL_ALLOC);

		return(-1);
	}

	for(i = a->c; i < c; i++) {
		t[i] = 0.0;
	}

	a->p = (void *) t;
	a->c = c;

	return(0);
}

static struct dsl_var *dsl_array_get_item_var(struct dsl_var *v, ssize_t d, int n, struct dsl_ctx *c) {
	size_t u;

	/* Handle negative offset */
	if(d < 0) {
		u = v->array->v.c + d;
	}
	else {
		u = (size_t) d;
	}

	if(u >= v->array->v.t) {
		/* Item is off limits, extend the array if requested to do
		   so */
		if(n != IS_NO) {
			if(dsl_array_get_item_var_op(v->array, u + 1) != 0) {
				return(NULL);
			}
		}
		else {
			(void) dsl_error_set(ERROR_FATAL,
				DSL_ERROR_ARRAY_OFF_LIMITS);

			return(NULL);
		}
	}

	if(v->array->v.v[u] == NULL) {
		/* Create variable in this slot if needed */
		if((v->array->v.v[u] = dsl_array_put_items_at(v->array,
			u, c)) == NULL) {

			return(NULL);
		}

		if(u + 1 > v->array->v.c) {
			/* Extend variables allocated counter to this var */
			v->array->v.c = u + 1;
		}
	}

	/* Store item value also in array variable */
	switch(v->array->type) {
		case DSL_PARAM_TYPE_NUMBER:
			switch(v->array->subt) {
				case DSL_NUMBER_TYPE_DOUBLE:
					/* Falls through */
				case DSL_NUMBER_TYPE_DEFAULT:
					v->number->number.n =
						v->array->v.v[u]->number->number.n;

					break;
				default:
					(void) dsl_error_set(ERROR_FATAL,
						DSL_ERROR_PARAM_TYPE_INCOMPATIBLE);

					return(NULL);
			}

			(void) dsl_num_set_type(v->number, v->array->subt);

			break;
		default:
			(void) dsl_error_set(ERROR_FATAL,
				DSL_ERROR_PARAM_TYPE_INCOMPATIBLE);

			return(NULL);
	}

	return(v->array->v.v[u]);
}

static int dsl_array_get_item_var_op(struct dsl_arr *a, size_t c) {
	unsigned int s;

	size_t i, u;

	struct dsl_var **t;

	s = dsl_arr_get_bucket_size();
	u = c + s;

	if((t = realloc(a->v.v, sizeof(struct dsl_var *) * u)) == NULL) {
		(void) dsl_error_set(ERROR_FATAL,
			DSL_ERROR_GENERAL_ALLOC);

		return(-1);
	}

	for(i = a->v.t; i < a->v.t + (c + s); i++) {
		t[i] = NULL;
	}

	a->v.v = t;
	a->v.t = u;

	return(0);
}

int dsl_array_put_items_num(struct dsl_var *d, struct dsl_cmd_range_n *s, size_t n) {
	size_t i, j, t;

	double k, v;
	double *a;

	struct position e, f;

	if(d->array->p != NULL) {
		(void) free(d->array->p);
	}

	/* Figure out needed array size... */
	t = dsl_array_put_items_op(s, n);

	/* ...allocate it... */
	if((d->array->p = malloc(t * sizeof(double))) == NULL) {
		(void) dsl_error_set(ERROR_FATAL,
			DSL_ERROR_GENERAL_ALLOC);

		return(-1);
	}

	/* ...and fill it with numbers */
	a = (double *) d->array->p;

	for(i = 0, j = 0; i < n; i++) {
		switch(s[i].c) {
			case DSL_RANGE_TYPE_LINEAR:
				if(s[i].b - s[i].a > 0.0) {
					e.x = s[i].a;
					f.x = s[i].b;

					for(k = s[i].a, v = 0.0; k < s[i].b;
						k += s[i].s) {

						a[j++] = coords_intp_linear_x(&e,
							&f, v);

						v += 1.0 / ((s[i].b - s[i].a) /
							s[i].s);
					}
				}
				else if(s[i].b - s[i].a < 0.0) {
					e.x = s[i].a;
					f.x = s[i].b;

					for(k = s[i].a, v = 0.0; k > s[i].b;
						k -= s[i].s) {

						a[j++] = coords_intp_linear_x(&e,
							&f, v);

						v -= 1.0 / ((s[i].b - s[i].a) /
							s[i].s);
					}
				}

				a[j++] = s[i].b;

				break;
			default:
				a[j++] = s[i].a;

				break;
		}
	}

	d->array->c = j;

	d->array->type = DSL_PARAM_TYPE_NUMBER;
	d->array->subt = DSL_NUMBER_TYPE_DEFAULT;

	d->c = DSL_PARAM_TYPE_ARRAY;
	d->m = DSL_MEMBER_TYPE_NUM_ARRAY;

	return(0);
}

int dsl_array_put_items_var(struct dsl_var *d, struct dsl_cmd_range_n *s, size_t n, struct dsl_ctx *c) {
	size_t i, j, t;

	double k, v;

	struct dsl_num b;
	struct dsl_var **a;

	struct position e, f;

	if(d->array->v.v != NULL) {
		(void) free(d->array->v.v);
	}

	/* Figure out needed array size... */
	t = dsl_array_put_items_op(s, n);

	/* ...allocate it... */
	if((d->array->v.v = malloc(t * sizeof(struct dsl_var *))) == NULL) {
		(void) dsl_error_set(ERROR_FATAL,
			DSL_ERROR_GENERAL_ALLOC);

		return(-1);
	}

	d->array->v.t = t;

	/* ...and fill it with variables */
	(void) memset((void *) &b, 0, sizeof(b));

	(void) dsl_str_set_string(&b.rpn.r, NULL);

	(void) dsl_var_init_default_cs(&b.rpn.r,
		STRING_UTF8,
		CHARSET_DEFAULT,
		CHARSET_DEFAULT_SIZE, 0);

	(void) dsl_num_set_type(&b, DSL_NUMBER_TYPE_DEFAULT);

	a = (struct dsl_var **) d->array->v.v;

	for(i = 0, j = 0; i < n; i++) {
		switch(s[i].c) {
			case DSL_RANGE_TYPE_LINEAR:
				if(s[i].b - s[i].a > 0.0) {
					e.x = s[i].a;
					f.x = s[i].b;

					/* Linear interpolation */
					for(k = s[i].a, v = 0.0; k < s[i].b;
						k += s[i].s) {

						if((a[j] = dsl_array_put_items_at(d->array,
							j, c)) == NULL) {

							return(-1);
						}

						b.number.n = coords_intp_linear_x(&e,
							&f, v);

						v += 1.0 / ((s[i].b - s[i].a) /
							s[i].s);

						(void) dsl_var_put_number(a[j++],
							&b, c, IS_YES);
					}
				}
				else if(s[i].b - s[i].a < 0.0) {
					e.x = s[i].a;
					f.x = s[i].b;

					/* Linear interpolation */
					for(k = s[i].a, v = 0.0; k > s[i].b;
						k -= s[i].s) {

						if((a[j] = dsl_array_put_items_at(d->array,
							j, c)) == NULL) {

							return(-1);
						}

						b.number.n = coords_intp_linear_x(&e,
							&f, v);

						v -= 1.0 / ((s[i].b - s[i].a) /
							s[i].s);

						(void) dsl_var_put_number(a[j++],
							&b, c, IS_YES);
					}
				}

				if((a[j] = dsl_array_put_items_at(d->array,
					j, c)) == NULL) {

					return(-1);
				}

				b.number.n = s[i].b;

				(void) dsl_var_put_number(a[j++], &b, c,
					IS_YES);

				break;
			default:
				if((a[j] = dsl_array_put_items_at(d->array,
					j, c)) == NULL) {

					return(-1);
				}

				b.number.n = s[i].a;

				(void) dsl_var_put_number(a[j++], &b, c,
					IS_YES);

				break;
		}
	}

	d->array->v.c = j;

	d->array->type = DSL_PARAM_TYPE_NUMBER;
	d->array->subt = DSL_NUMBER_TYPE_DEFAULT;

	d->c = DSL_PARAM_TYPE_ARRAY;
	d->m = DSL_MEMBER_TYPE_VAR_ARRAY;

	return(0);
}

static size_t dsl_array_put_items_op(struct dsl_cmd_range_n *s, size_t n) {
	size_t i, t;

	double k;

	/* Get the size of needed array */
	for(i = 0, t = 0; i < n; i++) {
		switch(s[i].c) {
			case DSL_RANGE_TYPE_LINEAR:
				if(s[i].b - s[i].a > 0.0) {
					for(k = s[i].a; k < s[i].b;
						k += s[i].s) {

						t++;
					}
				}
				else if(s[i].b - s[i].a < 0.0) {
					for(k = s[i].a; k > s[i].b;
						k -= s[i].s) {

						t++;
					}
				}

				break;

			default:
				break;
		}

		t++;
	}

	return(t);
}

static struct dsl_var *dsl_array_put_items_at(struct dsl_arr *a, size_t n, struct dsl_ctx *c) {
	size_t k;

	struct dsl_var *r;

	struct dsl_var b;
	struct dsl_vars t;

	(void) dsl_str_set_string(&b.variable, b.variable.s_embed);

	/* REMEMBER item name must fit in s_embed */
	k = (size_t) snprintf(dsl_str_get_string(&b.variable),
		D_STRING_EMBEDDED_SIZE, "%zu", n);

	(void) dsl_str_set_length(&b.variable, k);
	(void) dsl_str_set_allocated(&b.variable, 0);

	(void) dsl_var_init_default_cs(&b.variable,
		STRING_UTF8,
		CHARSET_DEFAULT,
		CHARSET_DEFAULT_SIZE, 0);

	b.c = DSL_PARAM_TYPE_NONE;

	b.variable.s_id = 0;

	/* Use temporary vars for creating new item so that dsl_var_new() does
	   not mess with actual array */
	t.c = 0;
	t.t = 0;
	t.v = NULL;

	if(dsl_var_new(&b, &t, c) != 0) {
		return(NULL);
	}

	/* No return value check needed as var is guaranteed to exist */
	r = dsl_var_get_struct_by_name(&b.variable, &t);

	(void) free(t.v);

	/* Variable type is same as array type */
	switch(a->type) {
		case DSL_PARAM_TYPE_NUMBER:
			(void) dsl_num_set_type(r->number, a->subt);

			break;
		default:
			break;
	}

	r->c = a->type;

	return(r);
}

void dsl_array_free(struct dsl_arr *a) {
	size_t i;

	if(a->p != NULL) {
		(void) free(a->p);
	}

	if(a->v.v != NULL) {
		for(i = 0; i < a->v.c; i++) {
			if(a->v.v[i] != NULL) {
				(void) dsl_var_free(a->v.v[i]);
			}
		}

		(void) free(a->v.v);

		a->v.c = 0;
		a->v.t = 0;
		a->v.v = NULL;
	}

	a->type = DSL_PARAM_TYPE_NONE;
	a->subt = 0;

	a->c = 0;
	a->p = NULL;
}

static unsigned int dsl_arr_get_bucket_size(void) {
	return(a_size);
}

void dsl_arr_set_bucket_size(unsigned int n) {
	a_size = n;
}
#endif
