/*
 *      CRFsuite library.
 *
 * Copyright (c) 2007-2010, Naoaki Okazaki
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the names of the authors nor the names of its contributors
 *       may be used to endorse or promote products derived from this
 *       software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* $Id$ */

#include <os.h>

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

#include <crfsuite.h>
#include "logging.h"

int crf1de_create_instance(const char *iid, void **ptr);
int crfsuite_dictionary_create_instance(const char *interface, void **ptr);
int crf1m_create_instance_from_file(const char *filename, void **ptr);
int crf1m_create_instance_from_memory(const void *data, size_t size, void **ptr);

int crfsuite_create_instance(const char *iid, void **ptr)
{
    int ret = 
        crf1de_create_instance(iid, ptr) == 0 ||
        crfsuite_dictionary_create_instance(iid, ptr) == 0;

    return ret;
}

int crfsuite_create_instance_from_file(const char *filename, void **ptr)
{
    int ret = crf1m_create_instance_from_file(filename, ptr);
    return ret;
}

int crfsuite_create_instance_from_memory(const void *data, size_t size, void **ptr)
{
    int ret = crf1m_create_instance_from_memory(data, size, ptr);
    return ret;
}


void crfsuite_attribute_init(crfsuite_attribute_t* cont)
{
    memset(cont, 0, sizeof(*cont));
    cont->value = 1;
}

void crfsuite_attribute_set(crfsuite_attribute_t* cont, int aid, floatval_t value)
{
    crfsuite_attribute_init(cont);
    cont->aid = aid;
    cont->value = value;
}

void crfsuite_attribute_copy(crfsuite_attribute_t* dst, const crfsuite_attribute_t* src)
{
    dst->aid = src->aid;
    dst->value = src->value;
}

void crfsuite_attribute_swap(crfsuite_attribute_t* x, crfsuite_attribute_t* y)
{
    crfsuite_attribute_t tmp = *x;
    x->aid = y->aid;
    x->value = y->value;
    y->aid = tmp.aid;
    y->value = tmp.value;
}



void crfsuite_item_init(crfsuite_item_t* item)
{
    memset(item, 0, sizeof(*item));
}

void crfsuite_item_init_n(crfsuite_item_t* item, int num_contents)
{
    crfsuite_item_init(item);
    item->num_contents = num_contents;
    item->cap_contents = num_contents;
    item->contents = (crfsuite_attribute_t*)calloc(num_contents, sizeof(crfsuite_attribute_t));
}

void crfsuite_item_finish(crfsuite_item_t* item)
{
    free(item->contents);
    crfsuite_item_init(item);
}

void crfsuite_item_copy(crfsuite_item_t* dst, const crfsuite_item_t* src)
{
    int i;

    dst->num_contents = src->num_contents;
    dst->cap_contents = src->cap_contents;
    dst->contents = (crfsuite_attribute_t*)calloc(dst->num_contents, sizeof(crfsuite_attribute_t));
    for (i = 0;i < dst->num_contents;++i) {
        crfsuite_attribute_copy(&dst->contents[i], &src->contents[i]);
    }
}

void crfsuite_item_swap(crfsuite_item_t* x, crfsuite_item_t* y)
{
    crfsuite_item_t tmp = *x;
    x->num_contents = y->num_contents;
    x->cap_contents = y->cap_contents;
    x->contents = y->contents;
    y->num_contents = tmp.num_contents;
    y->cap_contents = tmp.cap_contents;
    y->contents = tmp.contents;
}

int crfsuite_item_append_attribute(crfsuite_item_t* item, const crfsuite_attribute_t* cont)
{
    if (item->cap_contents <= item->num_contents) {
        item->cap_contents = (item->cap_contents + 1) * 2;
        item->contents = (crfsuite_attribute_t*)realloc(
            item->contents, sizeof(crfsuite_attribute_t) * item->cap_contents);
    }
    crfsuite_attribute_copy(&item->contents[item->num_contents++], cont);
    return 0;
}

int  crfsuite_item_empty(crfsuite_item_t* item)
{
    return (item->num_contents == 0);
}




void crfsuite_instance_init(crfsuite_instance_t* inst)
{
    memset(inst, 0, sizeof(*inst));
    inst->weight = 1.;
}

void crfsuite_instance_init_n(crfsuite_instance_t* inst, int num_items)
{
    crfsuite_instance_init(inst);
    inst->num_items = num_items;
    inst->cap_items = num_items;
    inst->items = (crfsuite_item_t*)calloc(num_items, sizeof(crfsuite_item_t));
    inst->labels = (int*)calloc(num_items, sizeof(int));
}

void crfsuite_instance_finish(crfsuite_instance_t* inst)
{
    int i;

    for (i = 0;i < inst->num_items;++i) {
        crfsuite_item_finish(&inst->items[i]);
    }
    free(inst->labels);
    free(inst->items);
    crfsuite_instance_init(inst);
}

