/*****************************************************************************
 *
 * MODULE:       Vector library
 *
 * AUTHOR(S):    Radim Blazek
 *
 * PURPOSE:      Lower level functions for reading/writing/manipulating vectors.
 *
 * COPYRIGHT:    (C) 2001 by the GRASS Development Team
 *
 *               This program is free software under the GNU General Public
 *               License (>=v2). Read the file COPYING that comes with
 *               GRASS for details.
 *
 *****************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <grass/vector.h>

/*!
 * \brief Initialize Plus_head structure (cidx)
 *
 * \param Plus pointer to Plus_head structure
 *
 * \return 1 OK
 * \return 0 on error
 */
int dig_cidx_init(struct Plus_head *Plus)
{
    G_debug(3, "dig_cidx_init()");

    Plus->n_cidx = 0;
    Plus->a_cidx = 5;
    Plus->cidx =
        (struct Cat_index *)G_malloc(Plus->a_cidx * sizeof(struct Cat_index));
    if (!Plus->cidx)
        return 0;
    Plus->cidx_up_to_date = 0;
    return 1;
}

/* Free category index */
void dig_cidx_free(struct Plus_head *Plus)
{
    int i;
    struct Cat_index *ci;

    G_debug(2, "dig_cidx_free()");
    for (i = 0; i < Plus->n_cidx; i++) {
        ci = &(Plus->cidx[i]);
        G_free(ci->cat);
        ci->cat = NULL;
        ci->field = ci->n_cats = ci->a_cats = ci->n_types = 0;
    }
    if (Plus->cidx) {
        G_free(Plus->cidx);
        Plus->cidx = NULL;
    }
    Plus->a_cidx = 0;
    Plus->n_cidx = 0;
    Plus->cidx_up_to_date = 0;
}

/*
 *  dig_cidx_add_cat ()
 *  add new field - cat - line record, space is allocated if necessary
 *
 *  returns 1 OK
 *          0 on error
 */
int dig_cidx_add_cat(struct Plus_head *Plus, int field, int cat, int line,
                     int type)
{
    int i, si, found;
    struct Cat_index *ci;

    G_debug(3, "dig_cidx_add_cat(): field = %d cat = %d line = %d type = %d",
            field, cat, line, type);

    /* Find field or add new */
    si = -1;
    for (i = 0; i < Plus->n_cidx; i++) {
        if (Plus->cidx[i].field == field) {
            si = i;
        }
    }
    if (si == -1) { /* not found add new */
        if (Plus->n_cidx == Plus->a_cidx) {
            Plus->a_cidx += 10;
            Plus->cidx = (struct Cat_index *)G_realloc(
                Plus->cidx, Plus->a_cidx * sizeof(struct Cat_index));
            if (!Plus->cidx)
                return 0;
        }
        si = Plus->n_cidx;
        ci = &(Plus->cidx[si]);
        ci->field = field;
        ci->n_cats = ci->a_cats = 0;
        ci->cat = NULL;
        ci->n_types = 0;
        ci->offset = 0;
        Plus->n_cidx++;
    }

    /* Add new cat - line record */
    ci = &(Plus->cidx[si]);
    if (ci->n_cats == ci->a_cats) {
        ci->a_cats += 5000;
        ci->cat = G_realloc(ci->cat, ci->a_cats * 3 * sizeof(int));
    }

    ci->cat[ci->n_cats][0] = cat;
    ci->cat[ci->n_cats][1] = type;
    ci->cat[ci->n_cats][2] = line;
    ci->n_cats++;

    /* Add type */
    found = 0;
    for (i = 0; i < ci->n_types; i++) {
        if (ci->type[i][0] == type) {
            ci->type[i][1]++;
            found = 1;
        }
    }
    if (!found) {
        ci->type[ci->n_types][0] = type;
        ci->type[ci->n_types][1] = 1;
        ci->n_types++;
    }

    return 1;
}

/* Compare by cat, resolve ties by type, resolve ties by id */
static int cmp_cat(const void *pa, const void *pb)
{
    int *p1 = (int *)pa;
    int *p2 = (int *)pb;

    if (p1[0] < p2[0])
        return -1;
    if (p1[0] > p2[0])
        return 1;
    if (p1[1] < p2[1])
        return -1;
    if (p1[1] > p2[1])
        return 1;
    if (p1[2] < p2[2])
        return -1;
    if (p1[2] > p2[2])
        return 1;
    return 0;
}

/* Compare by field */
static int cmp_field(const void *pa, const void *pb)
{
    struct Cat_index *p1 = (struct Cat_index *)pa;
    struct Cat_index *p2 = (struct Cat_index *)pb;

    if (p1->field < p2->field)
        return -1;
    if (p1->field > p2->field)
        return 1;
    return 0;
}

/*
 *  dig_cidx_add_cat_sorted ()
 *  add new field - cat - line record to sorted category index, space is
 * allocated if necessary
 *
 *  returns 1 OK
 *          0 on error
 */
