/*
    Copyright (C) 2012 Modelon AB

    This program is free software: you can redistribute it and/or modify
    it under the terms of the BSD style license.

     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
    FMILIB_License.txt file for more details.

    You should have received a copy of the FMILIB_License.txt file
    along with this program. If not, contact Modelon AB <http://www.modelon.com>.
*/

/**
* \file jm_vector_template.h
* \brief Vector template definition.
*
* This file is supposed to be included into a C-file that instantiate the template.
* jm_vector.h must be included before this file.
* It expects JM_TEMPLATE_INSTANCE_TYPE to be defined to the template type to be instantiated.
*/

#include <stdlib.h>
#include <string.h>
#include "jm_vector.h"

/** \addtogroup jm_vector  A vector of items (dynamic array)*/
/** @{
*/

#ifndef JM_TEMPLATE_INSTANCE_TYPE
#error "JM_TEMPLATE_INSTANCE_TYPE must be defined before including this file"
#endif

jm_vector(JM_TEMPLATE_INSTANCE_TYPE) * jm_vector_alloc(JM_TEMPLATE_INSTANCE_TYPE) (size_t size, size_t capacity, jm_callbacks* c) {
        size_t reserve;
        jm_callbacks* cc;
        jm_vector(JM_TEMPLATE_INSTANCE_TYPE) * v;
        if(c)
            cc = c;
        else
            cc = jm_get_default_callbacks();

        reserve = capacity;
        if(reserve < size) reserve = size;
        if(reserve > JM_VECTOR_MINIMAL_CAPACITY) {
            v = (jm_vector(JM_TEMPLATE_INSTANCE_TYPE)*)cc->malloc(
                        sizeof(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)) +
                        sizeof(JM_TEMPLATE_INSTANCE_TYPE) * (reserve -JM_VECTOR_MINIMAL_CAPACITY));
            if(!v) return 0;
            v->capacity = reserve;
        }
        else {
            v = (jm_vector(JM_TEMPLATE_INSTANCE_TYPE)*)cc->malloc(sizeof(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)));
            if(!v) return 0;
            v->capacity = JM_VECTOR_MINIMAL_CAPACITY;
        }
        v->callbacks = cc;
        v->items = &(v->preallocated[0]);
        v->size = size;
        return v;
}

void jm_vector_free(JM_TEMPLATE_INSTANCE_TYPE) (jm_vector(JM_TEMPLATE_INSTANCE_TYPE) * a) {
    if(!a) return;
    jm_vector_free_data(JM_TEMPLATE_INSTANCE_TYPE)(a);
    a->callbacks->free(a);
}

size_t jm_vector_init(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* a, size_t initSize, jm_callbacks* c) {        
        if(c)
            a->callbacks = c;
        else
            a->callbacks = jm_get_default_callbacks();
        a->items = a->preallocated;
        a->size = 0;
        a->capacity = JM_VECTOR_MINIMAL_CAPACITY;

        if(initSize > a->size)
            return jm_vector_resize(JM_TEMPLATE_INSTANCE_TYPE)(a, initSize);
        return 0;
}

void jm_vector_set_item(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* a, size_t index, JM_TEMPLATE_INSTANCE_TYPE item) {
    *(jm_vector_get_itemp(JM_TEMPLATE_INSTANCE_TYPE)(a, index)) = item;
}

size_t jm_vector_resize(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* a, size_t size) {
        if(size > a->capacity)  {
            if(jm_vector_reserve(JM_TEMPLATE_INSTANCE_TYPE)(a, size) < size) {
                a->size = a->capacity;
                return a->capacity;
            }
        }
        a->size = size;
        return size;
}

size_t jm_vector_reserve(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* a, size_t size) {
        void* newmem;
        if(size <= a->capacity) return a->capacity;
        newmem = a->callbacks->malloc(size * sizeof(JM_TEMPLATE_INSTANCE_TYPE));
        if(!newmem) return a->capacity;
        memcpy(newmem, a->items, a->size * sizeof(JM_TEMPLATE_INSTANCE_TYPE));
        if(a->items !=  a->preallocated) a->callbacks->free((void*)(a->items));
        a->items = newmem;
        a->capacity = size;
        return a->capacity;
}

