 #include "invwabor_t.h"

void invwabor_t_setup(void){
    
    if(invwabor_t_test_compileparam()) {
        error("External iWabor: Plugin Compiled with wrong parameters."); //Testet alle Parameter ob sie richtig sind    
        return; 
    }
    pcinvWabor_t = class_new(gensym("invwabor_tilde"),
            (t_newmethod)invwabor_t_new, (t_method)invwabor_t_delete,
            sizeof(sinvWabor_t),
            CLASS_DEFAULT,
            A_DEFFLOAT, 0);
    
    class_addmethod(pcinvWabor_t, (t_method)invwabor_t_dsp, gensym("dsp"), 0);
    CLASS_MAINSIGNALIN(pcinvWabor_t, sinvWabor_t, fConstSignal);
    class_addbang(pcinvWabor_t, invwabor_t_bang);
    class_addmethod(pcinvWabor_t, (t_method)invwabor_t_set_soundbuffer_length_factor, gensym("set_soundbuffer_length_factor"), A_DEFFLOAT, 0);
    class_addmethod(pcinvWabor_t, (t_method)invwabor_t_set_num_threads, gensym("set_num_threads"), A_DEFFLOAT, 0);
    class_addmethod(pcinvWabor_t, (t_method)invwabor_t_set_compute_synthesis, gensym("set_compute_synthesis"), A_DEFFLOAT, 0);
    
}

void *invwabor_t_new(t_floatarg fArg UNUSED){
    sinvWabor_t *psW = (sinvWabor_t *)pd_new(pcinvWabor_t);
    
    invwabor_t_set_pointers_to_NULL(psW);

    psW->fConstSignal=0;
    
    psW->x_inC = inlet_new(&(psW->x_obj), &(psW->x_obj.ob_pd), &s_signal, &s_signal);
    psW->x_inf = floatinlet_new(&psW->x_obj, &psW->fMinValforCount); 
    psW->x_outS = outlet_new(&(psW->x_obj), &s_signal);
    psW->x_outM = outlet_new(&(psW->x_obj), &s_float);
    
    
    
    psW->sSound.iLength=5000*PD_BLOCKSIZE;
    
    psW->fMinValforCount=600;
    psW->iComputeSynthesis=1;

    psW->num_threads=4;
    psW->iThreadCount=0;
    INITIALIZE_MUTEX(&psW->mutex);
    
    invwabor_t_initialize_sSound(psW);
    
    
    return (void *)psW;
}

void invwabor_t_set_pointers_to_NULL(sinvWabor_t* psW){
    for(t_int i=0; i<WABOR_T_MAX_THREADS; ++i){
        psW->sSound.paA[i]=NULL;
        psW->sSound.paC[i]=NULL;
        psW->sSound.paE[i]=NULL;
        psW->painvwp[i]=NULL;
    }  
    
    psW->x_inC = NULL;
    psW->x_inf = NULL;
    psW->x_outS = NULL;
    psW->x_outM = NULL;
    psW->paf=NULL;
    
    return;
}

void invwabor_t_delete(sinvWabor_t* psW){
    inlet_free(psW->x_inC);
    psW->x_inC=NULL;
    inlet_free(psW->x_inf);
    psW->x_inf=NULL;
    outlet_free(psW->x_outS);
    psW->x_outS=NULL;    
    outlet_free(psW->x_outM);
    psW->x_outM=NULL;

    DELETE_MUTEX(&psW->mutex);    

    for(t_int i=0; i<WABOR_T_MAX_THREADS; ++i) {
        wabor_t_free(psW->sSound.paA[i]);
        psW->sSound.paA[i]=NULL;
        psW->sSound.paC[i]=NULL;
        psW->sSound.paE[i]=NULL;
    }
    
}  

void invwabor_t_dsp(sinvWabor_t* psW, t_signal **sp){
#define IWABOR_T_PERFORM_NUM_POINTERS (4)
    dsp_add(invwabor_t_perform, IWABOR_T_PERFORM_NUM_POINTERS,
            psW,
            sp[0]->s_vec,
            sp[1]->s_vec,
            sp[2]->s_vec
            );
}

