/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details.
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* modutil.c - modify utility routine */

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "slap.h"

#define SIZE_INIT 4 /* initial element size */
#define SIZE_INC 2  /* size increment */
/*
 * Free an array of LDAPMod structures.  Just like ldap_mods_free,
 * except that it assumes that the mods are expressed as a bervec.
 */
void
freepmods(LDAPMod **pmods)
{
    int i;

    for (i = 0; pmods[i] != NULL; ++i) {
        if (pmods[i]->mod_bvalues != NULL) {
            ber_bvecfree(pmods[i]->mod_bvalues);
        }
        if (pmods[i]->mod_type != NULL) {
            slapi_ch_free((void **)&pmods[i]->mod_type);
        }
        slapi_ch_free((void **)&pmods[i]);
    }
    slapi_ch_free((void **)&pmods);
}

/* ======= Utility functions for manipulating a list of LDAPMods ======= */

/*
 * Slapi_Mods can be used in two ways:
 * 1) To wrap an existing array of LDAPMods, or
 * 2) To create a new array of LDAPMods.
 *
 * Slapi_Mods provides memory management and array manipulation
 * functions to make using (LDAPMod**) easier.
 *
 */

Slapi_Mods *
slapi_mods_new()
{
    Slapi_Mods *mods;
    mods = (Slapi_Mods *)slapi_ch_calloc(1, sizeof(Slapi_Mods));
    return mods;
}

/*
 * Initialise a Slapi_Mod suggesting how big an LDAPMod array is needed.
 * It will be free'd when the Slapi_Mods is destroyed.
 */
void
slapi_mods_init(Slapi_Mods *smods, int initCount)
{
    if (NULL == smods) {
        return;
    }
    memset(smods, 0, sizeof(*smods));
    smods->free_mods = 1;
    if (initCount > 0) {
        smods->num_elements = initCount + 1; /* one for NULL element */
        smods->mods = (LDAPMod **)slapi_ch_calloc(1, smods->num_elements * sizeof(LDAPMod *));
    }
}

/*
 * Initialise a Slapi_Mod passing in responsibility for the (LDAPMod **).
 * It will be free'd when the Slapi_Mods is destroyed.
 */
void
slapi_mods_init_passin(Slapi_Mods *smods, LDAPMod **mods)
{
    if (NULL == smods) {
        return;
    }
    slapi_mods_init_byref(smods, mods);
    smods->free_mods = 1;
}

/*
 * Initialise a Slapi_Mod passing in a reference to the (LDAPMod **)
 * It will *not* be free'd when the Slapi_Mods is destroyed.
 */
void
slapi_mods_init_byref(Slapi_Mods *smods, LDAPMod **mods)
{
    if (NULL == smods) {
        return;
    }
    memset(smods, 0, sizeof(*smods));
    if (mods != NULL) {
        smods->mods = mods;
        for (smods->num_mods = 0; mods[smods->num_mods] != NULL; smods->num_mods++)
            ;
        smods->num_elements = smods->num_mods + 1; /* We assume there's nothing spare on the end. */
    }
}

void
slapi_mods_free(Slapi_Mods **smods)
{
    if (smods != NULL && *smods != NULL) {
        slapi_mods_done(*smods);
        slapi_ch_free((void **)smods);
    }
}

void
slapi_mods_done(Slapi_Mods *smods)
{
    if (NULL == smods) {
        return;
    }
    if (smods->mods != NULL) {
        if (smods->free_mods) {
            ldap_mods_free(smods->mods, 1 /* Free the Array and the Elements */);
        }
    }
    memset(smods, 0, sizeof(*smods));
}

static void
slapi_mods_add_one_element(Slapi_Mods *smods)
{
    int need;
    if (NULL == smods) {
        return;
    }
    need = smods->num_mods + 2;
    if (smods->num_elements == 0) {
        PR_ASSERT(smods->mods == NULL);
        smods->num_elements = SIZE_INIT;
        smods->mods = (LDAPMod **)slapi_ch_malloc(smods->num_elements * sizeof(LDAPMod *));
        smods->free_mods = 1;
    }
    if (smods->num_elements < need) {
        PR_ASSERT(smods->free_mods);
        smods->num_elements *= SIZE_INC;
        smods->mods = (LDAPMod **)slapi_ch_realloc((char *)smods->mods, smods->num_elements * sizeof(LDAPMod *));
    }
}

