/* array.c */
/*
$MawkId: array.c,v 1.32 2026/01/17 00:17:30 tom Exp $

copyright 2009-2024,2026 Thomas E. Dickey
copyright 1991-1996,2014 Michael D. Brennan

This is a source file for mawk, an implementation of
the AWK programming language.

Mawk is distributed without warranty under the terms of
the GNU General Public License, version 2, 1991.
-------------------------------------------------------------------------------
array.c and array.h were originally generated with the commands

   notangle -R'"array.c"' array.w > array.c
   notangle -R'"array.h"' array.w > array.h

Notangle is part of Norman Ramsey's noweb literate programming package
available from CTAN(ftp.shsu.edu).
*/

#define Visible_ARRAY
#define Visible_CELL
#define Visible_STRING

#include <mawk.h>
#include <symtype.h>
#include <memory.h>
#include <split.h>
#include <field.h>
#include <bi_vars.h>

struct anode;
typedef struct {
    struct anode *slink, *ilink;
} DUAL_LINK;

typedef struct anode {
    struct anode *slink;
    struct anode *ilink;
    STRING *sval;
    unsigned hval;
    Int ival;
    CELL cell;
} ANODE;

static ANODE *find_by_ival(ARRAY, Int, int, int *);

static ANODE *find_by_sval(ARRAY, STRING *, int, int *);

#define NOT_AN_IVALUE (-Max_Int-1)	/* usually 0x80000000 */
static void add_string_associations(ARRAY);

#define STARTING_HMASK    63	/* 2^6-1, must have form 2^n-1 */
#define MAX_AVE_LIST_LENGTH   12
#define hmask_to_limit(x) (((x)+1)*MAX_AVE_LIST_LENGTH)
#define ahash(sval) hash2((sval)->str, (sval)->len)
static void make_empty_table(ARRAY, int);

static void convert_split_array_to_table(ARRAY);

static void double_the_hash_table(ARRAY);

CELL *
array_find(
	      ARRAY A,
	      CELL *cp,
	      int create_flag)
{
    ANODE *ap;
    int redid;
    if (A->size == 0 && !create_flag)
	/* eliminating this trivial case early avoids unnecessary conversions later */
	return (CELL *) 0;
    switch (cp->type) {
    case C_DOUBLE:
	{
	    double d = cp->dval;
	    Int ival = d_to_I(d);
	    if ((double) ival == d) {
		if (A->type == AY_SPLIT) {
		    if (ival >= 1 && ival <= (int) A->size)
			return (CELL *) A->ptr + (ival - 1);
		    if (!create_flag)
			return (CELL *) 0;
		    convert_split_array_to_table(A);
		} else if (A->type == AY_NULL)
		    make_empty_table(A, AY_INT);
		ap = find_by_ival(A, ival, create_flag, &redid);
	    } else {
		/* convert to string */
		char buff[260];
		STRING *sval;
		sprintf(buff, string(CONVFMT)->str, d);
		sval = new_STRING(buff);
		ap = find_by_sval(A, sval, create_flag, &redid);
		free_STRING(sval);
	    }
	}

	break;
    case C_NOINIT:
	ap = find_by_sval(A, &null_str, create_flag, &redid);
	break;
    default:
	ap = find_by_sval(A, string(cp), create_flag, &redid);
	break;
    }
    return ap ? &ap->cell : (CELL *) 0;
}

void
array_delete(
		ARRAY A,
		CELL *cp)
{
    ANODE *ap;
    int redid;
    if (A->size == 0)
	return;
    switch (cp->type) {
    case C_DOUBLE:
	{
	    double d = cp->dval;
	    Int ival = d_to_I(d);
	    if ((double) ival == d) {
		if (A->type == AY_SPLIT) {
		    if (ival >= 1 && ival <= (int) A->size)
			convert_split_array_to_table(A);
		    else
			return;	/* ival not in range */
		}
		ap = find_by_ival(A, ival, NO_CREATE, &redid);
		if (ap) {	/* remove from the front of the ilist */
		    DUAL_LINK *table = (DUAL_LINK *) A->ptr;
		    table[(unsigned) ap->ival & A->hmask].ilink = ap->ilink;
		    if (ap->sval) {
			ANODE *p, *q = NULL;
			unsigned indx = (unsigned) ap->hval & A->hmask;
			p = table[indx].slink;
			while (p != ap) {
			    q = p;
			    p = q->slink;
			}
			if (q)
			    q->slink = p->slink;
			else
			    table[indx].slink = p->slink;
			free_STRING(ap->sval);
		    }

		    cell_destroy(&ap->cell);
		    ZFREE(ap);
		    if (--A->size == 0)
			array_clear(A);

		}
		return;
	    }

	    else {		/* get the string value */
		char buff[260];
		STRING *sval;
		sprintf(buff, string(CONVFMT)->str, d);
		sval = new_STRING(buff);
		ap = find_by_sval(A, sval, NO_CREATE, &redid);
		free_STRING(sval);
	    }
	}
	break;
    case C_NOINIT:
	ap = find_by_sval(A, &null_str, NO_CREATE, &redid);
	break;
    default:
	ap = find_by_sval(A, string(cp), NO_CREATE, &redid);
	break;
    }
    if (ap) {			/* remove from the front of the slist */
	DUAL_LINK *table = (DUAL_LINK *) A->ptr;
	table[ap->hval & A->hmask].slink = ap->slink;
	if (ap->ival != NOT_AN_IVALUE) {
	    ANODE *p, *q = NULL;
	    unsigned indx = (unsigned) ap->ival & A->hmask;
	    p = table[indx].ilink;
	    while (p != ap) {
		q = p;
		p = q->ilink;
	    }
	    if (q)
		q->ilink = p->ilink;
	    else
		table[indx].ilink = p->ilink;
	}

	free_STRING(ap->sval);
	cell_destroy(&ap->cell);
	ZFREE(ap);
	if (--A->size == 0)
	    array_clear(A);

    }
}