t_int *invwabor_t_perform(t_int *aw){
    sinvWabor_t *psW= (sinvWabor_t *)(aw[1]);
    t_sample    *inG   =  (t_sample *)(aw[2]);
    t_sample    *inC   =  (t_sample *)(aw[3]);
    t_sample    *out1  =  (t_sample *)(aw[4]);
    
    sWatom_Param* param=find_pointer_at_address(&inC[WABOR_T_WATOM_PARAM]);
    sWabor_Coeff **patc = find_pointer_at_address(&inG[WABOR_T_POINTER_TO_COEFF_BIN]);
    
    if(psW->iComputeSynthesis==0) { 
        memset(out1,0 , PD_BLOCKSIZE * sizeof *out1);
        return aw + IWABOR_T_PERFORM_NUM_POINTERS + 1;
    }
    
    if(   patc==NULL ||
          param==NULL ||
          find_int_at_address(&inC[WABOR_T_MAGIC_NUMBER_BIN]) != WABOR_T_MAGIC_NUMBER_C  ||
          find_int_at_address(&inG[WABOR_T_MAGIC_NUMBER_BIN]) != WABOR_T_MAGIC_NUMBER_G  )
    {
        memset(out1,0 , PD_BLOCKSIZE * sizeof *out1);
        return aw + IWABOR_T_PERFORM_NUM_POINTERS + 1; //Wenn ich nicht mit wabor_t verbunden bin
    }
    
    t_int Lm=param->iLongestFrame_paf;
    t_int q_sup=param->iq_compute_sup;
    paf_type** paf_r = param->paf_r;
    paf_type** paf_i = param->paf_i;
    
    if(   Lm<=0 || q_sup<=0 || paf_r==NULL)
    {
        memset(out1,0 , PD_BLOCKSIZE * sizeof *out1);
        return aw + IWABOR_T_PERFORM_NUM_POINTERS + 1; //Wenn ich nicht mit wabor_t verbunden bin
    }
    
    if(Lm > psW->sSound.iLength) {
        psW->sSound.iLength=(4*Lm/((t_int)PD_BLOCKSIZE))*PD_BLOCKSIZE;
        invwabor_t_initialize_sSound(psW);
        if(DEBUG) post("inv_wabor: Neues laengstes Frame");
    }

    
    t_int num_blocks=find_int_at_address(&inG[WABOR_T_NUM_BLOCKS_BIN]);
    
    t_int num_threads=psW->num_threads;
    for(t_int i=0; i<num_threads; ++i){
        psW->painvwp[i]->sC=psW->sSound.paC[i];
        psW->painvwp[i]->Lm=Lm;
        psW->painvwp[i]->paf_r=paf_r;
        psW->painvwp[i]->paf_i=paf_i;
        psW->painvwp[i]->patc=patc;
        psW->painvwp[i]->qMin=i*q_sup/num_threads;
        psW->painvwp[i]->qSup=(i+1)*q_sup/num_threads;    
        psW->painvwp[i]->num_blocks=num_blocks;
    }        
        

    for(t_int i=1; i<num_threads; ++i){
        LOCK( &psW->mutex );          
            ++psW->iThreadCount;        
        UNLOCK( &psW->mutex );
        CREATE_THREAD(psW->thread_id, invwabor_t_perform_thread, psW->painvwp[i]);
        //_beginthread( invwabor_t_perform_thread, 0, psW->painvtp[i]);   
        //pthread_create(&(psW->thread_id), NULL, &invwabor_t_perform_thread, (void*) psW->painvtp[i]);
        //invwabor_t_perform_thread(psW->painvtp[0]);      
    }
    LOCK (&psW->mutex);
        ++psW->iThreadCount;        
    UNLOCK (&psW->mutex);
    invwabor_t_perform_thread(psW->painvwp[0]);
     
    while (psW->iThreadCount > 0) { 
        MUSLEEP(0.1); 
    }        

    
    //Zum Ausgang kopieren
    sound_type sum;
    for(t_int i=0; i<PD_BLOCKSIZE; ++i) {
        sum=0;
        for( t_int j=0; j<num_threads; ++j){

            sum+= *(psW->sSound.paC[j]+i);
        }
        //out1[i]=(t_sample)(2.0*sum); //mal 2, weil ich nur die Haelfte aller Koeffizieten berechne (nicht die negativen)
        out1[i]=(t_sample)(sum); //mal 2, weil ich nur die Haelfte aller Koeffizieten berechne (nicht die negativen)
    }
    for(t_int i=0; i<num_threads; ++i){
        memset(psW->sSound.paC[i],0,PD_BLOCKSIZE * sizeof *(psW->sSound.paC[i]));
        psW->sSound.paC[i]+=PD_BLOCKSIZE;
        if(psW->sSound.paC[i]==psW->sSound.paE[i]) {
           psW->sSound.paC[i]=psW->sSound.paA[i];
        }
    }
    
    outlet_float(psW->x_outM, (t_float)(Lm-1) ); //Ausgabe des laengsten Frames minus 1, da ich eins Überstand lassen kann, und ich habe immer noch perfect reconstruction
    return aw + IWABOR_T_PERFORM_NUM_POINTERS + 1;
}