void crfsuite_instance_copy(crfsuite_instance_t* dst, const crfsuite_instance_t* src)
{
    int i;

    dst->num_items = src->num_items;
    dst->cap_items = src->cap_items;
    dst->items = (crfsuite_item_t*)calloc(dst->num_items, sizeof(crfsuite_item_t));
    dst->labels = (int*)calloc(dst->num_items, sizeof(int));
    dst->weight = src->weight;
    dst->group = src->group;
    for (i = 0;i < dst->num_items;++i) {
        crfsuite_item_copy(&dst->items[i], &src->items[i]);
        dst->labels[i] = src->labels[i];
    }
}

void crfsuite_instance_swap(crfsuite_instance_t* x, crfsuite_instance_t* y)
{
    crfsuite_instance_t tmp = *x;
    x->num_items = y->num_items;
    x->cap_items = y->cap_items;
    x->items = y->items;
    x->labels = y->labels;
    x->weight = y->weight;
    x->group = y->group;
    y->num_items = tmp.num_items;
    y->cap_items = tmp.cap_items;
    y->items = tmp.items;
    y->labels = tmp.labels;
    y->weight = tmp.weight;
    y->group = tmp.group;
}

int crfsuite_instance_append(crfsuite_instance_t* inst, const crfsuite_item_t* item, int label)
{
    if (inst->cap_items <= inst->num_items) {
        inst->cap_items = (inst->cap_items + 1) * 2;
        inst->items = (crfsuite_item_t*)realloc(inst->items, sizeof(crfsuite_item_t) * inst->cap_items);
        inst->labels = (int*)realloc(inst->labels, sizeof(int) * inst->cap_items);
    }
    crfsuite_item_copy(&inst->items[inst->num_items], item);
    inst->labels[inst->num_items] = label;
    ++inst->num_items;
    return 0;
}

int  crfsuite_instance_empty(crfsuite_instance_t* inst)
{
    return (inst->num_items == 0);
}




void crfsuite_data_init(crfsuite_data_t* data)
{
    memset(data, 0, sizeof(*data));
}

void crfsuite_data_init_n(crfsuite_data_t* data, int n)
{
    crfsuite_data_init(data);
    data->num_instances = n;
    data->cap_instances = n;
    data->instances = (crfsuite_instance_t*)calloc(n, sizeof(crfsuite_instance_t));
}

void crfsuite_data_finish(crfsuite_data_t* data)
{
    int i;

    for (i = 0;i < data->num_instances;++i) {
        crfsuite_instance_finish(&data->instances[i]);
    }
    free(data->instances);
    crfsuite_data_init(data);
}

void crfsuite_data_copy(crfsuite_data_t* dst, const crfsuite_data_t* src)
{
    int i;

    dst->num_instances = src->num_instances;
    dst->cap_instances = src->cap_instances;
    dst->instances = (crfsuite_instance_t*)calloc(dst->num_instances, sizeof(crfsuite_instance_t));
    for (i = 0;i < dst->num_instances;++i) {
        crfsuite_instance_copy(&dst->instances[i], &src->instances[i]);
    }
}

void crfsuite_data_swap(crfsuite_data_t* x, crfsuite_data_t* y)
{
    crfsuite_data_t tmp = *x;
    x->num_instances = y->num_instances;
    x->cap_instances = y->cap_instances;
    x->instances = y->instances;
    y->num_instances = tmp.num_instances;
    y->cap_instances = tmp.cap_instances;
    y->instances = tmp.instances;
}

int  crfsuite_data_append(crfsuite_data_t* data, const crfsuite_instance_t* inst)
{
    if (0 < inst->num_items) {
        if (data->cap_instances <= data->num_instances) {
            data->cap_instances = (data->cap_instances + 1) * 2;
            data->instances = (crfsuite_instance_t*)realloc(
                data->instances, sizeof(crfsuite_instance_t) * data->cap_instances);
        }
        crfsuite_instance_copy(&data->instances[data->num_instances++], inst);
    }
    return 0;
}

int crfsuite_data_maxlength(crfsuite_data_t* data)
{
    int i, T = 0;
    for (i = 0;i < data->num_instances;++i) {
        if (T < data->instances[i].num_items) {
            T = data->instances[i].num_items;
        }
    }
    return T;
}

int  crfsuite_data_totalitems(crfsuite_data_t* data)
{
    int i, n = 0;
    for (i = 0;i < data->num_instances;++i) {
        n += data->instances[i].num_items;
    }
    return n;
}

static char *safe_strncpy(char *dst, const char *src, size_t n)
{
    strncpy(dst, src, n-1);
    dst[n-1] = 0;
    return dst;
}

void crfsuite_evaluation_init(crfsuite_evaluation_t* eval, int n)
{
    memset(eval, 0, sizeof(*eval));
    eval->tbl = (crfsuite_label_evaluation_t*)calloc(n+1, sizeof(crfsuite_label_evaluation_t));
    if (eval->tbl != NULL) {
        eval->num_labels = n;
    }
}

void crfsuite_evaluation_clear(crfsuite_evaluation_t* eval)
{
    int i;
    for (i = 0;i <= eval->num_labels;++i) {
        memset(&eval->tbl[i], 0, sizeof(eval->tbl[i]));
    }

    eval->item_total_correct = 0;
    eval->item_total_num = 0;
    eval->item_total_model = 0;
    eval->item_total_observation = 0;
    eval->item_accuracy = 0;

    eval->inst_total_correct = 0;
    eval->inst_total_num = 0;
    eval->inst_accuracy = 0;

    eval->macro_precision = 0;
    eval->macro_recall = 0;
    eval->macro_fmeasure = 0;
}