/*
 * Shift everything down to make room to insert the new mod.
 */
void
slapi_mods_insert_at(Slapi_Mods *smods, LDAPMod *mod, int pos)
{
    int i;

    if ((NULL == smods) || (NULL == mod)) {
        return;
    }
    slapi_mods_add_one_element(smods);
    for (i = smods->num_mods - 1; i >= pos; i--) {
        smods->mods[i + 1] = smods->mods[i];
    }
    smods->mods[pos] = mod;
    smods->num_mods++;
    smods->mods[smods->num_mods] = NULL;
}

void
slapi_mods_insert_smod_at(Slapi_Mods *smods, Slapi_Mod *smod, int pos)
{
    if ((NULL == smods) || (NULL == smod)) {
        return;
    }
    slapi_mods_insert_at(smods, smod->mod, pos);
}

/*
 * Shift everything down to make room to insert the new mod.
 */
void
slapi_mods_insert_before(Slapi_Mods *smods, LDAPMod *mod)
{
    if ((NULL == smods) || (NULL == mod)) {
        return;
    }
    slapi_mods_insert_at(smods, mod, smods->iterator);
    smods->iterator++;
}

void
slapi_mods_insert_smod_before(Slapi_Mods *smods, Slapi_Mod *smod)
{
    if ((NULL == smods) || (NULL == smod)) {
        return;
    }
    slapi_mods_insert_before(smods, smod->mod);
}

/*
 * Shift everything down to make room to insert the new mod.
 */
void
slapi_mods_insert_after(Slapi_Mods *smods, LDAPMod *mod)
{
    if ((NULL == smods) || (NULL == mod)) {
        return;
    }
    slapi_mods_insert_at(smods, mod, smods->iterator + 1);
}

void
slapi_mods_insert_smod_after(Slapi_Mods *smods, Slapi_Mod *smod)
{
    slapi_mods_insert_after(smods, smod->mod);
}

/*
 * Add the LDAPMod to the end of the array.
 * Does NOT copy the mod.
 */
void
slapi_mods_add_ldapmod(Slapi_Mods *smods, LDAPMod *mod)
{
    slapi_mods_insert_at(smods, mod, smods->num_mods);
}

void
slapi_mods_add_smod(Slapi_Mods *smods, Slapi_Mod *smod)
{
    slapi_mods_add_ldapmod(smods, smod->mod);
}

/*
 * Makes a copy of everything.
 */
void
slapi_mods_add_modbvps(Slapi_Mods *smods, int modtype, const char *type, struct berval **bvps)
{
    LDAPMod *mod;

    if (NULL == smods) {
        return;
    }
    mod = (LDAPMod *)slapi_ch_malloc(sizeof(LDAPMod));
    mod->mod_type = slapi_ch_strdup(type);
    mod->mod_op = modtype | LDAP_MOD_BVALUES;
    mod->mod_bvalues = NULL;

    if (NULL != bvps) {
        int num_values, i;
        num_values = 0;
        /* Count mods */
        while (NULL != bvps[num_values]) {
            num_values++;
        }
        mod->mod_bvalues = (struct berval **)slapi_ch_malloc((num_values + 1) *
                                                             sizeof(struct berval *));
        for (i = 0; i < num_values; i++) {
            mod->mod_bvalues[i] = ber_bvdup((struct berval *)bvps[i]); /* jcm had to cast away const */
        }
        mod->mod_bvalues[num_values] = NULL;
    }
    slapi_mods_add_ldapmod(smods, mod);
}

/*
 * Makes a copy of everything.
 */
void
slapi_mods_add_mod_values(Slapi_Mods *smods, int modtype, const char *type, Slapi_Value **va)
{
    LDAPMod *mod;
    if (NULL == smods) {
        return;
    }
    mod = (LDAPMod *)slapi_ch_malloc(sizeof(LDAPMod));
    mod->mod_type = slapi_ch_strdup(type);
    mod->mod_op = modtype | LDAP_MOD_BVALUES;
    mod->mod_bvalues = NULL;
    valuearray_get_bervalarray(va, &mod->mod_bvalues);
    slapi_mods_add_ldapmod(smods, mod);
}

