/* -*- mode: C -*-  */
/*
   IGraph library.
   Copyright (C) 2003-2012  Gabor Csardi <csardi.gabor@gmail.com>
   334 Harvard street, Cambridge, MA 02139 USA

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301 USA

*/

#include "igraph_types.h"
#include "igraph_strvector.h"
#include "igraph_memory.h"
#include "igraph_error.h"
#include "config.h"

#include <assert.h>
#include <string.h>         /* memcpy & co. */
#include <stdlib.h>

/**
 * \section igraph_strvector_t
 * <para>The <type>igraph_strvector_t</type> type is a vector of strings.
 * The current implementation is very simple and not too efficient. It
 * works fine for not too many strings, e.g. the list of attribute
 * names is returned in a string vector by \ref
 * igraph_cattribute_list(). Do not expect great performance from this
 * type.</para>
 *
 * <para>
 * \example examples/simple/igraph_strvector.c
 * </para>
 */

/**
 * \ingroup strvector
 * \function igraph_strvector_init
 * \brief Initialize
 *
 * Reserves memory for the string vector, a string vector must be
 * first initialized before calling other functions on it.
 * All elements of the string vector are set to the empty string.
 * \param sv Pointer to an initialized string vector.
 * \param len The (initial) length of the string vector.
 * \return Error code.
 *
 * Time complexity: O(\p len).
 */

int igraph_strvector_init(igraph_strvector_t *sv, long int len) {
    long int i;
    sv->data = igraph_Calloc(len, char*);
    if (sv->data == 0) {
        IGRAPH_ERROR("strvector init failed", IGRAPH_ENOMEM);
    }
    for (i = 0; i < len; i++) {
        sv->data[i] = igraph_Calloc(1, char);
        if (sv->data[i] == 0) {
            igraph_strvector_destroy(sv);
            IGRAPH_ERROR("strvector init failed", IGRAPH_ENOMEM);
        }
        sv->data[i][0] = '\0';
    }
    sv->len = len;

    return 0;
}

/**
 * \ingroup strvector
 * \function igraph_strvector_destroy
 * \brief Free allocated memory
 *
 * Destroy a string vector. It may be reinitialized with \ref
 * igraph_strvector_init() later.
 * \param sv The string vector.
 *
 * Time complexity: O(l), the total length of the strings, maybe less
 * depending on the memory manager.
 */

void igraph_strvector_destroy(igraph_strvector_t *sv) {
    long int i;
    assert(sv != 0);
    if (sv->data != 0) {
        for (i = 0; i < sv->len; i++) {
            if (sv->data[i] != 0) {
                igraph_Free(sv->data[i]);
            }
        }
        igraph_Free(sv->data);
    }
}

/**
 * \ingroup strvector
 * \function igraph_strvector_get
 * \brief Indexing
 *
 * Query an element of a string vector. See also the \ref STR macro
 * for an easier way.
 * \param sv The input string vector.
 * \param idx The index of the element to query.
 * \param Pointer to a <type>char*</type>, the address of the string
 *   is stored here.
 *
 * Time complexity: O(1).
 */

void igraph_strvector_get(const igraph_strvector_t *sv, long int idx,
                          char **value) {
    assert(sv != 0);
    assert(sv->data != 0);
    assert(sv->data[idx] != 0);
    *value = sv->data[idx];
}

/**
 * \ingroup strvector
 * \function igraph_strvector_set
 * \brief Set an element
 *
 * The provided \p value is copied into the \p idx position in the
 * string vector.
 * \param sv The string vector.
 * \param idx The position to set.
 * \param value The new value.
 * \return Error code.
 *
 * Time complexity: O(l), the length of the new string. Maybe more,
 * depending on the memory management, if reallocation is needed.
 */

int igraph_strvector_set(igraph_strvector_t *sv, long int idx,
                         const char *value) {
    assert(sv != 0);
    assert(sv->data != 0);
    if (sv->data[idx] == 0) {
        sv->data[idx] = igraph_Calloc(strlen(value) + 1, char);
        if (sv->data[idx] == 0) {
            IGRAPH_ERROR("strvector set failed", IGRAPH_ENOMEM);
        }
    } else {
        char *tmp = igraph_Realloc(sv->data[idx], strlen(value) + 1, char);
        if (tmp == 0) {
            IGRAPH_ERROR("strvector set failed", IGRAPH_ENOMEM);
        }
        sv->data[idx] = tmp;
    }
    strcpy(sv->data[idx], value);

    return 0;
}

