// SPDX-License-Identifier: LGPL-3.0-or-other
// Copyright (C) 2021 Contributors to the SLS Detector Package
#ifndef MULTITHREADED_ANALOG_DETECTOR_H
#define MULTITHREADED_ANALOG_DETECTOR_H

#define MAXTHREADS 1000

#include <vector>
#include <string>
#include <sstream>
#include <iomanip>
#include <fstream>
#include <stdio.h>
//#include <deque>
//#include <list>
//#include <queue>
#include <fstream>
#include <cstdlib> 
#include <pthread.h>

#include "analogDetector.h"
#include "circularFifo.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
//#include <mutex>


using namespace std;



class threadedAnalogDetector
{
public:
  threadedAnalogDetector(analogDetector<uint16_t> *d, int fs=10000) {
    char *mm;//*mem, 
    det=d;
    fifoFree=new CircularFifo<char>(fs);
    fifoData=new CircularFifo<char>(fs);
    // mem==NULL;
    /* mem=(char*)calloc(fs, det->getDataSize()); */
    /* if (mem) */
    /*   memset(mem,0, fs*det->getDataSize()); */
    int i;
    for (i=0; i<fs; i++) { 
      //
      //  mm=mem+i*det->getDataSize();
      // cout << i << endl;
      mm=(char*)calloc(1, det->getDataSize());
     
      if (mm) { 
	//memset(mm,0, det->getDataSize());
	fifoFree->push(mm);
      } else
	break;
    }  
    if (i<fs) cout << "Could allocate only "<< i <<" frames";
    

    busy=0;
    stop=1;
    fMode=eFrame;
    ff=NULL;
  }



  virtual int setFrameMode(int fm) {
    if (fm>=0) {
      det->setFrameMode((frameMode)fm); 
      fMode=fm; 
    }
    return fMode;
  };
  virtual double setThreshold(double th) {return det->setThreshold(th);};



  virtual void setROI(int xmin, int xmax, int ymin, int ymax) {det->setROI(xmin,xmax,ymin,ymax);};
  virtual int setDetectorMode(int dm) {
    if (dm>=0) {
      det->setDetectorMode((detectorMode)dm); 
      dMode=dm; 
    }
    return dMode;
  };

  virtual void newDataSet(){det->newDataSet();};
      //fMode=fm; return fMode;}

  /* void prepareInterpolation(int &ok) { */
  /*   cout << "-" << endl; */
  /*   det->prepareInterpolation(ok); */
  /* }; */

  virtual int *getImage() {
    return det->getImage();
  }
  virtual int getImageSize(int &nnx, int &nny, int &ns, int &nsy) {return det->getImageSize(nnx, nny, ns, nsy);};
  virtual int getDetectorSize(int &nnx, int &nny) {return det->getDetectorSize(nnx, nny);};

  virtual ~threadedAnalogDetector() {StopThread();  delete fifoFree; delete fifoData;}

   /** Returns true if the thread was successfully started, false if there was an error starting the thread */
   virtual bool StartThread()
   { stop=0;  
     cout << "Detector number " << det->getId() << endl; 
     cout << "common mode is " << det->getCommonModeSubtraction()<< endl;
     cout << "ghos summation is " << det->getGhostSummation()<< endl;
     return (pthread_create(&_thread, NULL, processData, this) == 0);
   }

   virtual void StopThread()
   { stop=1;
     (void) pthread_join(_thread, NULL);
   }
   

   virtual bool pushData(char* &ptr) {
     return fifoData->push(ptr);
   }

   virtual bool popFree(char* &ptr) {
     return fifoFree->pop(ptr);
   }

   virtual int isBusy() {if (fifoData->isEmpty() && busy==0) return 0; return 1;}
   
   //protected:
   /** Implement this method in your subclass with the code you want your thread to run. */
   //virtual void InternalThreadEntry() = 0;
   virtual void *writeImage(const char * imgname) {return det->writeImage(imgname);};

   virtual void clearImage(){det->clearImage();};

 
   virtual void setPedestal(double *ped, double *rms=NULL, int m=-1){det->setPedestal(ped,rms,m);};
   
   
   virtual void setPedestalRMS(double *rms){ det->setPedestalRMS(rms);};
   
 virtual double *getPedestal(double *ped=NULL){return det->getPedestal(ped);};
   
   
   virtual double *getPedestalRMS(double *rms=NULL){ return det->getPedestalRMS(rms);};
   
   
   

/** sets file pointer where to write the clusters to 
    \param f file pointer
    \returns current file pointer
*/
   FILE *setFilePointer(FILE *f){return det->setFilePointer(f); };

/** gets file pointer where to write the clusters to 
    \returns current file pointer
*/
FILE *getFilePointer(){return det->getFilePointer();};