/*
 * Makes a copy of everything.
 */
void
slapi_mods_add(Slapi_Mods *smods, int modtype, const char *type, unsigned long len, const char *val)
{
    struct berval bv;
    struct berval *bvps[2];
    if (NULL == smods) {
        return;
    }
    if (len > 0) {
        bv.bv_len = len;
        bv.bv_val = (void *)val; /* We cast away the const, but we're not going to change anything */
        bvps[0] = &bv;
        bvps[1] = NULL;
    } else {
        bvps[0] = NULL;
    }
    slapi_mods_add_modbvps(smods, modtype, type, bvps);
}

/*
 * Makes a copy of everything.
 */
void
slapi_mods_add_string(Slapi_Mods *smods, int modtype, const char *type, const char *val)
{
    PR_ASSERT(val);
    slapi_mods_add(smods, modtype, type, strlen(val), val);
}

void
slapi_mods_remove(Slapi_Mods *smods)
{
    if (NULL == smods) {
        return;
    }
    smods->mods[smods->iterator]->mod_op = LDAP_MOD_IGNORE;
}

LDAPMod *
slapi_mods_get_first_mod(Slapi_Mods *smods)
{
    if (NULL == smods) {
        return NULL;
    }
    /* Reset the iterator in the mod structure */
    smods->iterator = -1;
    return slapi_mods_get_next_mod(smods);
}

LDAPMod *
slapi_mods_get_next_mod(Slapi_Mods *smods)
{
    if (NULL == smods) {
        return NULL;
    }
    /* Move the iterator forward */
    LDAPMod *r = NULL;
    smods->iterator++;

    PR_ASSERT(smods->iterator >= 0);

    /* skip deleted mods if any */
    while (smods->iterator < smods->num_mods && smods->mods[smods->iterator]->mod_op == LDAP_MOD_IGNORE)
        smods->iterator++;

    if (smods->iterator < smods->num_mods) {
        r = smods->mods[smods->iterator];
    }
    return r;
}

static void
mod2smod(LDAPMod *mod, Slapi_Mod *smod)
{
    if ((NULL == smod) || (NULL == mod)) {
        return;
    }
    smod->mod = mod;
    smod->iterator = 0;
    smod->num_values = 0;

    if (mod->mod_op & LDAP_MOD_BVALUES) {
        while (mod->mod_bvalues && mod->mod_bvalues[smod->num_values]) {
            smod->num_values++;
        }
    } else {
        PR_ASSERT(0); /* ggood shouldn't ever use string values in server */
        while (mod->mod_values && mod->mod_values[smod->num_values]) {
            smod->num_values++;
        }
    }

    smod->num_elements = smod->num_values + 1; /* 1- for null char */
}

Slapi_Mod *
slapi_mods_get_first_smod(Slapi_Mods *smods, Slapi_Mod *smod)
{
    LDAPMod *mod = slapi_mods_get_first_mod(smods);

    if (mod == NULL)
        return NULL;

    mod2smod(mod, smod);

    return smod;
}

Slapi_Mod *
slapi_mods_get_next_smod(Slapi_Mods *smods, Slapi_Mod *smod)
{
    LDAPMod *mod = slapi_mods_get_next_mod(smods);

    if (mod == NULL) {
        return NULL;
    } else {
        mod2smod(mod, smod);
    }
    return smod;
}

void
slapi_mods_iterator_backone(Slapi_Mods *smods)
{
    if (NULL == smods) {
        return;
    }
    smods->iterator--;
}


