/**
 * @file
 *
 * @author jeffrey.daily@gmail.com
 *
 * Copyright (c) 2015 Battelle Memorial Institute.
 */
#include "config.h"

#include <assert.h>
#include <ctype.h>
#include <limits.h>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "parasail.h"
#include "parasail/memory.h"

void* parasail_memalign(size_t alignment, size_t size)
{
    void *ptr = NULL;
#if defined(HAVE__ALIGNED_MALLOC)
    ptr = _aligned_malloc(size, alignment);
#elif defined(HAVE_POSIX_MEMALIGN)
    int retcode = posix_memalign(&ptr, alignment, size);
    assert(0 == retcode);
#elif defined(HAVE_ALIGNED_ALLOC)
    ptr = aligned_alloc(alignment, size);
#elif defined(HAVE_MEMALIGN)
    ptr = memalign(alignment, size);
#else
#error "No suitable memory alignment routine found."
#endif
    assert(NULL != ptr);
    return ptr;
}

void parasail_free(void *ptr)
{
#if defined(HAVE__ALIGNED_MALLOC)
     _aligned_free(ptr);
#else
    free(ptr);
#endif
}

void parasail_free_unaligned(void *ptr)
{
    free(ptr);
}

int * parasail_memalign_int(size_t alignment, size_t size)
{
    return (int *) parasail_memalign(alignment, size*sizeof(int));
}

int8_t * parasail_memalign_int8_t(size_t alignment, size_t size)
{
    return (int8_t *) parasail_memalign(alignment, size*sizeof(int8_t));
}

int16_t * parasail_memalign_int16_t(size_t alignment, size_t size)
{
    return (int16_t *) parasail_memalign(alignment, size*sizeof(int16_t));
}

int32_t * parasail_memalign_int32_t(size_t alignment, size_t size)
{
    return (int32_t *) parasail_memalign(alignment, size*sizeof(int32_t));
}

int64_t * parasail_memalign_int64_t(size_t alignment, size_t size)
{
    return (int64_t *) parasail_memalign(alignment, size*sizeof(int64_t));
}

void parasail_memset(void *b, int c, size_t len)
{
    (void)memset(b, c, len);
}

void parasail_memset_int(int *b, int c, size_t len)
{
    size_t i;
    for (i=0; i<len; ++i) {
        b[i] = c;
    }
}

void parasail_memset_int8_t(int8_t *b, int8_t c, size_t len)
{
    size_t i;
    for (i=0; i<len; ++i) {
        b[i] = c;
    }
}

void parasail_memset_int16_t(int16_t *b, int16_t c, size_t len)
{
    size_t i;
    for (i=0; i<len; ++i) {
        b[i] = c;
    }
}

void parasail_memset_int32_t(int32_t *b, int32_t c, size_t len)
{
    size_t i;
    for (i=0; i<len; ++i) {
        b[i] = c;
    }
}

void parasail_memset_int64_t(int64_t *b, int64_t c, size_t len)
{
    size_t i;
    for (i=0; i<len; ++i) {
        b[i] = c;
    }
}

parasail_result_t* parasail_result_new()
{
    /* declare all variables */
    parasail_result_t *result = NULL;

    result = (parasail_result_t*)malloc(sizeof(parasail_result_t));
    assert(result);

    result->score = 0;
    result->end_query = 0;
    result->end_ref = 0;
    result->flag = 0;
    result->extra = NULL;

    return result;
}

parasail_result_t* parasail_result_new_stats()
{
    /* declare all variables */
    parasail_result_t *result = NULL;

    /* allocate struct to hold memory */
    result = parasail_result_new();

    /* allocate only tables */
    result->stats = (parasail_result_extra_stats_t*)
        malloc(sizeof(parasail_result_extra_stats_t));
    assert(result->stats);

    return result;
}

parasail_result_t* parasail_result_new_table1(const int a, const int b)
{
    /* declare all variables */
    parasail_result_t *result = NULL;

    /* validate inputs */
    assert(a > 0);
    assert(b > 0);
    
    /* allocate struct to hold memory */
    result = parasail_result_new();

    /* allocate only score table */
    result->tables = (parasail_result_extra_tables_t*)malloc(sizeof(parasail_result_extra_tables_t));
    assert(result->tables);
    result->tables->score_table = (int *)malloc(sizeof(int)*a*b);
    assert(result->tables->score_table);

    return result;
}