/**
 * \ingroup strvector
 * \function igraph_strvector_set2
 * \brief Sets an element
 *
 * This is almost the same as \ref igraph_strvector_set, but the new
 * value is not a zero terminated string, but its length is given.
 * \param sv The string vector.
 * \param idx The position to set.
 * \param value The new value.
 * \param len The length of the new value.
 * \return Error code.
 *
 * Time complexity: O(l), the length of the new string. Maybe more,
 * depending on the memory management, if reallocation is needed.
 */
int igraph_strvector_set2(igraph_strvector_t *sv, long int idx,
                          const char *value, int len) {
    assert(sv != 0);
    assert(sv->data != 0);
    if (sv->data[idx] == 0) {
        sv->data[idx] = igraph_Calloc(len + 1, char);
        if (sv->data[idx] == 0) {
            IGRAPH_ERROR("strvector set failed", IGRAPH_ENOMEM);
        }
    } else {
        char *tmp = igraph_Realloc(sv->data[idx], (size_t) len + 1, char);
        if (tmp == 0) {
            IGRAPH_ERROR("strvector set failed", IGRAPH_ENOMEM);
        }
        sv->data[idx] = tmp;
    }
    memcpy(sv->data[idx], value, (size_t) len * sizeof(char));
    sv->data[idx][len] = '\0';

    return 0;
}

/**
 * \ingroup strvector
 * \function igraph_strvector_remove_section
 * \brief Removes a section from a string vector.
 * \todo repair realloc
 */

void igraph_strvector_remove_section(igraph_strvector_t *v, long int from,
                                     long int to) {
    long int i;
    /*   char **tmp; */

    assert(v != 0);
    assert(v->data != 0);

    for (i = from; i < to; i++) {
        if (v->data[i] != 0) {
            igraph_Free(v->data[i]);
        }
    }
    for (i = 0; i < v->len - to; i++) {
        v->data[from + i] = v->data[to + i];
    }

    v->len -= (to - from);

    /* try to make it smaller */
    /*   tmp=igraph_Realloc(v->data, v->len, char*); */
    /*   if (tmp!=0) { */
    /*     v->data=tmp; */
    /*   } */
}

/**
 * \ingroup strvector
 * \function igraph_strvector_remove
 * \brief Removes a single element from a string vector.
 *
 * The string will be one shorter.
 * \param The string vector.
 * \param elem The index of the element to remove.
 *
 * Time complexity: O(n), the length of the string.
 */

void igraph_strvector_remove(igraph_strvector_t *v, long int elem) {
    assert(v != 0);
    assert(v->data != 0);
    igraph_strvector_remove_section(v, elem, elem + 1);
}

/**
 * \ingroup strvector
 * \function igraph_strvector_move_interval
 * \brief Copies an interval of a string vector.
 */

void igraph_strvector_move_interval(igraph_strvector_t *v, long int begin,
                                    long int end, long int to) {
    long int i;
    assert(v != 0);
    assert(v->data != 0);
    for (i = to; i < to + end - begin; i++) {
        if (v->data[i] != 0) {
            igraph_Free(v->data[i]);
        }
    }
    for (i = 0; i < end - begin; i++) {
        if (v->data[begin + i] != 0) {
            size_t len = strlen(v->data[begin + i]) + 1;
            v->data[to + i] = igraph_Calloc(len, char);
            memcpy(v->data[to + i], v->data[begin + i], sizeof(char)*len);
        }
    }
}

/**
 * \ingroup strvector
 * \function igraph_strvector_copy
 * \brief Initialization by copying.
 *
 * Initializes a string vector by copying another string vector.
 * \param to Pointer to an uninitialized string vector.
 * \param from The other string vector, to be copied.
 * \return Error code.
 *
 * Time complexity: O(l), the total length of the strings in \p from.
 */

int igraph_strvector_copy(igraph_strvector_t *to,
                          const igraph_strvector_t *from) {
    long int i;
    char *str;
    assert(from != 0);
    /*   assert(from->data != 0); */
    to->data = igraph_Calloc(from->len, char*);
    if (to->data == 0) {
        IGRAPH_ERROR("Cannot copy string vector", IGRAPH_ENOMEM);
    }
    to->len = from->len;

    for (i = 0; i < from->len; i++) {
        int ret;
        igraph_strvector_get(from, i, &str);
        ret = igraph_strvector_set(to, i, str);
        if (ret != 0) {
            igraph_strvector_destroy(to);
            IGRAPH_ERROR("cannot copy string vector", ret);
        }
    }

    return 0;
}

