//
// Created by Alberto on 2020-05-06.
//

#include <jni.h>
#include <stdio.h>
#include <complex.h>
#include <fftw3.h>
#include <cblas.h>
#include "defines.h"
#include "FileFunctions.h"
#include "ComputeFunctions.h"
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <android/log.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>


#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    sayHello
 * Signature: ()V
 */
    JNIEXPORT int JNICALL Java_es_uniovi_pruebacsep2_MainActivity_total(JNIEnv *, jobject, jobject assetManager,
                                                                        jstring input, jstring input2);

#ifdef __cplusplus
}
#endif
#endif

JNIEXPORT int JNICALL Java_es_uniovi_pruebacsep2_MainActivity_total(JNIEnv *env, jobject thisObj,jobject assetManager,
                                                                    jstring input, jstring input2) {



    const char *fileName;
    fileName= (*env) -> GetStringUTFChars(env,input,NULL);

    const char *path;
    path= (*env) -> GetStringUTFChars(env,input2,NULL);

    AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);

    AAsset *aasset;
    off_t offset, length;
    int fd;

    if ((aasset = AAssetManager_open(mgr, fileName, AASSET_MODE_UNKNOWN)) == NULL) {
        return 95;
    }

    length = AAsset_getLength(aasset);
    if ((fd = AAsset_openFileDescriptor64(aasset, &offset, &length)) < 0) {
        return ErrOpenFile;
    }


#ifdef DEPURA
    FILE *fpout;
#endif

#ifdef TALK
    double Time, hannfftTime, nmfTime, hsdTime, clusTime, invTime;
     #ifndef OMP
       double dtime;
     #endif
#endif

    int  nFrames, fftSize, nSamples, vCLTSize, maxThreads, *Ncor=NULL, nItera, bc, i,
            winSize, rowsNMF, colBases, planType, NoOverlap,  *Npul=NULL, *Patrones=NULL;

    MyType *yNMF=NULL, *wNMF=NULL, *Times=NULL, *audio=NULL, *corrX=NULL, *XpAUX=NULL, *vHanning=NULL,
            *hNMF=NULL, *SIM=NULL,  *rSNMF=NULL, *Bases=NULL, *xAUXI=NULL, *XcAUX=NULL, *Ts=NULL, dtmp;

    MyFFTcompl   *xIFFT=NULL,   *cSNMF=NULL, *vCLT=NULL, *Xp=NULL, *Xc=NULL;
    MyFFTCPUType *planFFT=NULL, *planIFFT=NULL;

    bool          esWAV=false;
    FILE          *fpin, *fpout;
    short         *wavAudio=NULL;
    WAVHeader     WavHeader;
    unsigned char RIFF[4];



    if ((fpin = fdopen(fd, "rb")) == NULL) {
        return ErrNULL;
    }
    if ((fseek(fpin, offset, SEEK_SET)) < 0) {
        return ErrOpenFile;
    }


    if (fread(RIFF, 1, 4, fpin) != 4) { fclose(fpin); return ErrReadFile; }
    if(RIFF[0]=='R' && RIFF[1]=='I' && RIFF[2]=='F' && RIFF[3]=='F')
    {
        /* Es fichero tipo wav compatible  */
        __android_log_print(ANDROID_LOG_ERROR, "Desde CCCC","HOLAa *%c*",RIFF);
        CHECKERR(ReadWavHeader(&WavHeader, fpin));
        nSamples=WavHeader.num_samples;

        CHECKNULL(wavAudio=(short *)calloc(nSamples, sizeof(short)));
        CHECKNULL(audio=  (MyType *)calloc(nSamples, sizeof(MyType)));

        CHECKERR(ReadAudioWav(nSamples, wavAudio, audio, fpin));
        __android_log_print(ANDROID_LOG_ERROR, "AUDIO","AUDIO%f ",audio[23]);

#ifdef DEPURA
       CHECKNULL(fpout=fopen("HOLA", "wb"));
       if (fwrite(RIFF, 1, 4, fpout) != 4) { fclose(fpout); return ErrReadFile; }
       CHECKERR(WriteWavHeader(WavHeader.header,  fpout));
       CHECKERR(WriteAudioWav(nSamples, wavAudio, fpout));
#endif

    }
    else
    {

        /* Es fichero tipo RAW data, binario plano */
        nSamples=RIFF[0] | (RIFF[1]<<8) | (RIFF[2]<<16) | (RIFF[3]<<24);

        CHECKNULL(audio=(MyType *)calloc(nSamples, sizeof(MyType)));

        CHECKERR(ReadAudio(nSamples, audio, fpin));
#ifdef DEPURA
        CHECKNULL(fpout=fopen("HOLA", "wb"));
       if (fwrite(&nSamples, sizeof(int), 1, fpout) != 1) { fclose(fpout); return ErrReadFile; };
       CHECKERR(WriteAudio(nSamples, audio, fpout));
#endif
    }
    CHECKERR(fclose(fpin));
