#include "wabor_t.h"
#include "wabor_t_array_functions.h"



void wabor_t_setup(void){

    
    pcWabor_t = class_new(gensym("wabor_tilde"),
            (t_newmethod)wabor_t_new, (t_method)wabor_t_delete,
            sizeof(sWabor_t),
            CLASS_DEFAULT,
            A_DEFFLOAT, 0);

    class_addmethod(pcWabor_t, (t_method)wabor_t_dsp, gensym("dsp"), 0);
    CLASS_MAINSIGNALIN(pcWabor_t, sWabor_t, fConstSignal);
    class_addbang(pcWabor_t, wabor_t_bang);
    
    //class_addmethod(pcWabor_t, (t_method)wabor_t_test, gensym("test"),  A_DEFFLOAT, 0);
    class_addmethod(pcWabor_t, (t_method)wabor_t_set_compute_coefficients, gensym("set_compute_coefficients"), A_DEFFLOAT, 0);
    class_addmethod(pcWabor_t, (t_method)wabor_t_set_soundbuffer_length_factor, gensym("set_soundbuffer_length_factor"), A_DEFFLOAT, 0);

    class_addmethod(pcWabor_t, (t_method)wabor_t_set_q_analyse_min, gensym("set_q_analyse_min"), A_DEFFLOAT, 0);
    class_addmethod(pcWabor_t, (t_method)wabor_t_set_q_analyse_sup, gensym("set_q_analyse_sup"), A_DEFFLOAT, 0);
    
    class_addmethod(pcWabor_t, (t_method)wabor_t_initialize, gensym("initialize"), A_DEFFLOAT, 0);    
    class_addmethod(pcWabor_t, (t_method)wabor_t_set_var, gensym("set_var"), A_DEFFLOAT, 0);    

    class_addmethod(pcWabor_t, (t_method)wabor_t_set_num_threads, gensym("set_num_threads"), A_DEFFLOAT, 0);    

    class_addmethod(pcWabor_t, (t_method)wabor_t_round_up_to_multiple_of_small_primes, gensym("round_up_to_multiple_of_small_primes"), A_DEFFLOAT, A_DEFFLOAT, 0);

    post("wabor_t external loaded!");
}




void wabor_t_set_pointers_to_NULL(sWabor_t *psW){
    //Setzt alle Pointer auf NULL
    
    psW->x_inC = NULL;
    psW->x_outG = NULL;
    psW->x_outC = NULL;
    psW->x_outM = NULL;
    
    psW->pawc= NULL;
    
    
    psW->sSound.pA=NULL;
    psW->sSound.pC=NULL;
    psW->sSound.pE=NULL;
    
    for(t_int i=0; i<WABOR_T_MAX_THREADS; ++i){
        psW->pawp[i]=NULL;
    }
}
void wabor_t_set_start_values(sWabor_t *psW){
    //Setzt alles auf Startwerte, diese sind nicht notwendigerweise sinnvoll, dürfen aber zu keinen Abstürzen führen
    
    psW->fConstSignal=0.5;
    
    psW->iComputeCoefficients=1;
    
    psW->sSound.iLengthTemp = psW->sSound.iLength=3000*PD_BLOCKSIZE; 
    psW->iLongestFrame=80000;
    psW->q_sup=2000;
    psW->iq_analyse_min=0;
    psW->iq_analyse_sup=2000;

    psW->var=1;
}

void *wabor_t_new(t_floatarg fArg UNUSED){
    
    sWabor_t *psW = (sWabor_t *)pd_new(pcWabor_t);
    
    wabor_t_set_pointers_to_NULL(psW);
    
    psW->x_inC = inlet_new(&(psW->x_obj), &(psW->x_obj.ob_pd), &s_signal, &s_signal);
    psW->x_outG = outlet_new(&(psW->x_obj), &s_signal);
    psW->x_outC = outlet_new(&(psW->x_obj), &s_signal);

    psW->x_outM = outlet_new(&(psW->x_obj), 0); 
    

    psW->num_threads=4;
    psW->iThreadCount=0;    
    INITIALIZE_MUTEX(&psW->mutex);  
    
    wabor_t_set_start_values(psW);        
    wabor_t_initialize(psW);
    psW->sSound.counter=0;

    return (void *)psW;
}