parasail_result_t* parasail_result_new_rowcol1(const int a, const int b)
{
    /* declare all variables */
    parasail_result_t *result = NULL;

    /* validate inputs */
    assert(a > 0);
    assert(b > 0);
    
    /* allocate struct to hold memory */
    result = parasail_result_new();

    /* allocate only score col and row */
    result->rowcols = (parasail_result_extra_rowcols_t*)malloc(sizeof(parasail_result_extra_rowcols_t));
    assert(result->rowcols);
    result->rowcols->score_row = (int *)malloc(sizeof(int)*b);
    assert(result->rowcols->score_row);
    result->rowcols->score_col = (int *)malloc(sizeof(int)*a);
    assert(result->rowcols->score_col);

    return result;
}

parasail_result_t* parasail_result_new_table3(const int a, const int b)
{
    /* declare all variables */
    parasail_result_t *result = NULL;

    /* validate inputs */
    assert(a > 0);
    assert(b > 0);
    
    /* allocate struct to hold memory */
    result = parasail_result_new();
    
    /* allocate only tables */
    result->stats = (parasail_result_extra_stats_t*)
        malloc(sizeof(parasail_result_extra_stats_t));
    assert(result->stats);

    result->stats->tables = (parasail_result_extra_stats_tables_t*)
        malloc(sizeof(parasail_result_extra_stats_tables_t));
    assert(result->stats->tables);

    result->stats->tables->score_table = (int *)malloc(sizeof(int)*a*b);
    assert(result->stats->tables->score_table);
    result->stats->tables->matches_table = (int *)malloc(sizeof(int)*a*b);
    assert(result->stats->tables->matches_table);
    result->stats->tables->similar_table = (int *)malloc(sizeof(int)*a*b);
    assert(result->stats->tables->similar_table);
    result->stats->tables->length_table = (int *)malloc(sizeof(int)*a*b);
    assert(result->stats->tables->length_table);

    return result;
}

parasail_result_t* parasail_result_new_trace(const int a, const int b, const size_t alignment, const size_t size)
{
    /* declare all variables */
    parasail_result_t *result = NULL;

    /* validate inputs */
    assert(a > 0);
    assert(b > 0);

    /* allocate struct to hold memory */
    result = parasail_result_new();

    result->trace = (parasail_result_extra_trace_t*)malloc(sizeof(parasail_result_extra_trace_t));
    assert(result->trace);
    result->trace->trace_table = parasail_memalign(alignment, size*a*b);
    assert(result->trace->trace_table);
    result->trace->trace_ins_table = NULL;
    result->trace->trace_del_table = NULL;

    return result;
}

parasail_result_t* parasail_result_new_rowcol3(const int a, const int b)
{
    /* declare all variables */
    parasail_result_t *result = NULL;

    /* validate inputs */
    assert(a > 0);
    assert(b > 0);
    
    /* allocate struct to hold memory */
    result = parasail_result_new();
    
    result->stats = (parasail_result_extra_stats_t*)
        malloc(sizeof(parasail_result_extra_stats_t));
    assert(result->stats);

    result->stats->rowcols = (parasail_result_extra_stats_rowcols_t*)
        malloc(sizeof(parasail_result_extra_stats_rowcols_t));
    assert(result->stats->rowcols);

    result->stats->rowcols->score_row = (int *)malloc(sizeof(int)*b);
    assert(result->stats->rowcols->score_row);
    result->stats->rowcols->matches_row = (int *)malloc(sizeof(int)*b);
    assert(result->stats->rowcols->matches_row);
    result->stats->rowcols->similar_row = (int *)malloc(sizeof(int)*b);
    assert(result->stats->rowcols->similar_row);
    result->stats->rowcols->length_row = (int *)malloc(sizeof(int)*b);
    assert(result->stats->rowcols->length_row);

    result->stats->rowcols->score_col = (int *)malloc(sizeof(int)*a);
    assert(result->stats->rowcols->score_col);
    result->stats->rowcols->matches_col = (int *)malloc(sizeof(int)*a);
    assert(result->stats->rowcols->matches_col);
    result->stats->rowcols->similar_col = (int *)malloc(sizeof(int)*a);
    assert(result->stats->rowcols->similar_col);
    result->stats->rowcols->length_col = (int *)malloc(sizeof(int)*a);
    assert(result->stats->rowcols->length_col);

    return result;
}