int dig_cidx_add_cat_sorted(struct Plus_head *Plus, int field, int cat,
                            int line, int type)
{
    int i, si, found, position;
    struct Cat_index *ci;

    G_debug(
        3, "dig_cidx_add_cat_sorted(): field = %d cat = %d line = %d type = %d",
        field, cat, line, type);

    /* Find field or add new */
    si = -1;
    for (i = 0; i < Plus->n_cidx; i++) {
        if (Plus->cidx[i].field == field) {
            si = i;
        }
    }
    if (si == -1) { /* not found add new */
        if (Plus->n_cidx == Plus->a_cidx) {
            Plus->a_cidx += 10;
            Plus->cidx = (struct Cat_index *)G_realloc(
                Plus->cidx, Plus->a_cidx * sizeof(struct Cat_index));
            if (!Plus->cidx)
                return 0;
        }
        si = Plus->n_cidx;
        ci = &(Plus->cidx[si]);
        ci->field = field;
        ci->n_cats = ci->a_cats = 0;
        ci->cat = NULL;
        ci->n_types = 0;
        ci->offset = 0;
        Plus->n_cidx++;
    }

    /* Add new cat - line record */
    ci = &(Plus->cidx[si]);
    if (ci->n_cats == ci->a_cats) {
        ci->a_cats += 5000;
        ci->cat = G_realloc(ci->cat, ci->a_cats * 3 * sizeof(int));
    }

    /* Find position and move on the way */
    for (position = ci->n_cats; position > 0; position--) {
        if (ci->cat[position - 1][0] < cat ||
            (ci->cat[position - 1][0] == cat &&
             ci->cat[position - 1][1] <= type)) {
            break;
        }
        ci->cat[position][0] = ci->cat[position - 1][0];
        ci->cat[position][1] = ci->cat[position - 1][1];
        ci->cat[position][2] = ci->cat[position - 1][2];
    }

    G_debug(4, "position = %d", position);

    ci->cat[position][0] = cat;
    ci->cat[position][1] = type;
    ci->cat[position][2] = line;
    ci->n_cats++;

    /* Add type */
    found = 0;
    for (i = 0; i < ci->n_types; i++) {
        if (ci->type[i][0] == type) {
            ci->type[i][1]++;
            found = 1;
        }
    }
    if (!found) {
        ci->type[ci->n_types][0] = type;
        ci->type[ci->n_types][1] = 1;
        ci->n_types++;
    }

    /* Sort by field */
    qsort(Plus->cidx, Plus->n_cidx, sizeof(struct Cat_index), cmp_field);

    G_debug(3, "Added new category to index");

    return 1;
}

/*
 *  dig_cidx_del_cat ()
 *  delete old field - cat - line record from _sorted_ category index
 *
 *  returns 1 OK
 *          0 on error
 */
int dig_cidx_del_cat(struct Plus_head *Plus, int field, int cat, int line,
                     int type)
{
    int i, position;
    struct Cat_index *ci;

    G_debug(3, "dig_cidx_del_cat(): field = %d cat = %d line = %d", field, cat,
            line);

    /* Find field or add new */
    ci = NULL;
    for (i = 0; i < Plus->n_cidx; i++) {
        if (Plus->cidx[i].field == field) {
            ci = &(Plus->cidx[i]);
        }
    }
    if (ci == NULL) { /* should not happen */
        G_warning("BUG: Category index not found for field %d.", field);
        return 0;
    }

    /* Find position */
    G_debug(3, "n_cats = %d", ci->n_cats);
    for (position = 0; position < ci->n_cats; position++) {
        if (ci->cat[position][0] == cat && ci->cat[position][1] == type &&
            ci->cat[position][2] == line) {
            break;
        }
    }

    G_debug(4, "position = %d", position);

    if (position == ci->n_cats) {
        G_warning("BUG: Category not found in category index.");
        return 0;
    }

    /* Delete */
    for (i = position; i < ci->n_cats - 1; i++) {
        ci->cat[i][0] = ci->cat[i + 1][0];
        ci->cat[i][1] = ci->cat[i + 1][1];
        ci->cat[i][2] = ci->cat[i + 1][2];
    }

    ci->n_cats--;

    for (i = 0; i < ci->n_types; i++) {
        if (ci->type[i][0] == type) {
            ci->type[i][1]--;
        }
    }

    G_debug(3, "Deleted from category index");
    return 1;
}

/*
 *  dig_cidx_sort ()
 *  sort all records in cat index
 *
 */
void dig_cidx_sort(struct Plus_head *Plus)
{
    int f;
    struct Cat_index *ci;

    G_debug(2, "dig_cidx_sort()");

    for (f = 0; f < Plus->n_cidx; f++) {
        int c, nucats = 0;

        ci = &(Plus->cidx[f]);

        /* Sort by 1. category, 2. type, 3. line id */
        qsort(ci->cat, ci->n_cats, 3 * sizeof(int), cmp_cat);

        /* Calculate number of unique cats */
        if (ci->n_cats > 0)
            nucats++;
        for (c = 1; c < ci->n_cats; c++) {
            if (ci->cat[c][0] != ci->cat[c - 1][0])
                nucats++;
        }
        ci->n_ucats = nucats;
    }

    /* Sort by field */
    qsort(Plus->cidx, Plus->n_cidx, sizeof(struct Cat_index), cmp_field);
}