thread_return_type invwabor_t_perform_thread(void *param){
    struct _sthread_invwabor_param const *p=param;
    t_int qMin=p->qMin;
    t_int qSup=p->qSup;
    struct _sWabor_Coeff  * const * const patc=p->patc;
    struct _sWabor_Coeff const * atc;
    sound_type *sA=p->sA; sound_type *sC=p->sC; sound_type *sE=p->sE;
    sinvWabor_t* psW=p->psW;
    paf_type **paf_r=p->paf_r;
    paf_type **paf_i=p->paf_i;
    
   
    t_int num_blocks=p->num_blocks;
    for(t_int k=0; k<num_blocks; ++k){
        atc=patc[k];
        t_int iNumTC=atc[0].numCoeff;
 
        t_int sL=sE-sA;

        sound_type dMinValforCount UNUSED = psW->fMinValforCount*1e-6;
        
        while(iNumTC--){
            struct _sWabor_Coeff const *tc=&(atc[iNumTC]);
            t_int q=tc->band;
            if(q<qMin || q>=qSup) {continue; }
            paf_type *af_r=paf_r[q];
            paf_type *af_i=paf_i[q];
            
            const sound_type val_r= tc->val_r; //damit ich unten das gesamte Signal mal 2 multiplizieren kann
            const sound_type val_i= tc->val_i;

            const t_int startT=tc->start;
            t_int aL_r=(t_int)af_r[WABOR_T_LENGTH_BIN];
            sound_type *sW;
            sound_type *sWA=sC+p->Lm+startT-aL_r;

            sound_type *sWE=sWA+aL_r;
            paf_type *aA_r=&(af_r[0]);
            paf_type *aA_i=&(af_i[0]);
            paf_type *aE_r=aA_r+aL_r;
            paf_type *aR_r=aA_r;
            paf_type *aR_i=aA_i;
            if(val_r==0 && val_i==0){
                
            } else if(val_r!=0 && val_i==0){
                if(sWE<=sE){
                    sW=sWA;
                    while(aR_r<aE_r){
                        *sW=*sW + val_r* (*aR_r); 
                        sW+=1;aR_r+=1;aR_i+=1;
                    }
                } else if (sWA>=sE) {
                    sW=sWA-sL;
                    while(aR_r<aE_r){
                        *sW=*sW + val_r* (*aR_r); 
                        sW+=1;aR_r+=1;aR_i+=1;
                    }
                } else {
                    sW=sWA;
                    while(sW<sE) {
                        *sW=*sW + val_r * (*aR_r); 
                        sW+=1;aR_r+=1;aR_i+=1;                
                    }
                    sW=sA;
                     while(aR_r<aE_r) {
                        *sW=*sW + val_r* (*aR_r);
                        sW+=1;aR_r+=1;aR_i+=1;                
                    }
                }                
            } else if(val_r==0 && val_i!=0){
                if(sWE<=sE){
                    sW=sWA;
                    while(aR_r<aE_r){
                        *sW=*sW - val_i * (*aR_i); 
                        sW+=1;aR_r+=1;aR_i+=1;
                    }
                } else if (sWA>=sE) {
                    sW=sWA-sL;
                    while(aR_r<aE_r){
                        *sW=*sW - val_i * (*aR_i); 
                        sW+=1;aR_r+=1;aR_i+=1;
                    }
                } else {
                    sW=sWA;
                    while(sW<sE) {
                        *sW=*sW - val_i * (*aR_i); 
                        sW+=1;aR_r+=1;aR_i+=1;                
                    }
                    sW=sA;
                     while(aR_r<aE_r) {
                        *sW=*sW - val_i * (*aR_i);
                        sW+=1;aR_r+=1;aR_i+=1;                
                    }
                }                
            } else {
                if(sWE<=sE){
                    sW=sWA;
                    while(aR_r<aE_r){
                        *sW=*sW + val_r* (*aR_r) - val_i * (*aR_i); 
                        sW+=1;aR_r+=1;aR_i+=1;
                    }
                } else if (sWA>=sE) {
                    sW=sWA-sL;
                    while(aR_r<aE_r){
                        *sW=*sW + val_r* (*aR_r) - val_i * (*aR_i); 
                        sW+=1;aR_r+=1;aR_i+=1;
                    }
                } else {
                    sW=sWA;
                    while(sW<sE) {
                        *sW=*sW + val_r * (*aR_r) - val_i * (*aR_i); 
                        sW+=1;aR_r+=1;aR_i+=1;                
                    }
                    sW=sA;
                     while(aR_r<aE_r) {
                        *sW=*sW + val_r* (*aR_r) - val_i * (*aR_i);
                        sW+=1;aR_r+=1;aR_i+=1;                
                    }
                }                
            }
    
        }
    }
    
    LOCK( &psW->mutex );          
        --psW->iThreadCount;        
    UNLOCK( &psW->mutex );
    
    return THREAD_RETURN_VAL;

}

