/** @file  psVector.c
*
*  @brief Contains support for basic vector types
*
*  This file defines the basic type for a vector struct and functions useful
*  in manupulating vectors.
*
*  @author Ross Harman, MHPCC
*  @author Robert DeSonia, MHPCC
*
*  @version $Revision: 1.42 $ $Name: not supported by cvs2svn $
*  @date $Date: 2005-06-10 21:46:46 $
*
*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
*/

#include <string.h>                        // for memcpy
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include "psMemory.h"
#include "psError.h"
#include "psVector.h"
#include "psLogMsg.h"
#include "psCompare.h"

#include "psCollectionsErrors.h"

typedef struct
{
    p_psVectorData data; // need this first for psVectorSortIndex to work.
    psU32 index;
}
indexedVector;

static void vectorFree(psVector* psVec);

static void vectorFree(psVector* psVec)
{
    if (psVec == NULL) {
        return;
    }

    psFree(psVec->data.U8);
}

// FUNCTION IMPLEMENTATION - PUBLIC

psVector* psVectorAlloc(psU32 nalloc, psElemType elemType)
{
    psVector* psVec = NULL;
    psS32 elementSize = 0;

    elementSize = PSELEMTYPE_SIZEOF(elemType);
    if (elementSize < 1) {
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                PS_ERRORTEXT_psVector_UNSUPPORTED_TYPE, elemType);
        return NULL;
    }


    // Create vector struct
    psVec = (psVector* ) psAlloc(sizeof(psVector));
    psMemSetDeallocator(psVec, (psFreeFcn) vectorFree);

    psVec->type.dimen = PS_DIMEN_VECTOR;
    psVec->type.type = elemType;
    *(int*)&psVec->nalloc = nalloc;
    psVec->n = nalloc;

    // Create vector data array
    psVec->data.U8 = psAlloc(nalloc * elementSize);

    return psVec;
}

psVector* psVectorRealloc(psVector* in, psU32 nalloc)
{
    psS32 elementSize = 0;
    psElemType elemType;

    if (in == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psVector_REALLOC_NULL);
        return NULL;
    } else if (in->nalloc != nalloc) {     // No need to realloc to same size
        elemType = in->type.type;
        elementSize = PSELEMTYPE_SIZEOF(elemType);
        if (nalloc < in->n) {
            in->n = nalloc;
        }
        // Realloc after decrementation to avoid accessing freed array elements
        in->data.U8 = psRealloc(in->data.U8, nalloc * elementSize);
        *(int*)&in->nalloc = nalloc;
    }

    return in;
}

psVector* psVectorRecycle(psVector* in, psU32 n, psElemType type)
{
    psS32 byteSize;

    if (in == NULL) {
        return psVectorAlloc(n, type);
    }

    if (in->type.dimen !=  PS_DIMEN_VECTOR &&
            in->type.dimen !=  PS_DIMEN_TRANSV) {
        psFree(in);
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                PS_ERRORTEXT_psVector_NOT_A_VECTOR);
        return NULL;
    }

    byteSize = n * PSELEMTYPE_SIZEOF(type);

    // need to increase data buffer?
    if (byteSize > in->nalloc*PSELEMTYPE_SIZEOF(in->type.type)) {
        in->data.U8 = psRealloc(in->data.U8, byteSize);
        *(int*)&in->nalloc = n;
    }

    in->type.dimen = PS_DIMEN_VECTOR;
    in->type.type = type;
    in->n = n;
    return in;
}

psVector *psVectorExtend(psVector *vector, int delta, int nExtend)
{
    // can't handle a NULL vector (don't know the data type to allocate)
    if (vector == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psVector_NULL);
        return NULL;
    }

    // requirement on delta; if < 1, set to 10.
    if (delta < 1) {
        delta = 10;
    }

    // adjust the allocated size, if needed. (if nExtend < 1, this is will never happen)
    if (nExtend > 0) {
        unsigned int minAlloc = vector->n + nExtend + nExtend;
        if (vector->nalloc < minAlloc) {
            unsigned int nAlloc = delta + vector->nalloc;
            // make sure the delta is large enough hold twice the extended length.
            if (nAlloc < minAlloc) {
                nAlloc = minAlloc;
            }
            vector = psVectorRealloc(vector, nAlloc);
        }
    } else if (nExtend < -vector->n) {
        // For the case of a negative nExtend, need to check that we are not decreasing
        // vector beyond its own length (i.e., creating a negative length).
        nExtend = -vector->n;
    }

    // increment the length by the value specified
    vector->n += nExtend;

    return vector;
}