void
array_load(
	      ARRAY A,
	      size_t cnt)
{
    if (A->type != AY_SPLIT || A->limit < cnt) {
	array_clear(A);
	A->limit = (cnt & (size_t) ~3) + 4;
	A->ptr = zmalloc(SizeTimes(A->limit, sizeof(CELL)));
	A->type = AY_SPLIT;
    } else {
	/* reusing an existing AY_SPLIT array */
	size_t i;
	for (i = 0; i < A->size; i++) {
	    cell_destroy((CELL *) A->ptr + i);
	}
    }

    A->size = cnt;
    transfer_to_array((CELL *) A->ptr, cnt);
}

void
array_clear(ARRAY A)
{
    unsigned i;
    ANODE *p, *q;
    if (A->type == AY_SPLIT) {
	for (i = 0; i < A->size; i++)
	    cell_destroy((CELL *) A->ptr + i);
	zfree(A->ptr, A->limit * sizeof(CELL));
    } else if (A->type & AY_STR) {
	DUAL_LINK *table = (DUAL_LINK *) A->ptr;
	for (i = 0; (unsigned) i <= A->hmask; i++) {
	    p = table[i].slink;
	    while (p) {
		q = p;
		p = q->slink;
		free_STRING(q->sval);
		cell_destroy(&q->cell);
		ZFREE(q);
	    }
	}
	zfree(A->ptr, (A->hmask + 1) * sizeof(DUAL_LINK));
    } else if (A->type & AY_INT) {
	DUAL_LINK *table = (DUAL_LINK *) A->ptr;
	for (i = 0; (unsigned) i <= A->hmask; i++) {
	    p = table[i].ilink;
	    while (p) {
		q = p;
		p = q->ilink;
		cell_destroy(&q->cell);
		ZFREE(q);
	    }
	}
	zfree(A->ptr, (A->hmask + 1) * sizeof(DUAL_LINK));
    }
    memset(A, 0, sizeof(*A));
}

static int
string_compare(
		  const void *l,
		  const void *r)
{
    STRING *const *a = (STRING * const *) l;
    STRING *const *b = (STRING * const *) r;

    return strcmp((*a)->str, (*b)->str);
}

STRING **
array_loop_vector(
		     ARRAY A,
		     size_t *sizep)
{
    *sizep = A->size;
    if (A->size > 0) {
	STRING **ret;
	if (!(A->type & AY_STR))
	    add_string_associations(A);
	ret = (STRING **) zmalloc(SizeTimes(A->size, sizeof(STRING *)));
	{
	    int r = 0;		/* indexes ret */
	    DUAL_LINK *table = (DUAL_LINK *) A->ptr;
	    int i;		/* indexes table */
	    ANODE *p;		/* walks slists */
	    for (i = 0; (unsigned) i <= A->hmask; i++) {
		for (p = table[i].slink; p; p = p->slink) {
		    ret[r++] = p->sval;
		    p->sval->ref_cnt++;
		}
	    }
	}

	if (getenv("WHINY_USERS") != NULL)	/* gawk compatibility */
	    qsort(ret, A->size, sizeof(STRING *), string_compare);
	return ret;
    }
    return (STRING **) 0;
}