void wabor_t_delete(sWabor_t* psW){
    
        inlet_free(psW->x_inC);
        psW->x_inC=NULL;
    
	outlet_free(psW->x_outG); 
        outlet_free(psW->x_outC); 
        outlet_free(psW->x_outM); 

        wabor_t_initialize_pawc(psW);
        for(t_int i=0; i<WABOR_T_MAX_THREADS; ++i){
            wabor_t_free(psW->pawc[i]);
        }

        wabor_t_free(psW->pawc);
       
        wabor_t_free(psW->sSound.pA);
        
        for(t_int i=0; i<WABOR_T_MAX_THREADS; ++i){
            wabor_t_free(psW->pawp[i]);
        }
        
        //XX pawc wird nicht gefreed. !!!! Boeses memleak
        
        DELETE_MUTEX(&psW->mutex);
        wabor_t_set_pointers_to_NULL(psW);
}

void wabor_t_dsp(sWabor_t* psW, t_signal **sp){
#define WABOR_T_PERFORM_NUM_POINTERS (6)
        dsp_add(wabor_t_perform, WABOR_T_PERFORM_NUM_POINTERS, 
            psW,
            sp[0]->s_vec, 
            sp[1]->s_vec,
            sp[2]->s_vec,
            sp[3]->s_vec,
            sp[0]->s_n
            );
}

t_int *wabor_t_perform(t_int *aw){
    
    sWabor_t    *psW = (sWabor_t *)(aw[1]);
    t_sample    *in1 = (t_sample *)(aw[2]);
    t_sample    *inC = (t_sample *) (aw[3]);
    t_sample    *outC  = (t_sample *)(aw[5]);
    t_sample    *outG  = (t_sample *)(aw[4]);
    t_int       iLength = (t_int)(aw[6]);
    
    
    //Kopieren nach sSound
    t_int counter=psW->sSound.counter;
    if(iLength!=PD_BLOCKSIZE)  {
        error("Fehler in wabor_t_perform - Blocksize falsch"); 
        return(aw+WABOR_T_PERFORM_NUM_POINTERS);
    }
    psW->sSound.pC+=PD_BLOCKSIZE;
    if(psW->sSound.pC==psW->sSound.pE) {
        psW->sSound.pC=psW->sSound.pA;
    }
    memcpy(psW->sSound.pC, in1, PD_BLOCKSIZE * sizeof *(psW->sSound.pA));

    sWatom_Param* param = find_pointer_at_address(&inC[WABOR_T_WATOM_PARAM]);
    
    
    if(   param==NULL || 
          find_int_at_address(&inC[WABOR_T_MAGIC_NUMBER_BIN]) != WABOR_T_MAGIC_NUMBER_C)
    {
        memset(outG,0 , PD_BLOCKSIZE * sizeof *outG);
        memset(outC,0 , PD_BLOCKSIZE * sizeof *outC);
        return aw + WABOR_T_PERFORM_NUM_POINTERS + 1; //Wenn ich nicht mit watom_t verbunden bin
    }
    
    t_int Lm=param->iLongestFrame_paf;
    t_int q_sup=param->iq_compute_sup;
    
    if(   Lm==0 || q_sup==0)
    {
        memset(outG,0 , PD_BLOCKSIZE * sizeof *outG);
        memset(outC,0 , PD_BLOCKSIZE * sizeof *outC);
        return aw + WABOR_T_PERFORM_NUM_POINTERS + 1; //Wenn ich nicht mit watom_t verbunden bin
    }
    
    if(psW->q_sup!=q_sup){
        psW->q_sup=q_sup;
        wabor_t_initialize_pawc(psW);
    }
    if(psW->iq_analyse_sup>q_sup){
        psW->q_sup=q_sup;
        psW->iq_analyse_sup=q_sup;
    }
    if(Lm>psW->iLongestFrame){
        psW->iLongestFrame=Lm;
        wabor_t_initialize_sSound(psW);
    }
    
    psW->psParam=param;
    
    t_int num_threads=psW->num_threads;
    if(psW->iComputeCoefficients) {
        for(t_int i=1; i<num_threads; ++i){
            psW->pawp[i]->iCounter=counter;  
            LOCK( &psW->mutex );          
                ++psW->iThreadCount;        
            UNLOCK( &psW->mutex );
            CREATE_THREAD(psW->thread_id, wabor_t_perform_thread, psW->pawp[i]);
//pthread_create(&(psW->thread_id), NULL, &wabor_t_perform_thread, (void*) psW->patp[i]);
//_beginthread( wabor_t_perform_thread, 0, psW->patp[i]);   
//wabor_t_perform_thread((void*) psW->patp[i]);
        }
        psW->pawp[0]->iCounter=counter;
        LOCK( &psW->mutex );          
            ++psW->iThreadCount;        
        UNLOCK( &psW->mutex );
        wabor_t_perform_thread((void*) psW->pawp[0]);
    } 
    
    psW->sSound.counter+=PD_BLOCKSIZE;

    //Control Werte setzen
    
    if(psW->iComputeCoefficients){
        hide_int_at_address( WABOR_T_MAGIC_NUMBER_G,           &outG[WABOR_T_MAGIC_NUMBER_BIN]);
        hide_pointer_at_address(psW->pawc,                  &outG[WABOR_T_POINTER_TO_COEFF_BIN]);
        hide_int_at_address( psW->num_threads, &outG[WABOR_T_NUM_BLOCKS_BIN]);
    } else {
        memset(outG, 0, PD_BLOCKSIZE * sizeof *outG);
    }
    
    
    hide_int_at_address( WABOR_T_MAGIC_NUMBER_C,           &outC[WABOR_T_MAGIC_NUMBER_BIN]);
    hide_pointer_at_address(psW->psParam,                &outC[WABOR_T_WATOM_PARAM]);
    
    while (psW->iThreadCount > 0) { 
        MUSLEEP(0.1); 
    }
    
    
    return (aw + WABOR_T_PERFORM_NUM_POINTERS + 1);
}