static void
pack_mods(LDAPMod ***modsp)
{
    LDAPMod **mods = NULL;
    if (NULL != modsp && NULL != *modsp) {
        int i;
        int num_slots;
        int src_index, dst_index;
        mods = *modsp;

        /* Make a pass through the array, freeing any marked LDAP_MODS_IGNORE */
        i = 0;
        while (NULL != mods[i]) {
            if (LDAP_MOD_IGNORE == (mods[i]->mod_op & ~LDAP_MOD_BVALUES)) {
                /* Free current slot */
                slapi_ch_free((void **)&mods[i]->mod_type);
                ber_bvecfree(mods[i]->mod_bvalues);
                slapi_ch_free((void **)&mods[i]);
            }
            i++;
        }
        num_slots = i + 1; /* Remember total number of slots */

        /* Make another pass, packing the array */
        dst_index = src_index = 0;
        while (src_index < num_slots) {
            if (NULL != mods[src_index]) {
                mods[dst_index] = mods[src_index];
                dst_index++;
            }
            src_index++;
        }
        mods[dst_index] = NULL;
        if (NULL == mods[0]) {
            /* Packed it down to size zero - deallocate */
            slapi_ch_free((void **)modsp);
        }
    }
}


LDAPMod **
slapi_mods_get_ldapmods_byref(Slapi_Mods *smods)
{
    if (NULL == smods) {
        return NULL;
    }
    pack_mods(&smods->mods); /* XXXggood const gets in the way of this */
    return smods->mods;
}

LDAPMod **
slapi_mods_get_ldapmods_passout(Slapi_Mods *smods)
{
    LDAPMod **mods;
    pack_mods(&smods->mods);
    mods = smods->mods;
    smods->free_mods = 0;
    slapi_mods_done(smods);
    return mods;
}

int
slapi_mods_get_num_mods(const Slapi_Mods *smods)
{
    return smods->num_mods;
}

void
slapi_mods_dump(const Slapi_Mods *smods, const char *text)
{
    int i;
    slapi_log_err(SLAPI_LOG_DEBUG, "slapi_mods_dump", "smod - %s\n", text);
    for (i = 0; i < smods->num_mods; i++) {
        slapi_mod_dump(smods->mods[i], i);
    }
}

/* ======== Utility functions for manipulating an LDAPMod ======= */

/*
 * Slapi_Mod can be used in two ways:
 * 1) To wrap an existing array of LDAPMods, or
 * 2) To create a new array of LDAPMods.
 *
 * Slapi_Mods provides memory management and array manipulation
 * functions to make using (LDAPMod**) easier.
 *
 */

Slapi_Mod *
slapi_mod_new()
{
    Slapi_Mod *mod = (Slapi_Mod *)slapi_ch_calloc(1, sizeof(Slapi_Mod));
    return mod;
}

void
slapi_mod_init(Slapi_Mod *smod, int initCount)
{
    PR_ASSERT(smod != NULL);
    memset(smod, 0, sizeof(*smod));
    smod->free_mod = 1;
    smod->num_elements = initCount + 1;
    smod->mod = (LDAPMod *)slapi_ch_calloc(1, sizeof(LDAPMod));
    if (smod->num_elements)
        smod->mod->mod_bvalues = (struct berval **)slapi_ch_calloc(smod->num_elements, sizeof(struct berval *));
}

void
slapi_mod_init_passin(Slapi_Mod *smod, LDAPMod *mod)
{
    PR_ASSERT(smod != NULL);
    memset(smod, 0, sizeof(*smod));
    smod->free_mod = 1;
    if (mod != NULL) {
        smod->mod = mod;
        if (smod->mod->mod_bvalues != NULL) {
            while (smod->mod->mod_bvalues[smod->num_values] != NULL)
                smod->num_values++;
            smod->num_elements = smod->num_values + 1; /* We assume there's nothing spare on the end. */
        }
    }
}

void
slapi_mod_init_byref(Slapi_Mod *smod, LDAPMod *mod)
{
    PR_ASSERT(smod != NULL);
    memset(smod, 0, sizeof(*smod));
    if (mod != NULL) {
        smod->mod = mod;
        if (smod->mod->mod_bvalues != NULL) {
            while (smod->mod->mod_bvalues[smod->num_values] != NULL)
                smod->num_values++;
            smod->num_elements = smod->num_values + 1; /* We assume there's nothing spare on the end. */
        }
    }
}