void crfsuite_evaluation_finish(crfsuite_evaluation_t* eval)
{
    free(eval->tbl);
    memset(eval, 0, sizeof(*eval));
}

int crfsuite_evaluation_accmulate(crfsuite_evaluation_t* eval, const int* reference, const int* prediction, int T)
{
    int t, nc = 0;

    for (t = 0;t < T;++t) {
        int lr = reference[t];
        int lt = prediction[t];

        if (eval->num_labels <= lr || eval->num_labels <= lt) {
            return 1;
        }

        ++eval->tbl[lr].num_observation;
        ++eval->tbl[lt].num_model;
        if (lr == lt) {
            ++eval->tbl[lr].num_correct;
            ++nc;
        }
        ++eval->item_total_num;
    }

    if (nc == T) {
        ++eval->inst_total_correct;
    }
    ++eval->inst_total_num;

    return 0;
}

void crfsuite_evaluation_finalize(crfsuite_evaluation_t* eval)
{
    int i;

    for (i = 0;i <= eval->num_labels;++i) {
        crfsuite_label_evaluation_t* lev = &eval->tbl[i];

        /* Do not evaluate labels that does not in the test data. */
        if (lev->num_observation == 0) {
            continue;
        }

        /* Sum the number of correct labels for accuracy calculation. */
        eval->item_total_correct += lev->num_correct;
        eval->item_total_model += lev->num_model;
        eval->item_total_observation += lev->num_observation;

        /* Initialize the precision, recall, and f1-measure values. */
        lev->precision = 0;
        lev->recall = 0;
        lev->fmeasure = 0;

        /* Compute the precision, recall, and f1-measure values. */
        if (lev->num_model > 0) {
            lev->precision = lev->num_correct / (double)lev->num_model;
        }
        if (lev->num_observation > 0) {
            lev->recall = lev->num_correct / (double)lev->num_observation;
        }
        if (lev->precision + lev->recall > 0) {
            lev->fmeasure = lev->precision * lev->recall * 2 / (lev->precision + lev->recall);
        }

        /* Exclude unknown labels from calculation of macro-average values. */
        if (i != eval->num_labels) {
            eval->macro_precision += lev->precision;
            eval->macro_recall += lev->recall;
            eval->macro_fmeasure += lev->fmeasure;
        }
    }

    /* Copute the macro precision, recall, and f1-measure values. */
    eval->macro_precision /= eval->num_labels;
    eval->macro_recall /= eval->num_labels;
    eval->macro_fmeasure /= eval->num_labels;

    /* Compute the item accuracy. */
    eval->item_accuracy = 0;
    if (0 < eval->item_total_num) {
        eval->item_accuracy = eval->item_total_correct / (double)eval->item_total_num;
    }

    /* Compute the instance accuracy. */
    eval->inst_accuracy = 0;
    if (0 < eval->inst_total_num) {
        eval->inst_accuracy = eval->inst_total_correct / (double)eval->inst_total_num;
    }
}

void crfsuite_evaluation_output(crfsuite_evaluation_t* eval, crfsuite_dictionary_t* labels, crfsuite_logging_callback cbm, void *instance)
{
    int i;
    const char *lstr = NULL;
    logging_t lg;

    lg.func = cbm;
    lg.instance = instance;

    logging(&lg, "Performance by label (#match, #model, #ref) (precision, recall, F1):\n");

    for (i = 0;i < eval->num_labels;++i) {
        const crfsuite_label_evaluation_t* lev = &eval->tbl[i];

        labels->to_string(labels, i, &lstr);
        if (lstr == NULL) lstr = "[UNKNOWN]";

        if (lev->num_observation == 0) {
            logging(&lg, "    %s: (%d, %d, %d) (******, ******, ******)\n",
                lstr, lev->num_correct, lev->num_model, lev->num_observation
                );
        } else {
            logging(&lg, "    %s: (%d, %d, %d) (%1.4f, %1.4f, %1.4f)\n",
                lstr, lev->num_correct, lev->num_model, lev->num_observation,
                lev->precision, lev->recall, lev->fmeasure
                );
        }
        labels->free(labels, lstr);
    }
    logging(&lg, "Macro-average precision, recall, F1: (%f, %f, %f)\n",
        eval->macro_precision, eval->macro_recall, eval->macro_fmeasure
        );
    logging(&lg, "Item accuracy: %d / %d (%1.4f)\n",
        eval->item_total_correct, eval->item_total_num, eval->item_accuracy
        );
    logging(&lg, "Instance accuracy: %d / %d (%1.4f)\n",
        eval->inst_total_correct, eval->inst_total_num, eval->inst_accuracy
        );
}

int crfsuite_interlocked_increment(int *count)
{
    return ++(*count);
}

int crfsuite_interlocked_decrement(int *count)
{
    return --(*count);
}