#ifdef DEPURA
    CHECKERR(fclose(fpout));
#endif



    winSize   = freqHz*seconds;	       // 8000 x 0.064 = 512
    fftSize   = nextPow2(winSize);      // 512
    NoOverlap = round(fftSize*overlap); // 256
    rowsNMF   = NoOverlap+1;	       // 257
    bc        = bcCorazon+bcPulmon;     // 55 (bcCorazon) + 64(bcPulmon) = 119
    nFrames   = floor((nSamples-NoOverlap)/(winSize-NoOverlap));




    //Leer fichero de bases
    if ((aasset = AAssetManager_open(mgr, "Bases64_4.dat", AASSET_MODE_UNKNOWN)) == NULL) {
        return 95;
    }

    length = AAsset_getLength(aasset);
    __android_log_print(ANDROID_LOG_ERROR, "length","length%d ",length);
    if ((fd = AAsset_openFileDescriptor(aasset, &offset, &length)) < 0) {
        return ErrOpenFile;
    }
    if ((fpin = fdopen(fd, "rb")) == NULL) {
        return ErrNULL;
    }
    if ((fseek(fpin, offset, SEEK_SET)) < 0) {
        return ErrOpenFile;
    }

    if (fread(&i,        sizeof(int), 1, fpin) != 1) { fclose(fpin); return ErrReadFile; }
    if (fread(&colBases, sizeof(int), 1, fpin) != 1) { fclose(fpin); return ErrReadFile; }
    if (i != rowsNMF) { printf("Error Bases dimensions\n"); fclose(fpin); return ErrReadFile; }

    CHECKNULL(Bases=(MyType *)calloc(rowsNMF*colBases, sizeof(MyType)));
    if (fread(Bases, sizeof(MyType), rowsNMF*colBases, fpin) != rowsNMF*colBases) { fclose(fpin); return ErrReadFile; }
    CHECKERR(fclose(fpin));


    /* Incializamos el generador de aleatorios */
    srand(13);
    planType=FFTW_ESTIMATE;

    #ifdef OMP
        //omp_set_num_threads(8);
        maxThreads=omp_get_max_threads();

    #else
        maxThreads=1;
    #endif

    #ifdef TALK
        printf("(nSamples, nFrames, winSize, fftSize)=(%d, %d, %d, %d)\n", nSamples, nFrames, winSize, fftSize);
        __android_log_print(ANDROID_LOG_ERROR,"TALK" ,"(nSamples, nFrames, winSize, fftSize, maxThreads)=(%d, %d, %d, %d, %d)",nSamples, nFrames, winSize, fftSize,maxThreads);
    #endif


    CHECKNULL(vHanning=(MyType *)calloc(winSize, sizeof(MyType)));

    CHECKNULL(   rSNMF=(MyType *)calloc(rowsNMF * nFrames, sizeof(MyType)));
    CHECKNULL(    yNMF=(MyType *)calloc(rowsNMF * nFrames, sizeof(MyType)));
    CHECKNULL(    wNMF=(MyType *)calloc(rowsNMF * bc,      sizeof(MyType)));
    CHECKNULL(    hNMF=(MyType *)calloc(bc      * nFrames, sizeof(MyType)));
    CHECKNULL(      Ts=(MyType *)calloc(nFrames,           sizeof(MyType)));
    CHECKNULL(Patrones=(int    *)calloc(nFrames,           sizeof(int)));

    #ifdef SIMPLE
        CHECKNULL(cSNMF=(MyFFTcompl *)fftwf_malloc(rowsNMF*nFrames*sizeof(MyFFTcompl)));
    #else
        CHECKNULL(cSNMF=(MyFFTcompl *) fftw_malloc(rowsNMF*nFrames*sizeof(MyFFTcompl)));
    #endif



    CHECKERR(hanning(winSize, vHanning));

    #ifdef TALK
        Time=Ctimer();
    #endif
        CHECKERR(cHannFFT(winSize, nFrames, NoOverlap, audio, vHanning, rowsNMF, rSNMF, cSNMF, fftSize, maxThreads, planType));
    #ifdef TALK
        hannfftTime=Ctimer()-Time;
    #endif


    #ifdef TALK
        nmfTime=Ctimer();
    #endif
        CHECKERR(uNMF(rowsNMF, nFrames, bc, rSNMF, wNMF, hNMF, yNMF, &nItera));
    #ifdef TALK
        nmfTime=Ctimer()-nmfTime;
    #endif

    dtmp=(MyType)(winSize-NoOverlap)/(MyType)freqHz;
    #ifdef OMP2
        #pragma omp parallel for simd  // ¿overhead > saved time.?
    #else
        #pragma GCC ivdep
    #endif
    for(i=0;i<nFrames;i++) { Ts[i]=i*dtmp; }


    #ifdef TALK
        hsdTime=Ctimer();
    #endif
    CHECKERR(HSD(nSamples, winSize, audio, Ts, &Times, Patrones, maxThreads, planType));
    #ifdef TALK
        hsdTime=Ctimer()-hsdTime;
    #endif

    vCLTSize=nextPow2(nFrames*(winSize-NoOverlap)+NoOverlap);

    /* Para la IFFT y FFT del calculo Roll-Off */