psVector* psVectorCopy(psVector* out, const psVector* in, psElemType type)
{
    if (in == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psVector_SORT_NULL);
        psFree(out);
        return NULL;
    }

    psS32 nElements = in->n;

    out = psVectorRecycle(out, nElements, type);

    #define PSVECTOR_COPY(INTYPE,OUTTYPE) { \
        ps##INTYPE *inVec = in->data.INTYPE; \
        ps##OUTTYPE *outVec = out->data.OUTTYPE; \
        for (psS32 col=0;col<nElements;col++) { \
            *(outVec++) = *(inVec++); \
        } \
    }

    #define PSVECTOR_COPY_CASE(OUTTYPE) \
case PS_TYPE_##OUTTYPE: { \
        switch (in->type.type) { \
        case PS_TYPE_S8: \
            PSVECTOR_COPY(S8,OUTTYPE); \
            break; \
        case PS_TYPE_S16: \
            PSVECTOR_COPY(S16,OUTTYPE); \
            break; \
        case PS_TYPE_S32: \
            PSVECTOR_COPY(S32,OUTTYPE); \
            break; \
        case PS_TYPE_S64: \
            PSVECTOR_COPY(S64,OUTTYPE); \
            break; \
        case PS_TYPE_U8: \
            PSVECTOR_COPY(U8,OUTTYPE); \
            break; \
        case PS_TYPE_U16: \
            PSVECTOR_COPY(U16,OUTTYPE); \
            break; \
        case PS_TYPE_U32: \
            PSVECTOR_COPY(U32,OUTTYPE); \
            break; \
        case PS_TYPE_U64: \
            PSVECTOR_COPY(U64,OUTTYPE); \
            break; \
        case PS_TYPE_F32: \
            PSVECTOR_COPY(F32,OUTTYPE); \
            break; \
        case PS_TYPE_F64: \
            PSVECTOR_COPY(F64,OUTTYPE); \
            break; \
        case PS_TYPE_C32: \
            PSVECTOR_COPY(C32,OUTTYPE); \
            break; \
        case PS_TYPE_C64: \
            PSVECTOR_COPY(C64,OUTTYPE); \
            break; \
        default: { \
                char* typeStr; \
                PS_TYPE_NAME(typeStr,type); \
                psError(PS_ERR_BAD_PARAMETER_TYPE, true, \
                        PS_ERRORTEXT_psVector_UNSUPPORTED_TYPE, \
                        typeStr); \
                psFree(out); \
            } \
        } \
        break; \
    }

    switch (type) {
        PSVECTOR_COPY_CASE(S8);
        PSVECTOR_COPY_CASE(S16);
        PSVECTOR_COPY_CASE(S32);
        PSVECTOR_COPY_CASE(S64);
        PSVECTOR_COPY_CASE(U8);
        PSVECTOR_COPY_CASE(U16);
        PSVECTOR_COPY_CASE(U32);
        PSVECTOR_COPY_CASE(U64);
        PSVECTOR_COPY_CASE(F32);
        PSVECTOR_COPY_CASE(F64);
        PSVECTOR_COPY_CASE(C32);
        PSVECTOR_COPY_CASE(C64);
    default: {
            char* typeStr;
            PS_TYPE_NAME(typeStr,type);
            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                    PS_ERRORTEXT_psVector_UNSUPPORTED_TYPE,
                    typeStr);
            psFree(out);

            break;
        }
    }
    return out;


}