/**
 * \function igraph_strvector_append
 * Concatenate two string vectors.
 *
 * \param to The first string vector, the result is stored here.
 * \param from The second string vector, it is kept unchanged.
 * \return Error code.
 *
 * Time complexity: O(n+l2), n is the number of strings in the new
 * string vector, l2 is the total length of strings in the \p from
 * string vector.
 */

int igraph_strvector_append(igraph_strvector_t *to,
                            const igraph_strvector_t *from) {
    long int len1 = igraph_strvector_size(to), len2 = igraph_strvector_size(from);
    long int i;
    igraph_bool_t error = 0;
    IGRAPH_CHECK(igraph_strvector_resize(to, len1 + len2));
    for (i = 0; i < len2; i++) {
        if (from->data[i][0] != '\0') {
            igraph_Free(to->data[len1 + i]);
            to->data[len1 + i] = strdup(from->data[i]);
            if (!to->data[len1 + i]) {
                error = 1;
                break;
            }
        }
    }
    if (error) {
        igraph_strvector_resize(to, len1);
        IGRAPH_ERROR("Cannot append string vector", IGRAPH_ENOMEM);
    }
    return 0;
}

/**
 * \function igraph_strvector_clear
 * Remove all elements
 *
 * After this operation the string vector will be empty.
 * \param sv The string vector.
 *
 * Time complexity: O(l), the total length of strings, maybe less,
 * depending on the memory manager.
 */

void igraph_strvector_clear(igraph_strvector_t *sv) {
    long int i, n = igraph_strvector_size(sv);
    char **tmp;

    for (i = 0; i < n; i++) {
        igraph_Free(sv->data[i]);
    }
    sv->len = 0;
    /* try to give back some memory */
    tmp = igraph_Realloc(sv->data, 1, char*);
    if (tmp != 0) {
        sv->data = tmp;
    }
}

/**
 * \ingroup strvector
 * \function igraph_strvector_resize
 * \brief Resize
 *
 * If the new size is bigger then empty strings are added, if it is
 * smaller then the unneeded elements are removed.
 * \param v The string vector.
 * \param newsize The new size.
 * \return Error code.
 *
 * Time complexity: O(n), the number of strings if the vector is made
 * bigger, O(l), the total length of the deleted strings if it is made
 * smaller, maybe less, depending on memory management.
 */

int igraph_strvector_resize(igraph_strvector_t* v, long int newsize) {
    long int toadd = newsize - v->len, i, j;
    char **tmp;
    long int reallocsize = newsize;
    if (reallocsize == 0) {
        reallocsize = 1;
    }

    assert(v != 0);
    assert(v->data != 0);
    /*   printf("resize %li to %li\n", v->len, newsize); */
    if (newsize < v->len) {
        for (i = newsize; i < v->len; i++) {
            igraph_Free(v->data[i]);
        }
        /* try to give back some space */
        tmp = igraph_Realloc(v->data, (size_t) reallocsize, char*);
        /*     printf("resize %li to %li, %p\n", v->len, newsize, tmp); */
        if (tmp != 0) {
            v->data = tmp;
        }
    } else if (newsize > v->len) {
        igraph_bool_t error = 0;
        tmp = igraph_Realloc(v->data, (size_t) reallocsize, char*);
        if (tmp == 0) {
            IGRAPH_ERROR("cannot resize string vector", IGRAPH_ENOMEM);
        }
        v->data = tmp;

        for (i = 0; i < toadd; i++) {
            v->data[v->len + i] = igraph_Calloc(1, char);
            if (v->data[v->len + i] == 0) {
                error = 1;
                break;
            }
            v->data[v->len + i][0] = '\0';
        }
        if (error) {
            /* There was an error, free everything we've allocated so far */
            for (j = 0; j < i; j++) {
                if (v->data[v->len + i] != 0) {
                    igraph_Free(v->data[v->len + i]);
                }
            }
            /* Try to give back space */
            tmp = igraph_Realloc(v->data, (size_t) (v->len), char*);
            if (tmp != 0) {
                v->data = tmp;
            }
            IGRAPH_ERROR("Cannot resize string vector", IGRAPH_ENOMEM);
        }
    }
    v->len = newsize;

    return 0;
}