thread_return_type wabor_t_perform_thread( void* _sthread_param ){
    sthread_wabor_param  *ptp=_sthread_param; 
    const t_int j0=ptp->iMin;
    const t_int j1=ptp->iSup;
    t_int iCounter=ptp->iCounter+j0;
    sWabor_t  *psW = ptp->psW; 
    const t_int qmin=psW->iq_analyse_min;
    const t_int qsup=psW->iq_analyse_sup;
    const t_int iBin=ptp->itcBin;
    //struct _sWabor_Coeff *patc = psW->patc;
    paf_type** paf_r=psW->psParam->paf_r;
    paf_type** paf_i=psW->psParam->paf_i;
    tc_type sum_r=0;
    tc_type sum_i=0;
    
    
    //berechne Wabor Werte
    t_int iNumTC=0; //Anzahl Wabor Coeffizienten
    for(t_int j=j0; j<j1; ++j){
        ++iCounter;
        for(t_int q=qmin; q<qsup; ++q){
            if(paf_r[q]!=NULL){
                t_int d=psW->psParam->aiDq[q];
                if(iCounter%d==0){
                    t_int aL=(t_int)paf_r[q][WABOR_T_LENGTH_BIN];
                    t_sample* sR=psW->sSound.pC+j;
                    t_sample* sRE=sR+1;
                    t_sample* sRA=sRE-aL;
                    paf_type* aA_r=&(paf_r[q][0]);
                    paf_type* aA_i=&(paf_i[q][0]);
                    paf_type* aR_r=aA_r+aL-1;
                    paf_type* aR_i=aA_i+aL-1;
                    t_sample* sA=psW->sSound.pA;
                    
                    sum_r=0;
                    sum_i=0;
                    if(sRA>=sA){
                        while(aR_r>=aA_r){
                            sum_r+= (*sR) * (*aR_r);
                            sum_i-= (*sR) * (*aR_i);
                            --sR; --aR_r; --aR_i;
                        }
                    } else {
                        while(sR>=sA){
                            sum_r+= (*sR) * (*aR_r); 
                            sum_i-= (*sR) * (*aR_i);
                            --sR; --aR_r; --aR_i;
                        }
                        sR=psW->sSound.pE-1;
                        while(aR_r>=aA_r){
                            sum_r+= (*sR) * (*aR_r); 
                            sum_i-= (*sR) * (*aR_i);
                            --sR; --aR_r; --aR_i;                            
                        }                        
                    }
                    psW->pawc[iBin][iNumTC].start=j;
                    psW->pawc[iBin][iNumTC].val_r=sum_r/PD_SAMPLING_RATE; //Inneres Produkt ist ein Integral, deshalb muss ich 1/SR multipliozieren
                    psW->pawc[iBin][iNumTC].val_i=sum_i/PD_SAMPLING_RATE;
                    psW->pawc[iBin][iNumTC].band=q;
                    ++iNumTC;
                }
            }
        }
    }
     
    psW->pawc[iBin][0].numCoeff=iNumTC;

    LOCK( &psW->mutex );   
        --psW->iThreadCount;
    UNLOCK( &psW->mutex );
    
    return THREAD_RETURN_VAL;
}


