/* -*- mode: C -*-  */
/*
   IGraph library.
   Copyright (C) 2005-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_attributes.h"
#include "igraph_memory.h"
#include "config.h"

#include <string.h>
#include <stdarg.h>

/* Should you ever want to have a thread-local attribute handler table, prepend
 * IGRAPH_THREAD_LOCAL to the following declaration */
igraph_attribute_table_t *igraph_i_attribute_table = 0;

int igraph_i_attribute_init(igraph_t *graph, void *attr) {
    graph->attr = 0;
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->init(graph, attr);
    } else {
        return 0;
    }
}

void igraph_i_attribute_destroy(igraph_t *graph) {
    if (igraph_i_attribute_table) {
        igraph_i_attribute_table->destroy(graph);
    }
}

int igraph_i_attribute_copy(igraph_t *to, const igraph_t *from, igraph_bool_t ga,
                            igraph_bool_t va, igraph_bool_t ea) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->copy(to, from, ga, va, ea);
    } else {
        return 0;
    }
}

int igraph_i_attribute_add_vertices(igraph_t *graph, long int nv, void *attr) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->add_vertices(graph, nv, attr);
    } else {
        return 0;
    }
}

int igraph_i_attribute_permute_vertices(const igraph_t *graph,
                                        igraph_t *newgraph,
                                        const igraph_vector_t *idx) {

    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->permute_vertices(graph, newgraph, idx);
    } else {
        return 0;
    }
}

int igraph_i_attribute_combine_vertices(const igraph_t *graph,
                                        igraph_t *newgraph,
                                        const igraph_vector_ptr_t *merges,
                                        const igraph_attribute_combination_t *comb) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->combine_vertices(graph, newgraph,
                merges,
                comb);
    } else {
        return 0;
    }
}

int igraph_i_attribute_add_edges(igraph_t *graph,
                                 const igraph_vector_t *edges, void *attr) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->add_edges(graph, edges, attr);
    } else {
        return 0;
    }
}

int igraph_i_attribute_permute_edges(const igraph_t *graph,
                                     igraph_t *newgraph,
                                     const igraph_vector_t *idx) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->permute_edges(graph, newgraph, idx);
    } else {
        return 0;
    }
}

int igraph_i_attribute_combine_edges(const igraph_t *graph,
                                     igraph_t *newgraph,
                                     const igraph_vector_ptr_t *merges,
                                     const igraph_attribute_combination_t *comb) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->combine_edges(graph, newgraph,
                merges,
                comb);
    } else {
        return 0;
    }
}

int igraph_i_attribute_get_info(const igraph_t *graph,
                                igraph_strvector_t *gnames,
                                igraph_vector_t *gtypes,
                                igraph_strvector_t *vnames,
                                igraph_vector_t *vtypes,
                                igraph_strvector_t *enames,
                                igraph_vector_t *etypes) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->get_info(graph, gnames, gtypes,
                vnames, vtypes,
                enames, etypes);
    } else {
        return 0;
    }
}

igraph_bool_t igraph_i_attribute_has_attr(const igraph_t *graph,
        igraph_attribute_elemtype_t type,
        const char *name) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->has_attr(graph, type, name);
    } else {
        return 0;
    }
}

int igraph_i_attribute_gettype(const igraph_t *graph,
                               igraph_attribute_type_t *type,
                               igraph_attribute_elemtype_t elemtype,
                               const char *name) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->gettype(graph, type, elemtype, name);
    } else {
        return 0;
    }

}

int igraph_i_attribute_get_numeric_graph_attr(const igraph_t *graph,
        const char *name,
        igraph_vector_t *value) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->get_numeric_graph_attr(graph, name, value);
    } else {
        return 0;
    }
}

int igraph_i_attribute_get_numeric_vertex_attr(const igraph_t *graph,
        const char *name,
        igraph_vs_t vs,
        igraph_vector_t *value) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->get_numeric_vertex_attr(graph, name, vs, value);
    } else {
        return 0;
    }
}

int igraph_i_attribute_get_numeric_edge_attr(const igraph_t *graph,
        const char *name,
        igraph_es_t es,
        igraph_vector_t *value) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->get_numeric_edge_attr(graph, name, es, value);
    } else {
        return 0;
    }
}

int igraph_i_attribute_get_string_graph_attr(const igraph_t *graph,
        const char *name,
        igraph_strvector_t *value) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->get_string_graph_attr(graph, name, value);
    } else {
        return 0;
    }
}

int igraph_i_attribute_get_string_vertex_attr(const igraph_t *graph,
        const char *name,
        igraph_vs_t vs,
        igraph_strvector_t *value) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->get_string_vertex_attr(graph, name, vs, value);
    } else {
        return 0;
    }
}

int igraph_i_attribute_get_string_edge_attr(const igraph_t *graph,
        const char *name,
        igraph_es_t es,
        igraph_strvector_t *value) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->get_string_edge_attr(graph, name, es, value);
    } else {
        return 0;
    }
}

int igraph_i_attribute_get_bool_graph_attr(const igraph_t *graph,
        const char *name,
        igraph_vector_bool_t *value) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->get_bool_graph_attr(graph, name, value);
    } else {
        return 0;
    }
}

int igraph_i_attribute_get_bool_vertex_attr(const igraph_t *graph,
        const char *name,
        igraph_vs_t vs,
        igraph_vector_bool_t *value) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->get_bool_vertex_attr(graph, name, vs, value);
    } else {
        return 0;
    }
}