/**
 * \ingroup strvector
 * \function igraph_strvector_size
 * \brief Gives the size of a string vector.
 *
 * \param sv The string vector.
 * \return The length of the string vector.
 *
 * Time complexity: O(1).
 */

long int igraph_strvector_size(const igraph_strvector_t *sv) {
    assert(sv != 0);
    assert(sv->data != 0);
    return sv->len;
}

/**
 * \ingroup strvector
 * \function igraph_strvector_add
 * \brief Adds an element to the back of a string vector.
 *
 * \param v The string vector.
 * \param value The string to add, it will be copied.
 * \return Error code.
 *
 * Time complexity: O(n+l), n is the total number of strings, l is the
 * length of the new string.
 */

int igraph_strvector_add(igraph_strvector_t *v, const char *value) {
    long int s = igraph_strvector_size(v);
    char **tmp;
    assert(v != 0);
    assert(v->data != 0);
    tmp = igraph_Realloc(v->data, (size_t) s + 1, char*);
    if (tmp == 0) {
        IGRAPH_ERROR("cannot add string to string vector", IGRAPH_ENOMEM);
    }
    v->data = tmp;
    v->data[s] = igraph_Calloc(strlen(value) + 1, char);
    if (v->data[s] == 0) {
        IGRAPH_ERROR("cannot add string to string vector", IGRAPH_ENOMEM);
    }
    strcpy(v->data[s], value);
    v->len += 1;

    return 0;
}

/**
 * \ingroup strvector
 * \function igraph_strvector_permdelete
 * \brief Removes elements from a string vector (for internal use)
 */

void igraph_strvector_permdelete(igraph_strvector_t *v, const igraph_vector_t *index,
                                 long int nremove) {
    long int i;
    char **tmp;
    assert(v != 0);
    assert(v->data != 0);

    for (i = 0; i < igraph_strvector_size(v); i++) {
        if (VECTOR(*index)[i] != 0) {
            v->data[ (long int) VECTOR(*index)[i] - 1 ] = v->data[i];
        } else {
            igraph_Free(v->data[i]);
        }
    }
    /* Try to make it shorter */
    tmp = igraph_Realloc(v->data, v->len - nremove ?
                         (size_t) (v->len - nremove) : 1, char*);
    if (tmp != 0) {
        v->data = tmp;
    }
    v->len -= nremove;
}

/**
 * \ingroup strvector
 * \function igraph_strvector_remove_negidx
 * \brief Removes elements from a string vector (for internal use)
 */

void igraph_strvector_remove_negidx(igraph_strvector_t *v, const igraph_vector_t *neg,
                                    long int nremove) {
    long int i, idx = 0;
    char **tmp;
    assert(v != 0);
    assert(v->data != 0);
    for (i = 0; i < igraph_strvector_size(v); i++) {
        if (VECTOR(*neg)[i] >= 0) {
            v->data[idx++] = v->data[i];
        } else {
            igraph_Free(v->data[i]);
        }
    }
    /* Try to give back some memory */
    tmp = igraph_Realloc(v->data, v->len - nremove ?
                         (size_t) (v->len - nremove) : 1, char*);
    if (tmp != 0) {
        v->data = tmp;
    }
    v->len -= nremove;
}

int igraph_strvector_print(const igraph_strvector_t *v, FILE *file,
                           const char *sep) {

    long int i, n = igraph_strvector_size(v);
    if (n != 0) {
        fprintf(file, "%s", STR(*v, 0));
    }
    for (i = 1; i < n; i++) {
        fprintf(file, "%s%s", sep, STR(*v, i));
    }
    return 0;

}

int igraph_strvector_index(const igraph_strvector_t *v,
                           igraph_strvector_t *newv,
                           const igraph_vector_t *idx) {

    long int i, newlen = igraph_vector_size(idx);
    IGRAPH_CHECK(igraph_strvector_resize(newv, newlen));

    for (i = 0; i < newlen; i++) {
        long int j = (long int) VECTOR(*idx)[i];
        char *str;
        igraph_strvector_get(v, j, &str);
        igraph_strvector_set(newv, i, str);
    }

    return 0;
}