psVector* psVectorSort(psVector* outVector, const psVector* inVector)
{
    psS32 N = 0;
    psS32 elSize = 0;
    psPtr inVec = NULL;
    psPtr outVec = NULL;
    psElemType inType = 0;

    if (inVector == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psVector_SORT_NULL);
        psFree(outVector);
        return NULL;
    }

    inType = inVector->type.type;
    N = inVector->n;
    inVec = (psPtr)inVector->data.U8;
    elSize = PSELEMTYPE_SIZEOF(inType);

    if (outVector == NULL) {
        outVector = psVectorAlloc(N, inType);
    }

    // check to see if output vector needs to be resized/retyped
    if ( (N > outVector->nalloc) ||
            (inType != outVector->type.type) ) {
        // reshape the output vector to match the input vector's size/type.
        outVector = psVectorRecycle(outVector,N,inType);
    }
    outVector->n = N;
    outVec = outVector->data.U8;

    if (N == 0) {
        // no need to sort anything, as there are no elements in input vector.
        return outVector;
    }

    // Copy input vector values into output vector if not in-place sorting
    if (inVector != outVector) {
        memcpy(outVec, inVec, elSize * N);
    }

    // Sort output vector
    switch (inType) {
    case PS_TYPE_U8:
        qsort(outVec, N, elSize, psCompareU8);
        break;
    case PS_TYPE_U16:
        qsort(outVec, N, elSize, psCompareU16);
        break;
    case PS_TYPE_U32:
        qsort(outVec, N, elSize, psCompareU32);
        break;
    case PS_TYPE_U64:
        qsort(outVec, N, elSize, psCompareU64);
        break;
    case PS_TYPE_S8:
        qsort(outVec, N, elSize, psCompareS8);
        break;
    case PS_TYPE_S16:
        qsort(outVec, N, elSize, psCompareS16);
        break;
    case PS_TYPE_S32:
        qsort(outVec, N, elSize, psCompareS32);
        break;
    case PS_TYPE_S64:
        qsort(outVec, N, elSize, psCompareS64);
        break;
    case PS_TYPE_F32:
        qsort(outVec, N, elSize, psCompareF32);
        break;
    case PS_TYPE_F64:
        qsort(outVec, N, elSize, psCompareF64);
        break;
    default:
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                PS_ERRORTEXT_psVector_UNSUPPORTED_TYPE,
                inType);
        psFree(outVector);
        return NULL;
    }

    return outVector;
}

psVector* psVectorSortIndex(psVector* outVector, const psVector* inVector)
{
    psS32 N = 0;
    psElemType inType = 0;

    if (inVector == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psVector_SORT_NULL);
        psFree(outVector);
        return NULL;
    }

    inType = inVector->type.type;
    N = inVector->n;

    if (N == 0) {
        // no need to sort anything, as there are no elements in input vector.
        return outVector;
    }

    // ok, let's create a temporary indexed vector
    indexedVector* idxVector = psAlloc(sizeof(indexedVector)*N);
    int elSize = PSELEMTYPE_SIZEOF(inType);
    for (int i = 0; i < N; i++) {
        idxVector[i].data.U8 = inVector->data.U8+i*elSize;
        idxVector[i].index = i;
    }

    // Sort indexed vector
    // n.b., since first element in indexedVector is a pointer to the data,
    // we can use the 'Ptr' version of the standard compare functions
    switch (inType) {
    case PS_TYPE_U8:
        qsort(idxVector, N, sizeof(indexedVector), (psCompareFcn)psCompareU8Ptr);
        break;
    case PS_TYPE_U16:
        qsort(idxVector, N, sizeof(indexedVector), (psCompareFcn)psCompareU16Ptr);
        break;
    case PS_TYPE_U32:
        qsort(idxVector, N, sizeof(indexedVector), (psCompareFcn)psCompareU32Ptr);
        break;
    case PS_TYPE_U64:
        qsort(idxVector, N, sizeof(indexedVector), (psCompareFcn)psCompareU64Ptr);
        break;
    case PS_TYPE_S8:
        qsort(idxVector, N, sizeof(indexedVector), (psCompareFcn)psCompareS8Ptr);
        break;
    case PS_TYPE_S16:
        qsort(idxVector, N, sizeof(indexedVector), (psCompareFcn)psCompareS16Ptr);
        break;
    case PS_TYPE_S32:
        qsort(idxVector, N, sizeof(indexedVector), (psCompareFcn)psCompareS32Ptr);
        break;
    case PS_TYPE_S64:
        qsort(idxVector, N, sizeof(indexedVector), (psCompareFcn)psCompareS64Ptr);
        break;
    case PS_TYPE_F32:
        qsort(idxVector, N, sizeof(indexedVector), (psCompareFcn)psCompareF32Ptr);
        break;
    case PS_TYPE_F64:
        qsort(idxVector, N, sizeof(indexedVector), (psCompareFcn)psCompareF64Ptr);
        break;
    default:
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                PS_ERRORTEXT_psVector_UNSUPPORTED_TYPE,
                inType);
        psFree(idxVector);
        psFree(outVector);
        return NULL;
    }

    // extract the indices to the output vector
    outVector = psVectorRecycle(outVector, N, PS_TYPE_U32);
    psU32* outData = outVector->data.U32;
    for (int i = 0; i < N; i++) {
        outData[i] = idxVector[i].index;
    }

    // Free temp memory
    psFree(idxVector);

    return outVector;
}