#ifdef SIMPLE
    CHECKNULL(      Xp=(MyFFTcompl   *)fftwf_malloc(sizeof(MyFFTcompl)   * rowsNMF  * nFrames));
     CHECKNULL(      Xc=(MyFFTcompl   *)fftwf_malloc(sizeof(MyFFTcompl)   * rowsNMF  * nFrames));
     CHECKNULL(   xIFFT=(MyFFTcompl   *)fftwf_malloc(sizeof(MyFFTcompl)   * fftSize  * maxThreads));
     CHECKNULL(    vCLT=(MyFFTcompl   *)fftwf_malloc(sizeof(MyFFTcompl)   * vCLTSize * maxThreads));
     CHECKNULL(planIFFT=(MyFFTCPUType *)fftwf_malloc(sizeof(MyFFTCPUType) * maxThreads));
     CHECKNULL( planFFT=(MyFFTCPUType *)fftwf_malloc(sizeof(MyFFTCPUType) * maxThreads));
#else
    CHECKNULL(      Xp=(MyFFTcompl    *)fftw_malloc(sizeof(MyFFTcompl)   * rowsNMF  * nFrames));
    CHECKNULL(      Xc=(MyFFTcompl    *)fftw_malloc(sizeof(MyFFTcompl)   * rowsNMF  * nFrames));
    CHECKNULL(   xIFFT=(MyFFTcompl    *)fftw_malloc(sizeof(MyFFTcompl)   * fftSize  * maxThreads));
    CHECKNULL(    vCLT=(MyFFTcompl    *)fftw_malloc(sizeof(MyFFTcompl)   * vCLTSize * maxThreads));
    CHECKNULL(planIFFT=(MyFFTCPUType  *)fftw_malloc(sizeof(MyFFTCPUType) * maxThreads));
    CHECKNULL( planFFT=(MyFFTCPUType  *)fftw_malloc(sizeof(MyFFTCPUType) * maxThreads));
#endif

    for(i=0;i<maxThreads;i++)
    {
#ifdef SIMPLE
        planIFFT[i]=fftwf_plan_dft_1d(fftSize,  &xIFFT[i*fftSize], &xIFFT[i*fftSize], FFTW_BACKWARD, planType);
       planFFT[i] =fftwf_plan_dft_1d(vCLTSize, &vCLT[i*vCLTSize], &vCLT[i*vCLTSize], FFTW_FORWARD,  planType);
#else
        planIFFT[i]=fftw_plan_dft_1d (fftSize,  &xIFFT[i*fftSize], &xIFFT[i*fftSize], FFTW_BACKWARD, planType);
        planFFT[i] =fftw_plan_dft_1d (vCLTSize, &vCLT[i*vCLTSize], &vCLT[i*vCLTSize], FFTW_FORWARD,  planType);
#endif
    }

    CHECKNULL(xAUXI=(MyType*)calloc(rowsNMF*nFrames*maxThreads, sizeof(MyType)));
    CHECKNULL(XcAUX=(MyType*)calloc(rowsNMF*nFrames*maxThreads, sizeof(MyType)));
    CHECKNULL(XpAUX=(MyType*)calloc(rowsNMF*nFrames*maxThreads, sizeof(MyType)));
    CHECKNULL(SIM  =(MyType*)calloc(colBases*maxThreads,        sizeof(MyType)));
    CHECKNULL(corrX=(MyType*)calloc(nFrames*maxThreads,         sizeof(MyType)));
    CHECKNULL(Ncor =(int   *)calloc(maxThreads,                 sizeof(int)));
    CHECKNULL(Npul =(int   *)calloc(maxThreads,                 sizeof(int)));

