#include "array.h"
#include "utility.h"

#include <stdlib.h>
#include <stddef.h>
#include <string.h>

typedef struct{
    size_t size;
    uint   use_count;
    uint   mem_count;
    
    char   buf[];
} VectorHeader;

#define getHeader(X)  ((VectorHeader*)(pChar(X)-offsetof(VectorHeader,buf)))
#define getTotal2(X,Y) ((Y)*(X)+sizeof(VectorHeader))
#define getTotal(X) getTotal2((X)->mem_count,(X)->size)

/********************** initalize *************************/
pvoid ctl_array_initX(ppvoid v, size_t size, uint count){
    VectorHeader *tmp;
    
    int mem = 0;
    if(count >= 1)
        for(mem = 1; mem < count; mem *= 2);
        
    tmp = (VectorHeader*)ctl_malloc(getTotal2(mem, size));
    
    tmp->size      = size;
    tmp->use_count = count;
    tmp->mem_count = mem;
    if(v != NULL){
        *v = pVoid(tmp->buf);
    }
    return pVoid(tmp->buf);
}

/*********************** destructure **********************/
pvoid ctl_array_freeX(ppvoid v){
    free(pVoid(getHeader(*v)));
    *v = NULL;
    return NULL;
}

/******************** methods about get??? ****************/
int ctl_array_getSizeX(ppvoid v){
    return getHeader(*v)->use_count;
}

pvoid ctl_array_getEntryX(ppvoid v, uint index){
    return pVoid(pChar(*v) + getHeader(*v)->size * index);
}

int ctl_array_getEntrySizeX(ppvoid v){
    return getHeader(*v)->size;
}

/******************** methods about set??? ****************/
int ctl_array_setSizeX(ppvoid v, uint count){
    VectorHeader *tmp = getHeader(*v);
    
    int mem = 0;
    if(count >= 1)
        for(mem = 1; mem < count; mem *= 2);
    tmp->use_count = count;
    tmp->mem_count =   mem;
    
    tmp = (VectorHeader*)ctl_realloc(pVoid(tmp), getTotal(tmp));
    *v = pVoid(tmp->buf);
    
    return tmp->use_count;
}
pvoid ctl_array_setEntryX(ppvoid v, uint index, pcvoid data){
    VectorHeader *tmp = getHeader(*v);
    memcpy(pVoid(tmp->buf + index * tmp->size), data, tmp->size);
}

/************** add/del element on the back ***************/
int ctl_array_addBackX(ppvoid v, pcvoid entry){
    VectorHeader *tmp = getHeader(*v);
    
    if(tmp->use_count + 1 > tmp->mem_count){
        if(tmp->mem_count == 0) tmp->mem_count  = 1;
        else                    tmp->mem_count *= 2;
        tmp = (VectorHeader*)realloc(pVoid(tmp), getTotal(tmp));
        *v = pVoid(tmp->buf);
    }
    memcpy(tmp->buf + tmp->size * tmp->use_count, entry, tmp->size);
    tmp->use_count++;
    return tmp->use_count;
}
int ctl_array_delBackX(ppvoid v){
    VectorHeader *tmp = getHeader(*v);
    
    if((tmp->use_count - 1) * 2 < tmp->mem_count){
        tmp->mem_count /= 2;
        tmp = (VectorHeader*)realloc(pVoid(tmp), getTotal(tmp));
        *v = pVoid(tmp->buf);
    }
    
    tmp->use_count--;
    return tmp->use_count;
}
//int ctl_array_addFrontX(ppvoid v, pcvoid entry);
//int ctl_array_delFrontX(ppvoid v);

int ctl_array_catX(ppvoid v, ppcvoid v2){
    int count0 = getHeader(*v)->use_count;
    int count2 = getHeader(*v2)->use_count;
    ctl_array_setSize(v, count0 + count2);
    int i;
    for(i = 0; i < count2; i++)
        ctl_array_setEntry(v, count0 + i, ctl_array_getEntry(v2, i));
    return count0 + count2;
}
pvoid ctl_array_copyX(ppvoid v, ppcvoid v2){
    VectorHeader* tmp = getHeader(*v2);
    VectorHeader* p = (VectorHeader*)malloc(getTotal(tmp));
    memcpy(pVoid(p), pVoid(tmp), getTotal(tmp));
    if(v != NULL){
        if(*v != NULL){
            ctl_array_free(v);
        }
        *v = p->buf;
    }
    return p->buf;
}
int ctl_array_replaceX( ppvoid v , uint start1, uint length1,
                        ppcvoid v2, uint start2,  int length2){
    int end1 = (int)start1 +     length1  - 1;
    int end2 = (int)start2 + abs(length2) - 1, step2 = 1;
    if(length2 < 0){
        length2 *= -1;
        step2   *= -1;
        ctl_swap(int, start2, end2);
    }
    if      (length1 < length2){ // need increase size
        int sz0   = ctl_array_getSize(v);
        int delta = length2 - length1;
        ctl_array_setSize(v, sz0 + delta);
        int i;
        for(i = sz0 + delta - 1; i - delta > end1; i--){
            ctl_array_setEntry(v, i, ctl_array_getEntry(v, i - delta));
        }
    }else if(length1 > length2){ // need decrease size
        int sz0   = ctl_array_getSize(v);
        int delta = length1 - length2;
        int i;
        for(i = end1 - delta + 1; i + delta < sz0; i++){
            ctl_array_setEntry(v, i, ctl_array_getEntry(v, i + delta));
        }
        ctl_array_setSize(v, sz0 - delta);
    }
    for(end2 += step2; start2 != end2; start2 += step2, start1 += 1){
        ctl_array_setEntry(v, start1, ctl_array_getEntry(v2, start2));
    }
}