  virtual double setNSigma(double n) {return det->setNSigma(n);};
  virtual void setEnergyRange(double emi, double ema) {det->setEnergyRange(emi,ema);};


  virtual void prepareInterpolation(int &ok){
       slsInterpolation *interp=det->getInterpolation(); 
       if (interp)  
        interp->prepareInterpolation(ok); 
   }

   virtual int *getFlatField(){
     slsInterpolation *interp=(det)->getInterpolation();
     if (interp) 
       return interp->getFlatField();
     else
       return NULL;
   }
   
   virtual int *setFlatField(int *ff, int nb, double emin, double emax){
     slsInterpolation *interp=(det)->getInterpolation();
     if (interp) 
       return interp->setFlatField(ff, nb, emin, emax);
     else
       return NULL;
   }

   void *writeFlatField(const char * imgname) {
     slsInterpolation *interp=(det)->getInterpolation();
     //cout << "interp " << interp << endl;
     if (interp)  {
       cout << imgname << endl;
       return interp->writeFlatField(imgname);
     }
     return NULL;
   }

   void *readFlatField(const char * imgname, int nb=-1, double emin=1, double emax=0){
     slsInterpolation *interp=(det)->getInterpolation();
     if (interp)  
       return interp->readFlatField(imgname, nb, emin, emax);
     return NULL;
   }

   virtual int *getFlatField(int &nb, double emi, double ema){
     slsInterpolation *interp=(det)->getInterpolation();
     int *ff=NULL;
     if (interp) { 
       ff=interp->getFlatField(nb,emi,ema);
     }     
     return ff;
   }
   
   virtual slsInterpolation *getInterpolation() {
     return (det)->getInterpolation();
   }
   
   virtual void resetFlatField() {
     slsInterpolation *interp=(det)->getInterpolation();
     if (interp)   interp->resetFlatField();//((interpolatingDetector*)det)->resetFlatField();
   }


  virtual int setNSubPixels(int ns, int nsy)  { 
     slsInterpolation *interp=(det)->getInterpolation();
     if (interp)  interp->setNSubPixels(ns, nsy);
     return 1;};


   virtual slsInterpolation *setInterpolation(slsInterpolation *f){
     return (det)->setInterpolation(f);
   };
protected:
   analogDetector<uint16_t> *det;
   int fMode;
   int dMode;
   int *dataSize;
   pthread_t _thread;
   CircularFifo<char> *fifoFree;
   CircularFifo<char> *fifoData;
   int stop;
   int busy;
   char *data;
   int *ff;

   static void * processData(void * ptr) {
     threadedAnalogDetector *This=((threadedAnalogDetector *)ptr); 
     return This->processData();
   }

   void * processData() {
     //  busy=1;
     while (!stop) {
       if (fifoData->isEmpty()) {
	 busy=0;
	 usleep(100);
       } else {
	 busy=1;
	 fifoData->pop(data); //blocking!
	 det->processData(data);
	 fifoFree->push(data); 
	 //busy=0;
       }
     }
     return NULL;
   }



};



class multiThreadedAnalogDetector
{
public:
 multiThreadedAnalogDetector(analogDetector<uint16_t> *d, int n, int fs=1000) : stop(0), nThreads(n), ithread(0) { 
    dd[0]=d;
    if (nThreads==1)
      dd[0]->setId(100);
    else 
      dd[0]->setId(0);
    for (int i=1; i<nThreads; i++) {
      dd[i]=d->Clone();
      dd[i]->setId(i);
    }
    
    for (int i=0; i<nThreads; i++) {
      cout << "**" << i << endl;
      dets[i]=new threadedAnalogDetector(dd[i], fs);
    }
    
    image=NULL;
    ff=NULL;
    ped=NULL;
    cout << "Ithread is " << ithread << endl;
  }
  
  ~multiThreadedAnalogDetector() { 
    StopThreads();
    for (int i=0; i<nThreads; i++) 
      delete dets[i]; 
    /* for (int i=1; i<nThreads; i++)  */
    /*   delete dd[i]; */
    //delete [] image;
  }