size_t jm_vector_copy(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* destination, jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* source) {
        size_t destsize = jm_vector_resize(JM_TEMPLATE_INSTANCE_TYPE)(destination, source->size);
        if(destsize > 0) {
            memcpy((void*)destination->items, (void*)source->items, sizeof(JM_TEMPLATE_INSTANCE_TYPE)*destsize);
        }
        return destination->size;
}

jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* jm_vector_clone(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* v) {                              \
    jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* ret = jm_vector_alloc(JM_TEMPLATE_INSTANCE_TYPE)(v->size, v->size, v->callbacks);
    if(ret) jm_vector_copy(JM_TEMPLATE_INSTANCE_TYPE)(ret, v);
    return ret;
}

size_t jm_vector_append(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* destination, jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* source) {
        size_t oldsize, newsize;
        oldsize = jm_vector_get_size(JM_TEMPLATE_INSTANCE_TYPE)(destination);
        newsize = jm_vector_resize(JM_TEMPLATE_INSTANCE_TYPE)(destination, source->size + oldsize);
        memcpy((void*)(destination->items + oldsize), (void*)source->items, sizeof(JM_TEMPLATE_INSTANCE_TYPE)*(newsize - oldsize));
        return (newsize - oldsize);
}

JM_TEMPLATE_INSTANCE_TYPE* jm_vector_insert(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* a, size_t index, JM_TEMPLATE_INSTANCE_TYPE item) {
        size_t reserve;
        JM_TEMPLATE_INSTANCE_TYPE* pitem;
        if(index >= a->size) return 0;
        if(a->size == a->capacity) {
                if(a->capacity > JM_VECTOR_MAX_MEMORY_CHUNK)
                        reserve = JM_VECTOR_MAX_MEMORY_CHUNK + a->capacity;
                else
                        reserve = a->capacity * 2;
                if( jm_vector_reserve(JM_TEMPLATE_INSTANCE_TYPE)(a, reserve) != reserve) return 0;
        }
        assert(a->size < a->capacity);
        memmove((void*)(a->items+index+1),(void*)(a->items+index), (a->size - index)*sizeof(JM_TEMPLATE_INSTANCE_TYPE));
        a->items[index] = item;
        pitem = &(a->items[index]);
        a->size++;
        return pitem;
}

JM_TEMPLATE_INSTANCE_TYPE* jm_vector_resize1(JM_TEMPLATE_INSTANCE_TYPE) (jm_vector(JM_TEMPLATE_INSTANCE_TYPE) * a) {
        size_t reserve;
        JM_TEMPLATE_INSTANCE_TYPE* pitem;
        if(a->size == a->capacity) {
                if(a->capacity > JM_VECTOR_MAX_MEMORY_CHUNK)
                        reserve = JM_VECTOR_MAX_MEMORY_CHUNK + a->capacity;
                else
                        reserve = a->capacity * 2;
                if( jm_vector_reserve(JM_TEMPLATE_INSTANCE_TYPE)(a, reserve) != reserve) return 0;
        }
        assert(a->size < a->capacity);
        pitem = &(a->items[a->size]);
        a->size++;
        return pitem;
}

JM_TEMPLATE_INSTANCE_TYPE* jm_vector_push_back(JM_TEMPLATE_INSTANCE_TYPE) (jm_vector(JM_TEMPLATE_INSTANCE_TYPE) * a, JM_TEMPLATE_INSTANCE_TYPE item) {
        JM_TEMPLATE_INSTANCE_TYPE* pitem= jm_vector_resize1(JM_TEMPLATE_INSTANCE_TYPE) (a);
        if(!pitem) return 0;
        *pitem = item;
        return pitem;
}