CELL *
array_cat(
	     CELL *sp,
	     int cnt)
{
    CELL *p;			/* walks the eval stack */
    CELL subsep;		/* local copy of SUBSEP */
    size_t subsep_len;		/* string length of subsep_str */
    char *subsep_str;

    size_t total_len;		/* length of cat'ed expression */
    CELL *top;			/* value of sp at entry */
    char *target;		/* build cat'ed char* here */
    STRING *sval;		/* build cat'ed STRING here */
    cellcpy(&subsep, SUBSEP);
    if (subsep.type < C_STRING)
	cast1_to_s(&subsep);
    subsep_len = string(&subsep)->len;
    subsep_str = string(&subsep)->str;

    assert(cnt > 0);

    top = sp;
    sp -= (cnt - 1);

    total_len = ((size_t) (cnt - 1)) * subsep_len;
    for (p = sp; p <= top; p++) {
	if (p->type < C_STRING)
	    cast1_to_s(p);
	total_len += string(p)->len;
    }

    sval = new_STRING0(total_len);
    target = sval->str;
    for (p = sp; p < top; p++) {
	memcpy(target, string(p)->str, string(p)->len);
	target += string(p)->len;
	memcpy(target, subsep_str, subsep_len);
	target += subsep_len;
    }
    /* now p == top */
    memcpy(target, string(p)->str, string(p)->len);

    for (p = sp; p <= top; p++)
	free_STRING(string(p));
    free_STRING(string(&subsep));
    /* set contents of sp , sp->type > C_STRING is possible so reset */
    sp->type = C_STRING;
    sp->ptr = (PTR) sval;
    return sp;

}

static ANODE *
find_by_ival(
		ARRAY A,
		Int ival,
		int create_flag,
		int *redo)
{
    DUAL_LINK *table = (DUAL_LINK *) A->ptr;
    unsigned indx = (unsigned) ival & A->hmask;
    ANODE *p = table[indx].ilink;	/* walks ilist */
    ANODE *q = (ANODE *) 0;	/* trails p */
    while (1) {
	if (!p) {
	    /* search failed */
	    if (A->type & AY_STR) {
		/* need to search by string */
		char buff[256];
		STRING *sval;
		sprintf(buff, LONG_FMT, (Long) ival);
		sval = new_STRING(buff);
		p = find_by_sval(A, sval, create_flag, redo);
		if (*redo) {
		    table = (DUAL_LINK *) A->ptr;
		}
		free_STRING(sval);
		if (!p)
		    return (ANODE *) 0;
	    } else if (create_flag) {
		p = ZMALLOC(ANODE);
		p->sval = (STRING *) 0;
		p->cell.type = C_NOINIT;
		if (++A->size > A->limit) {
		    double_the_hash_table(A);	/* changes table, may change index */
		    table = (DUAL_LINK *) A->ptr;
		    indx = A->hmask & (unsigned) ival;
		}
	    } else
		return (ANODE *) 0;
	    p->ival = ival;
	    A->type |= AY_INT;

	    break;
	} else if (p->ival == ival) {
	    /* found it, now move to the front */
	    if (!q)		/* already at the front */
		return p;
	    /* delete for insertion at the front */
	    q->ilink = p->ilink;
	    break;
	}
	q = p;
	p = q->ilink;
    }
    /* insert at the front */
    p->ilink = table[indx].ilink;
    table[indx].ilink = p;
    return p;
}

static ANODE *
find_by_sval(
		ARRAY A,
		STRING * sval,
		int create_flag,
		int *redo)
{
    unsigned hval = ahash(sval);
    char *str = sval->str;
    DUAL_LINK *table;
    unsigned indx;
    ANODE *p;			/* walks list */
    ANODE *q = (ANODE *) 0;	/* trails p */
    if (!(A->type & AY_STR))
	add_string_associations(A);
    table = (DUAL_LINK *) A->ptr;
    indx = hval & A->hmask;
    p = table[indx].slink;
    *redo = 0;
    while (1) {
	if (!p) {
	    if (create_flag) {
		{
		    p = ZMALLOC(ANODE);
		    p->sval = sval;
		    sval->ref_cnt++;
		    p->ival = NOT_AN_IVALUE;
		    p->hval = hval;
		    p->cell.type = C_NOINIT;
		    if (++A->size > A->limit) {
			double_the_hash_table(A);	/* changes table, may change index */
			table = (DUAL_LINK *) A->ptr;
			indx = hval & A->hmask;
			*redo = 1;
		    }
		}

		break;
	    }
	    return (ANODE *) 0;
	} else if (p->hval == hval) {
	    if (strcmp(p->sval->str, str) == 0) {
		/* found */
		if (!q)		/* already at the front */
		    return p;
		else {		/* delete for move to the front */
		    q->slink = p->slink;
		    break;
		}
	    }
	}
	q = p;
	p = q->slink;
    }
    p->slink = table[indx].slink;
    table[indx].slink = p;
    return p;
}