#ifdef TALK
    clusTime=Ctimer();
#endif
#ifdef OMP
#pragma omp parallel num_threads(maxThreads)
#endif
    {
        int    myID, k;
        MyType freqRollOff, maxSIM, coefCorr;

#ifdef OMP
        myID=omp_get_thread_num();
#else
        myID=0;
#endif

#ifdef OMP
#pragma omp for
#endif
        for(k=0;k<bc;k++)
        {
            memSetValueRealComplex(vCLTSize, 0, &vCLT[myID*vCLTSize]);
            invSpectroExtended(rowsNMF, nFrames, bc, winSize, fftSize, NoOverlap, vHanning, &wNMF[k*rowsNMF], &hNMF[k],
                               &xAUXI[myID*rowsNMF*nFrames], &vCLT[myID*vCLTSize], &xIFFT[myID*fftSize], planIFFT[myID]);

#ifdef SIMPLE
            fftwf_execute(planFFT[myID]);
#else
            fftw_execute (planFFT[myID]);
#endif
            freqRollOff=RollOff(vCLTSize, &vCLT[myID*vCLTSize], energyThreshold);

            maxSIM=corrCos(rowsNMF, colBases, &wNMF[k*rowsNMF], Bases, &SIM[myID*colBases]);

            coefCorr=corrPatron(bc, nFrames, &hNMF[k], Patrones, &corrX[myID*nFrames]);

            if((maxSIM > simThreshold) && (freqRollOff < fcThreshold) && (coefCorr > coefcoThreshold))
            {
                suma(rowsNMF*nFrames, &XcAUX[myID*rowsNMF*nFrames], &xAUXI[myID*rowsNMF*nFrames]);
                Ncor[myID]++;
            }
            else
            {
                suma(rowsNMF*nFrames, &XpAUX[myID*rowsNMF*nFrames], &xAUXI[myID*rowsNMF*nFrames]);
                Npul[myID]++;
            }
        }
    }

    for(i=1;i<maxThreads;i++)
    {
        Ncor[0]+=Ncor[i]; Npul[0]+=Npul[i];
#ifdef SIMPLE
        cblas_saxpy(rowsNMF*nFrames, 1.0, &XpAUX[i*rowsNMF*nFrames], 1, XpAUX, 1);
       cblas_saxpy(rowsNMF*nFrames, 1.0, &XcAUX[i*rowsNMF*nFrames], 1, XcAUX, 1);
#else
        cblas_daxpy(rowsNMF*nFrames, 1.0, &XpAUX[i*rowsNMF*nFrames], 1, XpAUX, 1);
        cblas_daxpy(rowsNMF*nFrames, 1.0, &XcAUX[i*rowsNMF*nFrames], 1, XcAUX, 1);
#endif
    }
#ifdef TALK
    clusTime=Ctimer()-clusTime;
#endif


    char* temp="/";
    char * str3 = (char *) malloc(1 + strlen(path)+ strlen(temp) );
    strcpy(str3, path);
    strcat(str3, temp);

    int result_code = mkdir(str3, 0770);

#ifdef TALK
    invTime=Ctimer();
#endif
    ReconCorazon(rowsNMF*nFrames, Xc, Xp, cSNMF, XcAUX, XpAUX);
#ifdef OMP
#pragma omp parallel sections num_threads(2)
    {
#pragma omp section
        invSpectro(rowsNMF, nFrames, winSize, fftSize, NoOverlap, vHanning, Xc, &vCLT[0], &xIFFT[0], planIFFT[0]);
#pragma omp section
        invSpectro(rowsNMF, nFrames, winSize, fftSize, NoOverlap, vHanning, Xp, &vCLT[vCLTSize], &xIFFT[fftSize], planIFFT[1]);
    }
#ifdef TALK
    invTime=Ctimer()-invTime;
#endif

    temp="/Corazon.wav";
    str3 = (char *) malloc(1 + strlen(path)+ strlen(temp) );
    strcpy(str3, path);
    strcat(str3, temp);
        CHECKNULL(fpout=fopen(str3, "wb"));
        CHECKERR(WriteWavHeader(WavHeader.header, RIFF, fpout));
        CHECKERR(WriteAudioWav(nSamples, nFrames*(winSize-NoOverlap)+NoOverlap, wavAudio, &vCLT[0], fpout));
        CHECKERR(fclose(fpout));

    temp="/Pulmon.wav";
    str3 = (char *) malloc(1 + strlen(path)+ strlen(temp) );
    strcpy(str3, path);
    strcat(str3, temp);
    __android_log_print(ANDROID_LOG_ERROR, "path","path%s ",str3);
        CHECKNULL(fpout=fopen(str3, "wb"));
        CHECKERR(WriteWavHeader(WavHeader.header, RIFF, fpout));
        CHECKERR(WriteAudioWav(nSamples, nFrames*(winSize-NoOverlap)+NoOverlap, wavAudio, &vCLT[vCLTSize], fpout));
        CHECKERR(fclose(fpout));
        free(wavAudio);