void jm_vector_remove_item(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* v, size_t index) {
    size_t n =v->size - index -1;
    assert(index < v->size);
    if(n) {
        memmove((void*)&(v->items[index]),(void*) &(v->items[index+1]), n * sizeof(JM_TEMPLATE_INSTANCE_TYPE));
    }
    v->size--;
}

void jm_vector_zero(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* a) {
    if(jm_vector_get_size(JM_TEMPLATE_INSTANCE_TYPE)(a) > 0) {
        memset((void*)a->items,0,a->size * sizeof(JM_TEMPLATE_INSTANCE_TYPE));
    }
}

void jm_vector_foreach_c(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* a,
                                                    void (*f)(JM_TEMPLATE_INSTANCE_TYPE, void*), void * data) {
        size_t i;
        for(i = 0; i < jm_vector_get_size(JM_TEMPLATE_INSTANCE_TYPE)(a); i++)
            f(jm_vector_get_item(JM_TEMPLATE_INSTANCE_TYPE)(a, i), data);
}

void jm_vector_foreach(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* a,
                                                    void (*f)(JM_TEMPLATE_INSTANCE_TYPE)) {
        size_t i;
        for(i = 0; i < jm_vector_get_size(JM_TEMPLATE_INSTANCE_TYPE)(a); i++)
            f(jm_vector_get_item(JM_TEMPLATE_INSTANCE_TYPE)(a, i));
}

void jm_vector_qsort(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* v, jm_compare_ft f) {
    if(jm_vector_get_size(JM_TEMPLATE_INSTANCE_TYPE)(v) > 1) {
        qsort((void*)v->items, jm_vector_get_size(JM_TEMPLATE_INSTANCE_TYPE)(v), sizeof(JM_TEMPLATE_INSTANCE_TYPE),f);
    }
}

#define jm_vector_ptr2index(T) jm_mangle(jm_vector_ptr2index, T)

static size_t jm_vector_ptr2index(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* v, JM_TEMPLATE_INSTANCE_TYPE* itemp) {
    if(itemp)
        return (itemp - v->items);
    else
        return jm_vector_get_size(JM_TEMPLATE_INSTANCE_TYPE)(v);
}


size_t jm_vector_bsearch_index(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* v, JM_TEMPLATE_INSTANCE_TYPE* key, jm_compare_ft f) {
    return jm_vector_ptr2index(JM_TEMPLATE_INSTANCE_TYPE)(v, jm_vector_bsearch(JM_TEMPLATE_INSTANCE_TYPE)(v, key,f));
}

JM_TEMPLATE_INSTANCE_TYPE* jm_vector_bsearch(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* v, JM_TEMPLATE_INSTANCE_TYPE* key, jm_compare_ft f) {
    return bsearch(key, v->items,
                   jm_vector_get_size(JM_TEMPLATE_INSTANCE_TYPE)(v),
                   sizeof(JM_TEMPLATE_INSTANCE_TYPE),
                   f);
}

JM_TEMPLATE_INSTANCE_TYPE* jm_vector_find(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* a, JM_TEMPLATE_INSTANCE_TYPE* itemp, jm_compare_ft f) {
    size_t i = jm_vector_get_size(JM_TEMPLATE_INSTANCE_TYPE)(a);
    while(i--) {
        JM_TEMPLATE_INSTANCE_TYPE* cur = jm_vector_get_itemp(JM_TEMPLATE_INSTANCE_TYPE)(a, i);
        if(f(cur, itemp) == 0)
           return cur;
    };
    return 0;
}

size_t jm_vector_find_index(JM_TEMPLATE_INSTANCE_TYPE)(jm_vector(JM_TEMPLATE_INSTANCE_TYPE)* v, JM_TEMPLATE_INSTANCE_TYPE* itemp, jm_compare_ft f) {
    return jm_vector_ptr2index(JM_TEMPLATE_INSTANCE_TYPE)(v,jm_vector_find(JM_TEMPLATE_INSTANCE_TYPE)(v, itemp, f));
}

/** @}
*/