void parasail_result_free(parasail_result_t *result)
{
    /* validate inputs */
    assert(NULL != result);
    
    if (result->flag & PARASAIL_FLAG_STATS) {
        if (result->flag & PARASAIL_FLAG_TABLE) {
            free(result->stats->tables->score_table);
            free(result->stats->tables->matches_table);
            free(result->stats->tables->similar_table);
            free(result->stats->tables->length_table);
            free(result->stats->tables);
        }
        if (result->flag & PARASAIL_FLAG_ROWCOL) {
            free(result->stats->rowcols->score_row);
            free(result->stats->rowcols->matches_row);
            free(result->stats->rowcols->similar_row);
            free(result->stats->rowcols->length_row);
            free(result->stats->rowcols->score_col);
            free(result->stats->rowcols->matches_col);
            free(result->stats->rowcols->similar_col);
            free(result->stats->rowcols->length_col);
            free(result->stats->rowcols);
        }
        free(result->stats);
    }
    else {
        if (result->flag & PARASAIL_FLAG_TABLE) {
            free(result->tables->score_table);
            free(result->tables);
        }
        if (result->flag & PARASAIL_FLAG_ROWCOL) {
            free(result->rowcols->score_row);
            free(result->rowcols->score_col);
            free(result->rowcols);
        }
    }
    if (result->flag & PARASAIL_FLAG_TRACE) {
        parasail_free(result->trace->trace_table);
        if (NULL != result->trace->trace_ins_table)
            parasail_free(result->trace->trace_ins_table);
        if (NULL != result->trace->trace_del_table)
            parasail_free(result->trace->trace_del_table);
        free(result->trace);
    }

    free(result);
}

void parasail_version(int *major, int *minor, int *patch)
{
    *major = PARASAIL_VERSION_MAJOR;
    *minor = PARASAIL_VERSION_MINOR;
    *patch = PARASAIL_VERSION_PATCH;
}

static parasail_matrix_t* parasail_matrix_create_internal(
        const char *alphabet, const int match, const int mismatch, int case_sensitive)
{
    parasail_matrix_t *retval = NULL;
    int *matrix = NULL;
    int *mapper = NULL;
    size_t size = 0;
    size_t size1 = 0;
    size_t i = 0;
    size_t j = 0;
    size_t c = 0;

    size = strlen(alphabet);
    assert(size < INT_MAX);
    size1 = size + 1;

    matrix = (int*)malloc(sizeof(int)*size1*size1);
    assert(matrix);
    for (i=0; i<size; ++i) {
        for (j=0; j<size; ++j) {
            if (i == j) {
                matrix[c++] = match;
            }
            else {
                matrix[c++] = mismatch;
            }
        }
        matrix[c++] = 0;
    }
    for (j=0; j<size1; ++j) {
        matrix[c++] = 0;
    }

    mapper = (int*)malloc(sizeof(int)*256);
    assert(mapper);
    parasail_memset_int(mapper, (int)size, 256);
    if (case_sensitive) {
        for (i=0; i<size; ++i) {
            mapper[(unsigned char)alphabet[i]] = (int)i;
        }
    }
    else {
        for (i=0; i<size; ++i) {
            mapper[toupper((unsigned char)alphabet[i])] = (int)i;
            mapper[tolower((unsigned char)alphabet[i])] = (int)i;
        }
    }

    retval = (parasail_matrix_t*)malloc(sizeof(parasail_matrix_t));
    assert(retval);
    retval->name = "";
    retval->matrix = matrix;
    retval->mapper = mapper;
    retval->size = (int)size1;
    retval->max = match > mismatch ? match : mismatch;
    retval->min = match > mismatch ? mismatch : match;
    retval->user_matrix = matrix;
    return retval;
}

parasail_matrix_t* parasail_matrix_create(
        const char *alphabet, const int match, const int mismatch)
{
    return parasail_matrix_create_internal(alphabet, match, mismatch, 0);
}

parasail_matrix_t* parasail_matrix_create_case_sensitive(
        const char *alphabet, const int match, const int mismatch)
{
    return parasail_matrix_create_internal(alphabet, match, mismatch, 1);
}