char* psVectorToString(psVector* vector, int maxLength)
{

    if (maxLength < 5) {
        return NULL;
    }

    char* str = psAlloc(sizeof(char)*maxLength+1);

    if (vector == NULL) {
        snprintf(str,maxLength, "NULL");
        return str;
    }

    int size = vector->n;

    if (size == 0) {
        snprintf(str,maxLength, "[]");
        return str;
    }

    char* tempStr = psAlloc(sizeof(char)*maxLength+1);
    *str = '\0';
    bool full = false;

    #define APPEND_ELEMENTS_CASE(TYPE, NATIVE_TYPE, FORMAT) \
case PS_TYPE_##TYPE: \
    for (lcv=0; lcv < size && ! full; lcv++) { \
        snprintf(tempStr, maxLength, "%s" FORMAT, prefix, (NATIVE_TYPE) (vector->data.TYPE[lcv])); \
        strncat(str,tempStr,maxLength); \
        full = (strlen(str) > maxLength-2); \
        prefix = ","; \
    } \
    break;

    #define APPEND_ELEMENTS_CASE_COMPLEX(TYPE,CREAL,CIMAG) \
case PS_TYPE_##TYPE: \
    for (lcv=0; lcv < size && ! full; lcv++) { \
        snprintf(tempStr, maxLength, "%s%g%+gi", prefix, \
                 CREAL(vector->data.TYPE[lcv]), \
                 CIMAG(vector->data.TYPE[lcv])); \
        full = (strlen(str) > maxLength-2); \
        prefix = ","; \
    } \
    break;

    int lcv;
    char* prefix = "[";
    switch(vector->type.type) {
        APPEND_ELEMENTS_CASE(S8,char,"%hd")
        APPEND_ELEMENTS_CASE(S16,short int,"%hd")
        APPEND_ELEMENTS_CASE(S32,int,"%d")
        APPEND_ELEMENTS_CASE(S64,long,"%ld")
        APPEND_ELEMENTS_CASE(U8,unsigned char,"%hu")
        APPEND_ELEMENTS_CASE(U16,unsigned short,"%hu")
        APPEND_ELEMENTS_CASE(U32,unsigned int, "%u")
        APPEND_ELEMENTS_CASE(U64,unsigned long,"%lu")
        APPEND_ELEMENTS_CASE(F32,double,"%g")
        APPEND_ELEMENTS_CASE(F64,double,"%g")
        APPEND_ELEMENTS_CASE_COMPLEX(C32,crealf,cimagf)
        APPEND_ELEMENTS_CASE_COMPLEX(C64,creal,cimag)
    default:
        snprintf(str,maxLength,"[...]");
        break;
    }

    if (full) {
        // couldn't all fit in given string length

        // remove elements until there is room for ",...]"
        while (strlen(str) > maxLength - 5) {
            char* lastComma = strrchr(str,',');
            if (lastComma == NULL) { // no comma, must be first number
                str[1] = '\0';
            } else {
                *lastComma = '\0';
            }
        }
        strncat(str,",...]",maxLength);
    } else {
        strncat(str,"]",maxLength);
    }

    psFree(tempStr);

    return str;
}

psF64 p_psVectorGetElementF64(psVector* vector,
                              int position)
{
    if (vector == NULL) {
        return NAN;
    }
    if (position < 0 || position >= vector->n) {
        return NAN;
    }

    switch (vector->type.type) {
    case PS_TYPE_U8:
        return vector->data.U8[position];
        break;
    case PS_TYPE_U16:
        return vector->data.U16[position];
        break;
    case PS_TYPE_U32:
        return vector->data.U32[position];
        break;
    case PS_TYPE_U64:
        return vector->data.U64[position];
        break;
    case PS_TYPE_S8:
        return vector->data.S8[position];
        break;
    case PS_TYPE_S16:
        return vector->data.S16[position];
        break;
    case PS_TYPE_S32:
        return vector->data.S32[position];
        break;
    case PS_TYPE_S64:
        return vector->data.S64[position];
        break;
    case PS_TYPE_F32:
        return vector->data.F32[position];
        break;
    case PS_TYPE_F64:
        return vector->data.F64[position];
    default:
        return NAN;
    }
}

bool p_psVectorPrint (FILE *f, psVector *a, char *name)
{

    fprintf (f, "vector: %s\n", name);

    for (int i = 0; i < a[0].n; i++) {
        fprintf (f, "%f\n", p_psVectorGetElementF64(a, i));
    }
    fprintf (f, "\n");
    return (true);
}