void invwabor_t_bang(sinvWabor_t *psW){
    post("---------------------\ninvWabor:");
    post("fConstSignal: %f", psW->fConstSignal);
    post("iLength: %i", psW->sSound.iLength);
    post("fMinValforCount: %f", psW->fMinValforCount);
    if( psW->num_threads==1) {post("Multithreading: 1 thread"); }
    else {post("Multithreading: \t%i threads",psW->num_threads); }  
    post("Compute Synthesis: %i",psW->iComputeSynthesis);
    return;
}

t_int invwabor_t_test_compileparam(){
    if(sizeof(void*) > sizeof(t_int)) {error("Pointer zu gross (%i bytes)", (int)sizeof(void*)); return 1; }
    return 0;
}


void invwabor_t_initialize_sSound(sinvWabor_t* psW){
    t_int iSize=psW->sSound.iLength;
    t_int num_threads=psW->num_threads;
    for(t_int i=0; i<WABOR_T_MAX_THREADS;++i){
        wabor_t_free(psW->sSound.paA[i]);
        psW->sSound.paA[i]=NULL;
        psW->sSound.paC[i]=NULL;
        psW->sSound.paE[i]=NULL;
    }
    
    for(t_int i=0; i<num_threads; ++i){
        wabor_t_free(psW->sSound.paA[i]);
        psW->sSound.paA[i] = wabor_t_malloc0(iSize * sizeof *(psW->sSound.paA[i]));
    }
    for(t_int i=0; i<num_threads; ++i){
        if(psW->sSound.paA[i]==NULL){
            for( t_int j=0; j<WABOR_T_MAX_THREADS; ++j){
                psW->sSound.paA[j]=NULL;
                psW->sSound.paC[j]=NULL;
                psW->sSound.paE[j]=NULL;
            return;
            }
            
        }
    }
    for( t_int i=0; i<num_threads; ++i){
        psW->sSound.paC[i] = psW->sSound.paA[i];
        psW->sSound.paE[i] = psW->sSound.paA[i] + iSize;
        memset(psW->sSound.paA[i], 0, iSize * sizeof *(psW->sSound.paA[i]));
    }
    
    invwabor_t_initialize_invwabor_param(psW);
    
    return;
}

void invwabor_t_initialize_invwabor_param(sinvWabor_t* psW){
    //Funktion muss nach initialize_sSound aufgerufen werden
    if(psW->sSound.paA[0]==NULL) return;
    t_int num_threads=psW->num_threads;
    for(t_int i=0; i<WABOR_T_MAX_THREADS; ++i){
        wabor_t_free(psW->painvwp[i]);
        psW->painvwp[i]=NULL;
    }
    
    for(t_int i=0; i<num_threads; ++i){
        psW->painvwp[i]=wabor_t_malloc0(sizeof *(psW->painvwp[i]));
        if(psW->painvwp[i]==NULL){
            for(t_int j=0; j<num_threads; ++j){
                wabor_t_free(psW->painvwp[j]);
                psW->painvwp[j]=NULL;
                return;
            }
        }
    }
    for(t_int i=0; i<num_threads; ++i){
        psW->painvwp[i]->sA=psW->sSound.paA[i];
        psW->painvwp[i]->sC=psW->sSound.paC[i];
        psW->painvwp[i]->sE=psW->sSound.paE[i];
        psW->painvwp[i]->psW=psW;
    }
}

void invwabor_t_set_soundbuffer_length_factor(sinvWabor_t* psW, t_float fFactor){
    if(fFactor<=1) fFactor=5000;
    psW->sSound.iLength=(t_int)fFactor*PD_BLOCKSIZE;
    invwabor_t_initialize_sSound(psW);
    return;   
}
void invwabor_t_set_compute_synthesis(sinvWabor_t* psW, t_float fCompute){
    psW->iComputeSynthesis=fCompute?1:0;
    return;
}

void invwabor_t_set_num_threads(sinvWabor_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;
    
    invwabor_t_initialize_sSound(psW);
    invwabor_t_initialize_invwabor_param(psW);            
    
    return;
}