void wabor_t_bang(sWabor_t const * const psW){
    static t_int i=0;
    ++i;
    
    if(i>10 && i<20) post("Why are you banging me?"); 
    if(i>20 && i<26) post("I don't like being banged!"); 
    if(i>26 && i<30) post("WHY ARE YOU BANGING ME?");
    if(i>30) post("tommsch is the best!");
    
    
    post("\n-----------------Wabor--------------:");
    if(psW->psParam!=NULL){
        post("ComputeType: \t%i", (int)psW->psParam->iComputeType);
    }
    post("------------------Not important stuff");
    char* cDate = __DATE__;
    char* cTime = __TIME__;
    post("Buildtime \t%s\t, \t%s",cDate, cTime);
    #if defined (WABOR_T_NOTHREADS)
        post("Multithreading: 1 thread"); 
    #else
        if( psW->num_threads==1) {post("Multithreading: 1 thread"); }
        else {post("Multithreading: \t%i threads",psW->num_threads); }    
    #endif 
    post("Var= \t%f", psW->var);

    return;

}



void wabor_t_test_param(sWabor_t const * const psW){
    if(psW==NULL) error("wabor_t: Pointer to Object==NULL");
    if(sys_getsr()!=44100) error("Samplerate=%f falsch.",sys_getsr());
    return;
}

void wabor_t_initialize(sWabor_t* psW){

    wabor_t_set_temp_values(psW);
    
    //Alles loeschen
    wabor_t_initialize_pawp(psW);
    
    wabor_t_initialize_pawc(psW);
    
    wabor_t_initialize_sSound(psW); wabor_t_free(psW->sSound.pA);       psW->sSound.pA=NULL;
    
    psW->var=1;
    
    //Werte neu berechnen

    //Alles neu machen
    psW->iq_analyse_sup = 98765;
    psW->iq_analyse_min = 0;

    wabor_t_initialize_sSound(psW); 
    wabor_t_initialize_pawc(psW);
    
    wabor_t_test_param(psW);
}

void wabor_t_initialize_pawc(sWabor_t* psW){
    if(psW->pawc==NULL){
        psW->pawc=wabor_t_malloc0(WABOR_T_MAX_THREADS * sizeof *(psW->pawc));
        for(t_int i=0; i<WABOR_T_MAX_THREADS; ++i){
            psW->pawc[i]=wabor_t_malloc0(psW->q_sup*PD_BLOCKSIZE * sizeof *(psW->pawc[i]));
            if(psW->pawc[i]==NULL){
                for(t_int j=0; j<WABOR_T_MAX_THREADS; ++j){
                    wabor_t_free(psW->pawc[j]);
                    psW->pawc[j]=NULL;
                }
                wabor_t_free(psW->pawc);
                psW->pawc=NULL;
                return;
            }
        }
    } else {
        for(t_int i=0; i<WABOR_T_MAX_THREADS; ++i){
            wabor_t_free(psW->pawc[i]);
            psW->pawc[i]=wabor_t_malloc0(psW->q_sup*PD_BLOCKSIZE * sizeof *(psW->pawc[i]));
            if(psW->pawc[i]==NULL){
                for(t_int j=0; j<WABOR_T_MAX_THREADS; ++j){
                    wabor_t_free(psW->pawc[j]);
                    psW->pawc[j]=NULL;
                }
                wabor_t_free(psW->pawc);
                psW->pawc=NULL;
                return;
            }
        }
    }
    return;
}