  virtual int setFrameMode(int fm) { int ret=dets[0]->setFrameMode(fm); for (int i=1; i<nThreads; i++) { dets[i]->setFrameMode(fm);} return ret;};
  virtual double setThreshold(int fm) { double ret=dets[0]->setThreshold(fm); for (int i=1; i<nThreads; i++) dets[i]->setThreshold(fm); return ret;};
  virtual int setDetectorMode(int dm) { int ret=dets[0]->setDetectorMode(dm);; for (int i=1; i<nThreads; i++) dets[i]->setDetectorMode(dm); return ret;};
  virtual void setROI(int xmin, int xmax, int ymin, int ymax) { for (int i=0; i<nThreads; i++) dets[i]->setROI(xmin, xmax,ymin,ymax);};
  
   
  virtual void newDataSet(){for (int i=0; i<nThreads; i++) dets[i]->newDataSet();};
  
  virtual int *getImage(int &nnx, int &nny, int &ns, int &nsy) {
    int *img;
    // int nnx, nny, ns; 
    // int nnx, nny, ns;
    int nn=dets[0]->getImageSize(nnx, nny,ns, nsy);
    if (image) {
      delete [] image;
      image=NULL;
    }
    image=new int[nn];
    //int nn=dets[0]->getImageSize(nnx, nny, ns);
    //for (i=0; i<nn; i++) image[i]=0;
    
    for (int ii=0; ii<nThreads; ii++) {
      //cout << ii << " " << nn << " " << nnx << " " << nny << " " << ns << endl;
      img=dets[ii]->getImage();
      for (int i=0; i<nn; i++) {
	if (ii==0)
	  //	  if (img[i]>0)
	    image[i]=img[i];
	// else
	//  image[i]=0;
	else	//if (img[i]>0)
	  image[i]+=img[i];
	//if (img[i])	  cout << "det " << ii << " pix " << i << " val " <<  img[i] << " " << image[i] << endl;
      }
    
    }
    return image;
    
  }


  virtual void clearImage() {
  
    for (int ii=0; ii<nThreads; ii++) {
      dets[ii]->clearImage();
    }
    
  }

  virtual void *writeImage(const char * imgname, double t=1) {   
/* #ifdef SAVE_ALL   */
/*      for (int ii=0; ii<nThreads; ii++) { */
/*        char tit[10000];cout << "m" <<endl; */
/*        sprintf(tit,"/scratch/int_%d.tiff",ii); */
/*        dets[ii]->writeImage(tit); */
/*      } */
/* #endif */
    int nnx, nny, ns, nsy;
    getImage(nnx, nny, ns,nsy);
     //int nnx, nny, ns;
    int nn=dets[0]->getImageSize(nnx, nny, ns, nsy);
     float *gm=new float[nn];
     if (gm) {
       for (int ix=0; ix<nn; ix++) {
	 if (t) {
	   if (image[ix]<0) 
	     gm[ix]=0;
	   else
	     gm[ix]=(image[ix])/t;
	 } else
	   gm[ix]=image[ix];

	 //if (image[ix]>0 && ix/nnx<350) cout << ix/nnx << " " << ix%nnx << " " << image[ix]<< " " << gm[ix] << endl;
       }
       //cout << "image " << nnx << " " << nny << endl;
       WriteToTiff(gm,imgname ,nnx, nny); 
       delete [] gm;
     } else cout << "Could not allocate float image " << endl;
     return NULL;
   }
  

  virtual void StartThreads() { 
    for (int i=0; i<nThreads; i++) { 
      dets[i]->StartThread(); 
    }
    
  }
   


  virtual void StopThreads() { 
    for (int i=0; i<nThreads; i++) 
      dets[i]->StopThread(); 
      
    }

  
  virtual int isBusy() {
    int ret=0, ret1;  
    for (int i=0; i<nThreads; i++) {
      ret1=dets[i]->isBusy();
      ret|=ret1;
      //   if (ret1) cout << "thread " << i <<" still busy " << endl;
    }
    return ret;
  }
  
   
   virtual bool pushData(char* &ptr) {
     return dets[ithread]->pushData(ptr);
   }

   virtual bool popFree(char* &ptr) {
     //  cout << ithread << endl;
     return dets[ithread]->popFree(ptr);
   }
   
   virtual int nextThread() {
     ithread++;
     if (ithread==nThreads) ithread=0;
     return ithread;
   }


