/** @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.36 $ $Name: not supported by cvs2svn $
*  @date $Date: 2005-03-22 21:52:49 $
*
*  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"

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);

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

    psVec->type.dimen = PS_DIMEN_VECTOR;
    psVec->type.type = elemType;
    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);
        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);
        in->nalloc = n;
    }

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

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);
    }

    return outVector;
}

psVector* psVectorSortIndex(psVector* outVector, const psVector* inVector)
{
    psS32 N = 0;
    psVector* tmpVector = NULL;
    psVector* checkVector = NULL;
    psElemType inType = 0;
    psU32* outVec;

    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 (outVector == NULL) {
        outVector = psVectorAlloc(N, PS_TYPE_U32);
    }

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

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

    tmpVector = psVectorSort(tmpVector, inVector);

    checkVector = psVectorAlloc(N,PS_TYPE_U8);
    for(psS32 k=0; k<N; k++) {
        checkVector->data.U8[k] = 0;
    }

    #define SORT_INDICES(TYPE,absfcn,maxError) {                              \
        ps##TYPE* inVec = inVector->data.TYPE;                                \
        ps##TYPE* tmpVec = tmpVector->data.TYPE;                              \
        ps##TYPE  diff;                                                       \
        for(psS32 i=0; i<N; i++) {                                              \
            for(psS32 j=0; j<N; j++) {                                          \
                if(checkVector->data.U8[j] == 0 ) {                                    \
                    diff = absfcn(tmpVec[i] - inVec[j]);                          \
                    if(diff < maxError) {                                         \
                        outVec[i] = j;                                            \
                        checkVector->data.U8[j] = 1; \
                        break;                                                    \
                    }                                                             \
                } \
            }                                                                 \
        }                                                                     \
    }

    // Sort output vector
    switch (inType) {
    case PS_TYPE_U8:
        SORT_INDICES(U8,abs,1);
        break;
    case PS_TYPE_U16:
        SORT_INDICES(U16,abs,1);
        break;
    case PS_TYPE_U32:
        SORT_INDICES(U32,abs,1);
        break;
    case PS_TYPE_U64:
        SORT_INDICES(U64,abs,1);
        break;
    case PS_TYPE_S8:
        SORT_INDICES(S8,abs,1);
        break;
    case PS_TYPE_S16:
        SORT_INDICES(S16,abs,1);
        break;
    case PS_TYPE_S32:
        SORT_INDICES(S32,abs,1);
        break;
    case PS_TYPE_S64:
        SORT_INDICES(S64,abs,1);
        break;
    case PS_TYPE_F32:
        SORT_INDICES(F32,fabsf,FLT_EPSILON);
        break;
    case PS_TYPE_F64:
        SORT_INDICES(F64,fabs,DBL_EPSILON);
        break;
    default:
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                PS_ERRORTEXT_psVector_UNSUPPORTED_TYPE,
                inType);
    }

    // Free temp memory
    psFree(tmpVector);
    psFree(checkVector);

    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;
    }
}