void
slapi_mod_init_byval(Slapi_Mod *smod, const LDAPMod *mod)
{
    PR_ASSERT(smod != NULL);
    memset(smod, 0, sizeof(*smod));
    if (mod != NULL) {
        smod->mod = (LDAPMod *)slapi_ch_calloc(1, sizeof(LDAPMod));
        smod->free_mod = 1;
        slapi_mod_set_operation(smod, mod->mod_op);
        slapi_mod_set_type(smod, mod->mod_type);
        if (mod->mod_bvalues != NULL) {
            while (mod->mod_bvalues[smod->num_values] != NULL) {
                slapi_mod_add_value(smod, mod->mod_bvalues[smod->num_values]);
            }
        }
    }
}

void
slapi_mod_init_valueset_byval(Slapi_Mod *smod, int op, const char *type, const Slapi_ValueSet *svs)
{
    PR_ASSERT(smod != NULL);
    slapi_mod_init(smod, 0);
    slapi_mod_set_operation(smod, op);
    slapi_mod_set_type(smod, type);
    if (svs != NULL) {
        Slapi_Value **svary = valueset_get_valuearray(svs);
        ber_bvecfree(smod->mod->mod_bvalues);
        smod->mod->mod_bvalues = NULL;
        valuearray_get_bervalarray(svary, &smod->mod->mod_bvalues);
        smod->num_values = slapi_valueset_count(svs);
        smod->num_elements = smod->num_values + 1;
    }
}

void
slapi_mod_free(Slapi_Mod **smod)
{
    slapi_mod_done(*smod);
    slapi_ch_free((void **)smod);
    *smod = NULL;
}

void
slapi_mod_done(Slapi_Mod *smod)
{
    PR_ASSERT(smod != NULL);
    if (smod->free_mod) {
        ber_bvecfree(smod->mod->mod_bvalues);
        slapi_ch_free((void **)&(smod->mod->mod_type));
        slapi_ch_free((void **)&(smod->mod));
    }
    memset(smod, 0, sizeof(*smod));
}

/*
 * Add a value to the list of values in the modification.
 */
void
slapi_mod_add_value(Slapi_Mod *smod, const struct berval *val)
{
    PR_ASSERT(smod != NULL);
    PR_ASSERT(val != NULL);
    /*    PR_ASSERT(slapi_mod_get_operation(smod) & LDAP_MOD_BVALUES);*/

    bervalarray_add_berval_fast(&(smod->mod->mod_bvalues), (struct berval *)val, smod->num_values, &smod->num_elements);
    smod->num_values++;
}

/*
 * Remove the value at the iterator from the list of values in
 * the LDAP modification.
 */
void
slapi_mod_remove_value(Slapi_Mod *smod)
{
    /* loop over the mod values moving them down to cover up the value to be removed */
    struct berval **vals;
    int i, k;
    PR_ASSERT(smod != NULL);
    PR_ASSERT(smod->mod != NULL);
    vals = smod->mod->mod_bvalues;
    i = smod->iterator - 1;
    ber_bvfree(vals[i]);
    for (k = i + 1; vals[k] != NULL; k++) {
        vals[k - 1] = vals[k];
    }
    vals[k - 1] = NULL;
    /* Reset the iterator */
    smod->num_values--;
    smod->iterator--;
}

struct berval *
slapi_mod_get_first_value(Slapi_Mod *smod)
{
    PR_ASSERT(smod != NULL);
    /* Reset the iterator in the mod structure */
    smod->iterator = 0;
    return slapi_mod_get_next_value(smod);
}

struct berval *
slapi_mod_get_next_value(Slapi_Mod *smod)
{
    /* Move the iterator forward */
    struct berval *r = NULL;
    PR_ASSERT(smod != NULL);
    PR_ASSERT(smod->mod != NULL);
    if (smod->iterator < smod->num_values) {
        r = smod->mod->mod_bvalues[smod->iterator];
        smod->iterator++;
    }
    return r;
}

int
slapi_mod_get_num_values(const Slapi_Mod *smod)
{
    PR_ASSERT(smod != NULL);
    return smod->num_values;
}

const char *
slapi_mod_get_type(const Slapi_Mod *smod)
{
    PR_ASSERT(smod != NULL);
    PR_ASSERT(smod->mod != NULL);
    return smod->mod->mod_type;
}