void wabor_t_initialize_pawp(sWabor_t* psW){
    for(t_int i=0; i<WABOR_T_MAX_THREADS; ++i){
        wabor_t_free(psW->pawp[i]);
        psW->pawp[i]=wabor_t_malloc0(sizeof *(psW->pawp[i]));
    }
    for(t_int i=0;i<WABOR_T_MAX_THREADS;++i){
        psW->pawp[i]->psW=psW;
    }
    for(t_int i=0;i<psW->num_threads;++i){
        psW->pawp[i]->iMin=(PD_BLOCKSIZE*i)/psW->num_threads;
        psW->pawp[i]->iSup=(PD_BLOCKSIZE*(i+1))/psW->num_threads;
        psW->pawp[i]->itcBin=i;
    }
    psW->pawp[psW->num_threads-1]->iSup=PD_BLOCKSIZE;

}

void wabor_t_initialize_sSound(sWabor_t* psW){
    t_int iLongest=psW->iLongestFrame;
    if(iLongest) {
        t_int iSizeAlternate= ((iLongest/PD_BLOCKSIZE)+1)*PD_BLOCKSIZE;
        if(iSizeAlternate>psW->sSound.iLength) {
            psW->sSound.iLength=iSizeAlternate;
        }
    }
    t_int iSize=psW->sSound.iLength;

    wabor_t_free(psW->sSound.pA);
    
    psW->sSound.pA = wabor_t_malloc0(iSize * sizeof *(psW->sSound.pA));
    if(psW->sSound.pA==NULL){
        psW->sSound.pC=NULL;
        psW->sSound.pE=NULL;
        return;
    }
    psW->sSound.pC = psW->sSound.pA;
    psW->sSound.pE = psW->sSound.pA + iSize;
    
    memset(psW->sSound.pA, 0, iSize * sizeof *(psW->sSound.pA));
    
    return;
}

void wabor_t_set_compute_coefficients(sWabor_t *psW, t_float fCompute){
    psW->iComputeCoefficients = fCompute ? 1 : 0;
    return;
}

void wabor_t_set_temp_values(sWabor_t *psW){
    //Aendert alles
    psW->sSound.iLength=psW->sSound.iLengthTemp;
}

void wabor_t_set_soundbuffer_length_factor(sWabor_t *psW, t_float fFactor){
    if(fFactor<1) fFactor=3000;
    psW->sSound.iLengthTemp=(t_int)fFactor*PD_BLOCKSIZE;
    return;   
}
void wabor_t_set_q_analyse_min(sWabor_t *psW, t_float qmin){
    if(qmin<0) {
        psW->iq_analyse_min=0;
        return;
    }
    if(qmin>=psW->iq_analyse_sup){
        psW->iq_analyse_min=psW->iq_analyse_sup-1;
        return;
    }
    psW->iq_analyse_min=qmin;
    return;
}
void wabor_t_set_q_analyse_sup(sWabor_t *psW, t_float qsup){
    if(qsup<=0) {
        psW->iq_analyse_sup=0;
        return;
    }
    if(qsup<=psW->iq_analyse_min){
        psW->iq_analyse_sup=psW->iq_analyse_min+1;
        return;
    }
    if(qsup>=psW->q_sup){
        psW->iq_analyse_sup=psW->q_sup;
        return;
    }
    psW->iq_analyse_sup=qsup;
    return;
}

void wabor_t_set_var(sWabor_t* psW, t_float f){
    psW->var=f;
}


void wabor_t_set_num_threads(sWabor_t* psW, t_float num){
    t_int i=num;
    if(i<1) i=1;
    if(i>WABOR_T_MAX_THREADS) i=WABOR_T_MAX_THREADS;
    psW->num_threads=i;
    wabor_t_initialize_pawp(psW);
}



t_int wabor_t_round_up_to_multiple_of_small_primes(UNUSED sWabor_t const * const psW, t_float n, t_float maxprime){
    t_int val=round_up_to_multiple_of_small_primes((t_int)n,(t_int)maxprime);
    post("Nearest Number with small primes: %i", val);
    return val;
}