parasail_matrix_t* parasail_matrix_copy(const parasail_matrix_t *original)
{
    parasail_matrix_t *retval = NULL;

    retval = (parasail_matrix_t*)malloc(sizeof(parasail_matrix_t));
    assert(retval);
    retval->name = original->name;
    retval->size = original->size;
    retval->max = original->max;
    retval->min = original->min;

    {
        size_t matrix_size = sizeof(int)*original->size*original->size;
        size_t mapper_size = sizeof(int)*256;
        int *new_mapper = NULL;
        int *new_matrix = NULL;

        new_mapper = (int*)malloc(mapper_size);
        assert(new_mapper);
        (void)memcpy(new_mapper, original->mapper, mapper_size);

        new_matrix = (int*)malloc(matrix_size);
        assert(new_matrix);
        (void)memcpy(new_matrix, original->matrix, matrix_size);

        retval->mapper = new_mapper;
        retval->matrix = new_matrix;
        retval->user_matrix = new_matrix;
    }

    return retval;
}

void parasail_matrix_set_value(parasail_matrix_t *matrix, int row, int col, int value)
{
    assert(matrix);

    if (NULL == matrix->user_matrix) {
        fprintf(stderr, "attempted to set value of built-in matrix '%s'\n",
                matrix->name);
        return;
    }

    matrix->user_matrix[row*matrix->size + col] = value;
    if (value > matrix->max) {
        matrix->max = value;
    }
    if (value < matrix->min) {
        matrix->min = value;
    }
}

void parasail_matrix_free(parasail_matrix_t *matrix)
{
    /* validate inputs */
    assert(NULL != matrix);
    if (NULL != matrix->user_matrix) {
        free((void*)matrix->matrix);
        free((void*)matrix->mapper);
        free(matrix);
    }
    else {
        fprintf(stderr, "attempted to free built-in matrix '%s'\n",
                matrix->name);
    }
}

parasail_profile_t* parasail_profile_new(
        const char * s1, const int s1Len, const parasail_matrix_t *matrix)
{
    /* declare all variables */
    parasail_profile_t *profile = NULL;

    profile = (parasail_profile_t*)malloc(sizeof(parasail_profile_t));
    assert(profile);

    profile->s1 = s1;
    profile->s1Len = s1Len;
    profile->matrix = matrix;
    profile->profile8.score = NULL;
    profile->profile8.matches = NULL;
    profile->profile8.similar = NULL;
    profile->profile16.score = NULL;
    profile->profile16.matches = NULL;
    profile->profile16.similar = NULL;
    profile->profile32.score = NULL;
    profile->profile32.matches = NULL;
    profile->profile32.similar = NULL;
    profile->profile64.score = NULL;
    profile->profile64.matches = NULL;
    profile->profile64.similar = NULL;
    profile->free = NULL;
    profile->stop = INT32_MAX;

    return profile;
}

void parasail_profile_free(parasail_profile_t *profile)
{
    if (NULL != profile->profile8.score) {
        profile->free(profile->profile8.score);
    }
    if (NULL != profile->profile8.matches) {
        profile->free(profile->profile8.matches);
    }
    if (NULL != profile->profile8.similar) {
        profile->free(profile->profile8.similar);
    }

    if (NULL != profile->profile16.score) {
        profile->free(profile->profile16.score);
    }
    if (NULL != profile->profile16.matches) {
        profile->free(profile->profile16.matches);
    }
    if (NULL != profile->profile16.similar) {
        profile->free(profile->profile16.similar);
    }

    if (NULL != profile->profile32.score) {
        profile->free(profile->profile32.score);
    }
    if (NULL != profile->profile32.matches) {
        profile->free(profile->profile32.matches);
    }
    if (NULL != profile->profile32.similar) {
        profile->free(profile->profile32.similar);
    }

    if (NULL != profile->profile64.score) {
        profile->free(profile->profile64.score);
    }
    if (NULL != profile->profile64.matches) {
        profile->free(profile->profile64.matches);
    }
    if (NULL != profile->profile64.similar) {
        profile->free(profile->profile64.similar);
    }

    free(profile);
}

char* parasail_reverse(const char *s, size_t length)
{
    char *r = NULL;
    size_t i = 0;
    size_t j = 0;

    r = (char*)malloc(sizeof(char)*(length + 1));
    r[length] = '\0';
    for (i=0,j=length-1; i<length; ++i,--j) {
        r[i] = s[j];
    }

    return r;
}

uint32_t* parasail_reverse_uint32_t(const uint32_t *s, size_t length)
{
    uint32_t *r = NULL;
    size_t i = 0;
    size_t j = 0;

    r = (uint32_t*)malloc(sizeof(uint32_t)*(length));
    for (i=0,j=length-1; i<length; ++i,--j) {
        r[i] = s[j];
    }

    return r;
}