int
slapi_mod_get_operation(const Slapi_Mod *smod)
{
    PR_ASSERT(smod != NULL);
    PR_ASSERT(smod->mod != NULL);
    return smod->mod->mod_op;
}

void
slapi_mod_set_type(Slapi_Mod *smod, const char *type)
{
    PR_ASSERT(smod != NULL);
    PR_ASSERT(smod->mod != NULL);
    if (smod->mod->mod_type != NULL) {
        slapi_ch_free((void **)&smod->mod->mod_type);
    }
    smod->mod->mod_type = slapi_ch_strdup(type);
}

void
slapi_mod_set_operation(Slapi_Mod *smod, int op)
{
    PR_ASSERT(smod != NULL);
    PR_ASSERT(smod->mod != NULL);
    smod->mod->mod_op = op;
}

const LDAPMod *
slapi_mod_get_ldapmod_byref(const Slapi_Mod *smod)
{
    PR_ASSERT(smod != NULL);
    return smod->mod;
}

LDAPMod *
slapi_mod_get_ldapmod_passout(Slapi_Mod *smod)
{
    LDAPMod *mod;
    PR_ASSERT(smod != NULL);
    mod = smod->mod;
    smod->free_mod = 0;
    slapi_mod_done(smod);
    return mod;
}

/* a valid LDAPMod is one with operation of LDAP_MOD_ADD, *_DELETE, *_REPLACE
   ored with LDAP_MOD_BVALUES; non-null type and at list one value
   for add and replace operations
 */
int
slapi_mod_isvalid(const Slapi_Mod *mod)
{
    int op;

    if (mod == NULL || mod->mod == NULL)
        return 0;

    op = mod->mod->mod_op;

    if (!SLAPI_IS_MOD_ADD(op) && !SLAPI_IS_MOD_DELETE(op) && !SLAPI_IS_MOD_REPLACE(op))
        return 0;

    if (mod->mod->mod_type == NULL)
        return 0;

    /* add op must have at least 1 value */
    if (SLAPI_IS_MOD_ADD(op) && (mod->num_values == 0))
        return 0;

    return 1;
}

void
slapi_mod_dump(LDAPMod *mod, int n)
{
    if (mod != NULL) {
        int operationtype = mod->mod_op & ~LDAP_MOD_BVALUES;
        switch (operationtype) {
        case LDAP_MOD_ADD:
            slapi_log_err(SLAPI_LOG_DEBUG, "slapi_mod_dump",
                          "smod %d - add: %s\n", n, mod->mod_type);
            break;

        case LDAP_MOD_DELETE:
            slapi_log_err(SLAPI_LOG_DEBUG, "slapi_mod_dump",
                          "smod %d - delete: %s\n", n, mod->mod_type);
            break;

        case LDAP_MOD_REPLACE:
            slapi_log_err(SLAPI_LOG_DEBUG, "slapi_mod_dump",
                          "smod %d - replace: %s\n", n, mod->mod_type);
            break;

        case LDAP_MOD_IGNORE:
            slapi_log_err(SLAPI_LOG_DEBUG, "slapi_mod_dump",
                          "smod %d - ignore: %s\n", n, mod->mod_type);
            break;
        }
        if (operationtype != LDAP_MOD_IGNORE) {
            int i;
            for (i = 0; mod->mod_bvalues != NULL && mod->mod_bvalues[i] != NULL; i++) {
                char *buf, *bufp;
                int len = strlen(mod->mod_type);
                len = LDIF_SIZE_NEEDED(len, mod->mod_bvalues[i]->bv_len) + 1;
                buf = slapi_ch_malloc(len);
                bufp = buf;
                slapi_ldif_put_type_and_value_with_options(&bufp, mod->mod_type, mod->mod_bvalues[i]->bv_val, mod->mod_bvalues[i]->bv_len, 0);
                *bufp = '\0';
                slapi_log_err(SLAPI_LOG_DEBUG, "slapi_mod_dump", "smod %d - value: %s\n", n, buf);
                slapi_ch_free((void **)&buf);
            }
        }
    } else {
        slapi_log_err(SLAPI_LOG_DEBUG, "slapi_mod_dump", "smod - null\n");
    }
}