static void
add_string_associations(ARRAY A)
{
    if (A->type == AY_NULL)
	make_empty_table(A, AY_STR);
    else {
	DUAL_LINK *table;
	int i;			/* walks table */
	ANODE *p;		/* walks ilist */
	char buff[256];
	if (A->type == AY_SPLIT)
	    convert_split_array_to_table(A);
	table = (DUAL_LINK *) A->ptr;
	for (i = 0; (unsigned) i <= A->hmask; i++) {
	    p = table[i].ilink;
	    while (p) {
		sprintf(buff, LONG_FMT, (Long) p->ival);
		p->sval = new_STRING(buff);
		p->hval = ahash(p->sval);
		p->slink = table[A->hmask & p->hval].slink;
		table[A->hmask & p->hval].slink = p;
		p = p->ilink;
	    }
	}
	A->type |= AY_STR;
    }
}

static void
make_empty_table(
		    ARRAY A,
		    int type)	/* AY_INT or AY_STR */
{
    size_t sz = (STARTING_HMASK + 1) * sizeof(DUAL_LINK);
    A->type = (short) type;
    A->hmask = STARTING_HMASK;
    A->limit = hmask_to_limit(STARTING_HMASK);
    A->ptr = memset(zmalloc(sz), 0, sz);
}

static void
convert_split_array_to_table(ARRAY A)
{
    CELL *cells = (CELL *) A->ptr;
    unsigned i;			/* walks cells */
    DUAL_LINK *table;
    unsigned j;			/* walks table */
    size_t entry_limit = A->limit;
    A->hmask = STARTING_HMASK;
    A->limit = hmask_to_limit(STARTING_HMASK);
    while (A->size > A->limit) {
	A->hmask = (A->hmask << 1) + 1;		/* double the size */
	A->limit = hmask_to_limit(A->hmask);
    }
    {
	size_t sz = (A->hmask + 1) * sizeof(DUAL_LINK);
	A->ptr = memset(zmalloc(sz), 0, sz);
	table = (DUAL_LINK *) A->ptr;
    }

    /* insert each cells[i] in the new hash table on an ilist */
    for (i = 0, j = 1; i < A->size; i++) {
	ANODE *p = ZMALLOC(ANODE);
	p->sval = (STRING *) 0;
	p->ival = (Int) (i + 1);
	p->cell = cells[i];
	p->ilink = table[j].ilink;
	table[j].ilink = p;
	j++;
	j &= A->hmask;
    }
    A->type = AY_INT;
    zfree(cells, entry_limit * sizeof(CELL));
}

static void
double_the_hash_table(ARRAY A)
{
    unsigned old_hmask = A->hmask;
    unsigned new_hmask = (old_hmask << 1) + 1;
    DUAL_LINK *table;
    A->ptr = zrealloc(A->ptr,
		      (old_hmask + 1) * sizeof(DUAL_LINK),
		      (new_hmask + 1) * sizeof(DUAL_LINK));
    table = (DUAL_LINK *) A->ptr;
    /* zero out the new part which is the back half */
    memset(&table[old_hmask + 1], 0, (old_hmask + 1) * sizeof(DUAL_LINK));

    if (A->type & AY_STR) {
	unsigned i;		/* index to old lists */
	unsigned j;		/* index to new lists */
	ANODE *p;		/* walks an old list */
	ANODE *q;		/* trails p for deletion */
	ANODE *tail;		/* builds new list from the back */
	ANODE dummy0, dummy1;
	for (i = 0, j = old_hmask + 1; i <= old_hmask; i++, j++) {
	    q = &dummy0;
	    q->slink = p = table[i].slink;
	    tail = &dummy1;
	    while (p) {
		if ((p->hval & new_hmask) != (unsigned) i) {	/* move it */
		    q->slink = p->slink;
		    tail = tail->slink = p;
		} else
		    q = p;
		p = q->slink;
	    }
	    table[i].slink = dummy0.slink;
	    tail->slink = (ANODE *) 0;
	    table[j].slink = dummy1.slink;
	}

    }

    if (A->type & AY_INT) {
	unsigned i;		/* index to old lists */
	unsigned j;		/* index to new lists */
	ANODE *p;		/* walks an old list */
	ANODE *q;		/* trails p for deletion */
	ANODE *tail;		/* builds new list from the back */
	ANODE dummy0, dummy1;
	for (i = 0, j = old_hmask + 1; i <= old_hmask; i++, j++) {
	    q = &dummy0;
	    q->ilink = p = table[i].ilink;
	    tail = &dummy1;
	    while (p) {
		if (((unsigned) p->ival & new_hmask) != i) {	/* move it */
		    q->ilink = p->ilink;
		    tail = tail->ilink = p;
		} else
		    q = p;
		p = q->ilink;
	    }
	    table[i].ilink = dummy0.ilink;
	    tail->ilink = (ANODE *) 0;
	    table[j].ilink = dummy1.ilink;
	}

    }

    A->hmask = new_hmask;
    A->limit = hmask_to_limit(new_hmask);
}