int parasail_result_is_nw(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_NW;
}

int parasail_result_is_sg(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_SG;
}

int parasail_result_is_sw(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_SW;
}

int parasail_result_is_saturated(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_SATURATED;
}

int parasail_result_is_banded(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_BANDED;
}

int parasail_result_is_scan(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_SCAN;
}

int parasail_result_is_striped(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_STRIPED;
}

int parasail_result_is_diag(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_DIAG;
}

int parasail_result_is_blocked(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_BLOCKED;
}

int parasail_result_is_stats(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_STATS;
}

int parasail_result_is_stats_table(const parasail_result_t * const restrict result)
{
    return (result->flag & PARASAIL_FLAG_STATS) && (result->flag & PARASAIL_FLAG_TABLE);
}

int parasail_result_is_stats_rowcol(const parasail_result_t * const restrict result)
{
    return (result->flag & PARASAIL_FLAG_STATS) && (result->flag & PARASAIL_FLAG_ROWCOL);
}

int parasail_result_is_table(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_TABLE;
}

int parasail_result_is_rowcol(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_ROWCOL;
}

int parasail_result_is_trace(const parasail_result_t * const restrict result)
{
    return result->flag & PARASAIL_FLAG_TRACE;
}

int parasail_result_get_score(const parasail_result_t * const restrict result)
{
    return result->score;
}

int parasail_result_get_end_query(const parasail_result_t * const restrict result)
{
    return result->end_query;
}

int parasail_result_get_end_ref(const parasail_result_t * const restrict result)
{
    return result->end_ref;
}

int parasail_result_get_matches(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats(result));
    return result->stats->matches;
}

int parasail_result_get_similar(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats(result));
    return result->stats->similar;
}

int parasail_result_get_length(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats(result));
    return result->stats->length;
}

int* parasail_result_get_score_table(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_table(result) || parasail_result_is_stats_table(result));
    if (parasail_result_is_stats_table(result)) {
        return result->stats->tables->score_table;
    }
    if (parasail_result_is_table(result)) {
        return result->tables->score_table;
    }
    return NULL; /* should not reach */
}

int* parasail_result_get_matches_table(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats_table(result));
    return result->stats->tables->matches_table;
}

int* parasail_result_get_similar_table(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats_table(result));
    return result->stats->tables->similar_table;
}

int* parasail_result_get_length_table(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats_table(result));
    return result->stats->tables->length_table;
}

int* parasail_result_get_score_row(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats_rowcol(result) || parasail_result_is_rowcol(result));
    if (parasail_result_is_stats_rowcol(result)) {
        return result->stats->rowcols->score_row;
    }
    if (parasail_result_is_rowcol(result)) {
        return result->rowcols->score_row;
    }
    return NULL; /* should not reach */
}

int* parasail_result_get_matches_row(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats_rowcol(result));
    return result->stats->rowcols->matches_row;
}

int* parasail_result_get_similar_row(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats_rowcol(result));
    return result->stats->rowcols->similar_row;
}

int* parasail_result_get_length_row(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats_rowcol(result));
    return result->stats->rowcols->length_row;
}

int* parasail_result_get_score_col(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats_rowcol(result) || parasail_result_is_rowcol(result));
    if (parasail_result_is_stats_rowcol(result)) {
        return result->stats->rowcols->score_col;
    }
    if (parasail_result_is_rowcol(result)) {
        return result->rowcols->score_col;
    }
    return NULL; /* should not reach */
}

int* parasail_result_get_matches_col(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats_rowcol(result));
    return result->stats->rowcols->matches_col;
}

int* parasail_result_get_similar_col(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats_rowcol(result));
    return result->stats->rowcols->similar_col;
}

int* parasail_result_get_length_col(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_stats_rowcol(result));
    return result->stats->rowcols->length_col;
}

int* parasail_result_get_trace_table(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_trace(result));
    return result->trace->trace_table;
}

int* parasail_result_get_trace_ins_table(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_trace(result));
    return result->trace->trace_ins_table;
}

int* parasail_result_get_trace_del_table(const parasail_result_t * const restrict result)
{
    assert(parasail_result_is_trace(result));
    return result->trace->trace_del_table;
}