int igraph_i_attribute_get_bool_edge_attr(const igraph_t *graph,
        const char *name,
        igraph_es_t es,
        igraph_vector_bool_t *value) {
    if (igraph_i_attribute_table) {
        return igraph_i_attribute_table->get_bool_edge_attr(graph, name, es, value);
    } else {
        return 0;
    }
}

/**
 * \function igraph_i_set_attribute_table
 * \brief Attach an attribute table.
 *
 * This function attaches attribute handling code to the igraph library.
 * Note that the attribute handler table is \em not thread-local even if
 * igraph is compiled in thread-local mode. In the vast majority of cases,
 * this is not a significant restriction.
 *
 * \param table Pointer to an \ref igraph_attribute_table_t object
 *    containing the functions for attribute manipulation. Supply \c
 *    NULL here if you don't want attributes.
 * \return Pointer to the old attribute handling table.
 *
 * Time complexity: O(1).
 */

igraph_attribute_table_t *
igraph_i_set_attribute_table(const igraph_attribute_table_t * table) {
    igraph_attribute_table_t *old = igraph_i_attribute_table;
    igraph_i_attribute_table = (igraph_attribute_table_t*) table;
    return old;
}

igraph_bool_t igraph_has_attribute_table() {
    return igraph_i_attribute_table != 0;
}

int igraph_attribute_combination_init(igraph_attribute_combination_t *comb) {
    IGRAPH_CHECK(igraph_vector_ptr_init(&comb->list, 0));
    return 0;
}

void igraph_attribute_combination_destroy(igraph_attribute_combination_t *comb) {
    long int i, n = igraph_vector_ptr_size(&comb->list);
    for (i = 0; i < n; i++) {
        igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[i];
        if (rec->name) {
            igraph_Free(rec->name);
        }
        igraph_Free(rec);
    }
    igraph_vector_ptr_destroy(&comb->list);
}

int igraph_attribute_combination_add(igraph_attribute_combination_t *comb,
                                     const char *name,
                                     igraph_attribute_combination_type_t type,
                                     igraph_function_pointer_t func) {
    long int i, n = igraph_vector_ptr_size(&comb->list);

    /* Search, in case it is already there */
    for (i = 0; i < n; i++) {
        igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i];
        const char *n = r->name;
        if ( (!name && !n) ||
             (name && n && !strcmp(n, name)) ) {
            r->type = type;
            r->func = func;
            break;
        }
    }

    if (i == n) {
        /* This is a new attribute name */
        igraph_attribute_combination_record_t *rec =
            igraph_Calloc(1, igraph_attribute_combination_record_t);

        if (!rec) {
            IGRAPH_ERROR("Cannot create attribute combination data",
                         IGRAPH_ENOMEM);
        }
        if (!name) {
            rec->name = 0;
        } else {
            rec->name = strdup(name);
        }
        rec->type = type;
        rec->func = func;

        IGRAPH_CHECK(igraph_vector_ptr_push_back(&comb->list, rec));

    }

    return 0;
}

int igraph_attribute_combination_remove(igraph_attribute_combination_t *comb,
                                        const char *name) {
    long int i, n = igraph_vector_ptr_size(&comb->list);

    /* Search, in case it is already there */
    for (i = 0; i < n; i++) {
        igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i];
        const char *n = r->name;
        if ( (!name && !n) ||
             (name && n && !strcmp(n, name)) ) {
            break;
        }
    }

    if (i != n) {
        igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i];
        if (r->name) {
            igraph_Free(r->name);
        }
        igraph_Free(r);
        igraph_vector_ptr_remove(&comb->list, i);
    } else {
        /* It is not there, we don't do anything */
    }

    return 0;
}

int igraph_attribute_combination_query(const igraph_attribute_combination_t *comb,
                                       const char *name,
                                       igraph_attribute_combination_type_t *type,
                                       igraph_function_pointer_t *func) {
    long int i, def = -1, len = igraph_vector_ptr_size(&comb->list);

    for (i = 0; i < len; i++) {
        igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[i];
        const char *n = rec->name;
        if ( (!name && !n) ||
             (name && n && !strcmp(n, name)) ) {
            *type = rec->type;
            *func = rec->func;
            return 0;
        }
        if (!n) {
            def = i;
        }
    }

    if (def == -1) {
        /* Did not find anything */
        *type = IGRAPH_ATTRIBUTE_COMBINE_DEFAULT;
        *func = 0;
    } else {
        igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[def];
        *type = rec->type;
        *func = rec->func;
    }

    return 0;
}

int igraph_attribute_combination(igraph_attribute_combination_t *comb, ...) {

    va_list ap;

    IGRAPH_CHECK(igraph_attribute_combination_init(comb));

    va_start(ap, comb);
    while (1) {
        igraph_function_pointer_t func = 0;
        igraph_attribute_combination_type_t type;
        const char *name;

        name = va_arg(ap, const char *);

        if (name == IGRAPH_NO_MORE_ATTRIBUTES) {
            break;
        }

        type = (igraph_attribute_combination_type_t)va_arg(ap, int);
        if (type == IGRAPH_ATTRIBUTE_COMBINE_FUNCTION) {
#if defined(__GNUC__)
            func = va_arg(ap, void (*)(void));
#else
            func = va_arg(ap, void*);
#endif
        }

        if (strlen(name) == 0) {
            name = 0;
        }

        IGRAPH_CHECK(igraph_attribute_combination_add(comb, name, type, func));
    }

    va_end(ap);

    return 0;
}