  virtual double *getPedestal(){
    int nx, ny;
    dets[0]->getDetectorSize(nx,ny);
    if (ped) delete [] ped;
    ped=new double[nx*ny];
    double *p0=new double[nx*ny];
    
    for (int i=0; i<nThreads; i++) {
      //inte=(slsInterpolation*)dets[i]->getInterpolation(nb,emi,ema);
      //  cout << i << endl;
      p0=dets[i]->getPedestal(p0);
      if (p0) {
	if (i==0) {

	for (int ib=0; ib<nx*ny; ib++) {
	  ped[ib]=p0[ib]/((double)nThreads);
	  //  cout << p0[ib] << " ";
	}
	} else {
	for (int ib=0; ib<nx*ny; ib++) {
	  ped[ib]+=p0[ib]/((double)nThreads);
	  //  cout << p0[ib] << " ";
	}
	}
      }
	
    } 
    delete [] p0;
    return ped;
  };
   
  
  virtual double *getPedestalRMS(){
    int nx, ny;
    dets[0]->getDetectorSize(nx,ny);
    // if (ped) delete [] ped;
    double *rms=new double[nx*ny];
    double *p0=new double[nx*ny];
    
    for (int i=0; i<nThreads; i++) {
      //inte=(slsInterpolation*)dets[i]->getInterpolation(nb,emi,ema);
      //  cout << i << endl;
      p0=dets[i]->getPedestalRMS(p0);
      if (p0) {
	if (i==0) {
	  
	  for (int ib=0; ib<nx*ny; ib++) {
	    rms[ib]=p0[ib]*p0[ib]/((double)nThreads);
	  //  cout << p0[ib] << " ";
	}
	} else {
	for (int ib=0; ib<nx*ny; ib++) {
	  rms[ib]+=p0[ib]*p0[ib]/((double)nThreads);
	  //  cout << p0[ib] << " ";
	}
	}
      }
	
    }
    delete [] p0;
    
    /* for (int ib=0; ib<nx*ny; ib++) { */
    /*   if (rms[ib]>0) */
    /* 	rms[ib]=sqrt(ped[ib]); */
    /*   else */
    /* 	rms[ib]=0; */
    /* } */


    return rms;
  };
   
  


   
   virtual double *setPedestal(double *h=NULL){
     //int nb=0;
    
     int nx, ny;
     dets[0]->getDetectorSize(nx,ny);
     if (h==NULL) h=ped;
     for (int i=0; i<nThreads; i++) {
       dets[i]->setPedestal(h);
     }
     
     return NULL;
   }; 




   virtual void *writePedestal(const char * imgname){     
    
     int nx, ny;
     dets[0]->getDetectorSize(nx,ny);
   
      getPedestal();
      float *gm=new float[nx*ny];
      if (gm) {
       for (int ix=0; ix<nx*ny; ix++) {
	 gm[ix]=ped[ix];
       }    
       WriteToTiff(gm,imgname ,nx, ny); 
       delete [] gm;
     } else cout << "Could not allocate float image " << endl;
    
    return NULL;

   };


   virtual void *writePedestalRMS(const char * imgname){     
    
     int nx, ny;
     dets[0]->getDetectorSize(nx,ny);
   
      double *rms=getPedestalRMS();
      float *gm=new float[nx*ny];
      if (gm) {
       for (int ix=0; ix<nx*ny; ix++) {
	 gm[ix]=rms[ix];
       }    
       WriteToTiff(gm,imgname ,nx, ny); 
       delete [] gm;
       delete [] rms;
     } else cout << "Could not allocate float image " << endl;
    
    return NULL;

   };


  virtual void *readPedestal(const char * imgname, int nb=-1, double emin=1, double emax=0){
    
    int nx, ny;
    dets[0]->getDetectorSize(nx,ny);
    uint32 nnx;
    uint32 nny;
    float *gm=ReadFromTiff(imgname, nnx, nny);
    if (ped) delete [] ped;
    if (nnx>(uint)nx) nx=nnx;
    if (nny>(uint)ny) ny=nny;
    ped=new double[nx*ny];
    
    for (int ix=0; ix<nx*ny; ix++) {
      ped[ix]=gm[ix];
    }
    delete [] gm;
    
    return setPedestal();
    
  };
  





/** sets file pointer where to write the clusters to 
    \param f file pointer
    \returns current file pointer
*/
   virtual FILE *setFilePointer(FILE *f){
     for (int i=0; i<nThreads; i++) {
       dets[i]->setFilePointer(f);
       //dets[i]->setMutex(&fmutex);
     }
     return dets[0]->getFilePointer();
   };

   /** gets file pointer where to write the clusters to 
       \returns current file pointer
   */
   virtual FILE *getFilePointer(){return dets[0]->getFilePointer();};



 protected:
  bool stop;
  const int nThreads;
  threadedAnalogDetector *dets[MAXTHREADS];
  analogDetector<uint16_t> *dd[MAXTHREADS];
  int ithread;
  int *image;
  int *ff;
  double *ped;
  pthread_mutex_t fmutex;
};

#endif