#else
    invSpectro(rowsNMF, nFrames, winSize, fftSize, NoOverlap, vHanning, Xc, vCLT, xIFFT, planIFFT[0]);
     #ifdef TALK
       invTime=Ctimer()-invTime;
     #endif

           temp="/Corazon.wav";
    str3 = (char *) malloc(1 + strlen(path)+ strlen(temp) );
    strcpy(str3, path);
    strcat(str3, temp);
        CHECKNULL(fpout=fopen(str3, "wb"));

       CHECKERR(WriteWavHeader(WavHeader.header, RIFF, fpout));
       CHECKERR(WriteAudioWav(nSamples, nFrames*(winSize-NoOverlap)+NoOverlap, wavAudio, vCLT, fpout));
       CHECKERR(fclose(fpout));


     #ifdef TALK
       dtime=Ctimer();
     #endif
     invSpectro(rowsNMF, nFrames, winSize, fftSize, NoOverlap, vHanning, Xp, vCLT, xIFFT, planIFFT[0]);
     #ifdef TALK
       invTime+=Ctimer()-dtime;
     #endif
    temp="/Pulmon.wav";
    str3 = (char *) malloc(1 + strlen(path)+ strlen(temp) );
    strcpy(str3, path);
    strcat(str3, temp);
    __android_log_print(ANDROID_LOG_ERROR, "path","path%s ",str3);
        CHECKNULL(fpout=fopen(str3, "wb"));
       CHECKERR(WriteWavHeader(WavHeader.header, RIFF, fpout));
       CHECKERR(WriteAudioWav(nSamples, nFrames*(winSize-NoOverlap)+NoOverlap, wavAudio, vCLT, fpout));
       CHECKERR(fclose(fpout));
       free(wavAudio);

#endif
#ifdef TALK
    Time = Ctimer() - Time;
#endif

    //Estimación de la FC con la opción 1.
    int s=BMP1(rowsNMF,nFrames,Xc,fftSize);


#ifdef TALK


        printf("Iteraciones NMF %d.\n", nItera);
        __android_log_print(ANDROID_LOG_ERROR, "TALK", "Iteraciones NMF %d.\n", nItera);
        printf("Hann+FFT %1.5e sec. uNMF %1.5e sec. HSD %1.5e sec. INV %1.5e sec. Todo %1.5e sec.\n",
               hannfftTime, nmfTime, hsdTime, invTime, Time);
        __android_log_print(ANDROID_LOG_ERROR, "TALK",
                            "Fase1 %1.5e sec. Fase2 %1.5e sec. Fase3 %1.5e sec. Fase4 %1.5e sec. Fase5 %1.5e Todo %1.5e sec.\n",
                            hannfftTime, nmfTime, hsdTime, clusTime, invTime, Time);
#endif


        /* Memory free */
        free(Ncor);
        free(SIM);
        free(rSNMF);
        free(wNMF);
        free(Times);
        free(audio);
        free(XcAUX);
        free(Patrones);
        free(Ts);
        free(Npul);
        free(yNMF);
        free(corrX);
        free(hNMF);
        free(Bases);
        free(xAUXI);
        free(XpAUX);
        free(vHanning);

        for (i = 0; i < maxThreads; i++) {
#ifdef SIMPLE
            fftwf_destroy_plan(planIFFT[i]);
           fftwf_destroy_plan(planFFT[i]);
#else
            fftw_destroy_plan(planIFFT[i]);
            fftw_destroy_plan(planFFT[i]);
#endif
        }
#ifdef SIMPLE
        fftwf_free(vCLT);
         fftwf_free(planIFFT);
         fftwf_free(planFFT);
         fftwf_free(xIFFT);
         fftwf_free(cSNMF);
         fftwf_free(Xp);
         fftwf_free(Xc);
#else
        fftw_free(vCLT);
        fftw_free(planIFFT);
        fftw_free(planFFT);
        fftw_free(xIFFT);
        fftw_free(cSNMF);
        fftw_free(Xp);
        fftw_free(Xc);
#endif

        return s;
    }

