/*******************************************************************************
* McStas, neutron ray-tracing package
*         Copyright (C) 1997-2008 Risoe National Laboratory, Roskilde, Denmark
*
* Component: NPI_tof_dhkl_detector
*
* %I
* Written by: Jan Saroun, saroun@ujf.cas.cz
* Date: July 1, 2017
* Version: $Revision$
* Origin: 
* Release: McStas 2.5
* Last update: 30/11/2018
*
* %D
* A cylindrical detector which converts time-of-flight data (x,y,z,time) to a 1D diffractogram in dhkl. 
* The detector model includes finite detection depth, spatial and time resolution. Optionally, the component
* exports position and time coordinates of detection events in an ASCII file.   
*
* The component can also handle setups employing a modulation chopper for peak multiplexing at a long pulse source,
* as proposed for the diffractometer BEER@ESS. In this case, the component requires chopper parameters 
* (modulation period, width of the primary unmodulated pulse), and a file with estimated dhkl values.
* The component then estimates valid regions on the angle/tof map, excluding areas assumed to be empty 
* or with overlaping lines. This map is exported together with the diffractogram. 
*
* Tips for usage:
* 1) Centre the detector at the sample axis, keep z-axis parallel to the incident beam.
* 2) Set the radius equal to the distance from the sample axis to the front face of the detection volume.
* 3) Linst-Lc is used to determine "instrumental" wavelength as lambda = h/m_n*time/(Linst-Lc);
*
* %P
* INPUT PARAMETERS:
*
* filename:	[str]	Name of file in which to store the detector data.
* radius:	[m]		Radius of detector.
* yheight:	[m]		Height of detector.
* zdepth:	[m]		Thickness of the detection volume.
* amin:		[deg]	minimum of scattering angle to be detected.
* amax: 	[deg]	maximum of scattering angle to be detected.
* d_min:	[AA]	minimum of inter-planar spacing to be detected.
* d_max:	[AA]	maximum of inter-planar spacing to be detected.
* time0:	[s]		Time delay of the wavelength definition chopper with respect to the source pulse.
* Lc:		[m]		Distance from the source to the pulse chopper (set 0 for a short pulse source).
* Linst:	[m]		Distance from the source to the detector.
* res_x:	[m]		Spatial resolution along x (Gaussian FWHM).
* res_y:	[m]		Spatial resolution along y (Gaussian FWHM).
* res_t:	[s]		Time resolution (Gaussian FWHM). Readout only, path to capture is accounted for by the tracing code.
* mu:		[1/cm/AA]	Capture coefficient for the detection medium.
* modulation:	[1|0]	Switch on/off the modulation regime (BEER multiplexing technique).
* mod_dt:	[s]		Modulation period.
* mod_twidth: [s]	Width of the primary source pulse.
* mod_shift:	[float]		Relative shift of the modulation pattern (delta_d/d, constant for all reflections).
* mod_d0_table: [str]	Name of a file with the list of dhkl estimates (one per line).
* verbose:	[1|0]	Switch for extended reporting.
* restore_neutron: 	[1|0]	Switch for restoring of previous neutron state.
*
* DEFINITION PARAMETERS:
*
* nd:	Number of bins for dhkl in the 1D diffractograms.
* na:	Number of bins for scattering angles in the 2D maps.
* nt:	Number of bins for time of flight in the 2D maps.
* nev:  Number of detection events to export in "events.dat" (only modulation mode, set 1 for no export) 
*
* %E
* Example 1, simple ToF regime
* NPI_tof_dhkl_detector(nd = 1800, filename = "result.dat", yheight = 0.5, zdepth = 0.02,
*			radius = 1.5, amin = 75, amax = 105, d_min = 0.4, d_max = 2.4,
			time0 = chopper_delay, Linst = 160, Lc = 6.5, modulation = 0)
*
* Example 2, modulation regime
* NPI_tof_dhkl_detector(nd=1800, filename="result.dat", yheight=1.0, zdepth=0.01, radius=2, 
*	yheight=1.0, zdepth=0.01, radius=2, amin=det_th1, amax=det_th2, d_min=0.8, d_max=2.4, 
*	time0=chopper_delay, Linst=160, Lc = 9.3, res_x=0.002, res_y=0.005, res_t=1e-6, mu=1.0, mod_shift=5e-4, 
*	modulation=1, mod_dt=4.464e-4, mod_twidth=0.003, mod_d0_table="dhkl.dat") 
*
******************************************************************************/
DEFINE COMPONENT NPI_tof_dhkl_detector

SETTING PARAMETERS (string filename=0, radius=2, yheight=0.3, zdepth=0.01, amin=75, amax=105, int nd=200, int na=800, int nt=800, int nev=1,
d_min=1, d_max=3, time0, Linst, Lc, 
res_x=0, res_y=0, res_t=0, mu=1.0, mod_shift=0,
modulation=0, mod_dt=0, mod_twidth=0, string mod_d0_table=0, 
verbose=0, restore_neutron=1)

/* Neutron parameters: (x,y,z,vx,vy,vz,t,sx,sy,sz,p) */ 

SHARE
%{
/* used for reading data table from file */
%include "read_table-lib"

/*
Save up to nmax events stored by the detector in a text file, one per row.
Each row contains detection coordinates: x, y, t, sin(theta), p.
*/

void saveEvents(double** EVENTS, char* file_name, long int nmax, long int iev) {
	long int i, j, n;
	double sumP;
	FILE *hfile;
	hfile=fopen(file_name,"w");
	if (!hfile)
	{
        fprintf(stderr,"saveEvents: can't open file for output (%s)\n",file_name);
        exit(-1);
	} else {
		n = nmax;
		if (n > iev) {
			n = iev;
		}
		sumP = 0.0;
		for (i=0;i<n;i++) {
			sumP += EVENTS[i][5];
		}
		fprintf(hfile, "# Detection events\n");
		fprintf(hfile, "# sum(p) = %g\n",sumP);
		fprintf(hfile, "# sum(n) = %ld\n",n);
		fprintf(hfile, "# Columns: x [mm], y[mm], z[mm], t[ms], sin(theta), p\n");
		for (i=0;i<n;i++) {
			for (j=0; j<6; j++) {
				fprintf(hfile, "%g ", EVENTS[i][j]);
			}
			fprintf(hfile, "\n");
		}
		fclose(hfile);
	}	
}  

/*
Used in the modulation mode:
Calculates the index of the nearest line which fits within given time interval (trange).
Excludes the line with given index (iex).

Parameters
-----------
iex: excluding this line index
sinth: sin(thetaB)
tof: neutron time of flight from the source (not from the chopper !) [s]
trange: time range for searching overlap [s]

Returns
-------
The index of the nearest diffraction line from the dhkl list, or -1 if not found.
*/
    int getNearestLine(int iex, double sinth, double tof, double trange, int n_dhkl, double* dhkl, double mod_shift, double Ltof) {
	int i;
	int j=0;
	int io=-1;
	double dtmax,dt,tline;
	const double hm=2*PI*K2V;
	dtmax=trange;
	for (i=0; i<n_dhkl; i++) {
		if (i !=iex) {
		// get centre of the pulse chain
		    tline=2*dhkl[i]*(1.0+mod_shift)*sinth/hm*Ltof;
			dt=fabs(tof-tline);
			if (dt<dtmax) {
				dtmax=dt;
				j=i;
			}
		}
	}
	if (dtmax<0.5*trange) {
		io=j;
	}
	return io;
}
	
/*
Estimates the overlap map in the (scattering angle, tof) space
for plotting with DETECTOR_OUT_2D. 

Returns
-------
TOF_N, TOF_p, TOF_p2 arrays for 2D plots with DETECTOR_OUT_2D.
The values are:
0: empty region
1: valid region (diffraction line without overlap)
2: overlaping of 2 or more lines
*/	
  void calcOverlaps(double tmin, double tmax, double ami, double ama, double trange, int na, int nt, double t2lam, double mod_shift, double Ltof, double** TOF_N, double** TOF_p, double** TOF_p2, int n_dhkl, double* dhkl) {
	int i,j,iline,io;
	double tdet,a,sth;	
	double a1,a2,drange;
	a1=ami*DEG2RAD;
	a2=ama*DEG2RAD;
	double da=(a2-a1)/na;
	double dt=(tmax-tmin)/nt;
	for (i=0;i<na;i++) {
		a=a1+(i+0.5)*da;
		sth=sin(a/2);
		drange=trange*t2lam/2/sth;
		tdet=tmin+(j+0.5)*dt;
		iline=getNearestLine(-1,sth, tdet, trange, n_dhkl, dhkl, mod_shift,Ltof);
		if (iline>=0) {
		  io=getNearestLine(iline, sth, tdet, trange, n_dhkl, dhkl, mod_shift,Ltof);
		  if (io<0) {
		    TOF_N[i][j] += 1;
		    TOF_p[i][j] += 1;
		    TOF_p2[i][j] += 1;
		  } else {
		    TOF_N[i][j] += 1;
		    TOF_p[i][j] += 2;
		    TOF_p2[i][j] += 4;
		  }
		} 
	}
  }

%}

DECLARE
%{
  DArray1d D_N;
  DArray1d D_p;
  DArray1d D_p2;
  DArray2d TOF_N;
  DArray2d TOF_p;
  DArray2d TOF_p2;
  DArray2d EVENTS;
  double *dhkl;
  double time_min;
  double time_max;
  double grf_th1;
  double grf_th2;
  double t2lam;
  double L0;
  double Ltof;
  long iev;
  int n_dhkl;
  // set to 1 for some debugging info
  int dbg;
  int dbgn; 
%}
INITIALIZE
  %{
    /* h/m_n in [Ang*m/s]  */
    double hm = 2*PI*K2V;
    dbgn=0;
    iev = -1;
    
    D_N=create_darr1d(nd);
    D_p=create_darr1d(nd);
    D_p2=create_darr1d(nd);
    TOF_N=create_darr2d(na,nt);
    TOF_p=create_darr2d(na,nt);
    TOF_p2=create_darr2d(na,nt);
    EVENTS=create_darr2d(nev,6);
    
    // read dhkl table
    n_dhkl=0;
    if (modulation) {	
      MPI_MASTER(
		 printf("%s: Modulation on, table = %s, line shift=%g\n",NAME_CURRENT_COMP,mod_d0_table,mod_shift);
		 )
	if (! mod_d0_table || !strlen(mod_d0_table) || !strcmp(mod_d0_table, "NULL")) {			
	  MPI_MASTER(
		     fprintf(stderr,"ERROR %s: Can't read table with d0 values: %s \n",NAME_CURRENT_COMP, mod_d0_table);
		     )
	    exit(-1);
	} else {
	  t_Table sTable;	
	  MPI_MASTER(printf("trying to read dhkl table [%s]\n",mod_d0_table));
	  Table_Read(&sTable, mod_d0_table, 1); 
	  double size = sTable.rows;
	  if (size>0) {
	    dhkl = (double*)malloc(sizeof(double)*size);
	    int i;
	    for (i=0; i<size; i++) {
	      dhkl[i] = Table_Index(sTable, i, 0);
	    }
	    n_dhkl=size;
	    if (verbose) printf("Read %d rows from dhkl table [%s]\n",n_dhkl,mod_d0_table);
	  }
	  Table_Free(&sTable);
	}
      if (! n_dhkl) {
	MPI_MASTER(
		   fprintf(stderr,"ERROR %s: Can't evaluate modulated data without dhkl list\n",NAME_CURRENT_COMP);
		   );
	exit(-1);
      }
      
      if (! n_dhkl) {
	MPI_MASTER(
		   fprintf(stderr,"ERROR %s: Can't evaluate modulated data without dhkl list\n",NAME_CURRENT_COMP);
		   );
	exit(-1);
      }
      if (mod_dt<=0) {
	MPI_MASTER(
		   fprintf(stderr,"ERROR %s: Modulation period must be positive\n",NAME_CURRENT_COMP);
		   );
	exit(-1);
      }
      if (mod_twidth<=0) {
	MPI_MASTER(
		   fprintf(stderr,"ERROR %s: Time range in modulation mode must be positive\n",NAME_CURRENT_COMP);
		   );
	exit(-1);
      }
    } else {
      MPI_MASTER(
		 printf("%s: Modulation off\n",NAME_CURRENT_COMP);
		 );
    }
    
    // Distance L0 determines the wavelength
    L0=Linst-Lc;
    Ltof=Linst;
    t2lam=hm/L0;
    
    
    // range of theta-tof plot
    time_min=2*d_min*sin(amin*DEG2RAD/2)/t2lam;
    time_max=2*d_max*sin(amax*DEG2RAD/2)/t2lam;
    grf_th1=amin;
    grf_th2=amax;
    if (modulation) {
      calcOverlaps(time_min, time_max, grf_th1, grf_th2, mod_twidth, na, nt, mod_shift, Ltof, t2lam, TOF_N, TOF_p, TOF_p2, n_dhkl, dhkl);
    }
    MPI_MASTER(
	       if (verbose) {
		 printf("%s: lambda/(t-t0)=%g\n",NAME_CURRENT_COMP,t2lam); 
		 printf("Lc=%g, L0=%g, LD=%g, time0=%g\n", Lc, L0, radius, time0*1000); 
		 printf("For Fe110 at 90 deg: tof=%g [ms]\n",2*2.1055*sin(45*DEG2RAD)/hm*Ltof); 
		 if (modulation) {	
		   printf("mod_dt=%g [ms], mod_shift=%g\n", mod_dt, mod_shift); 
		 }
	       }
	       );
    %}
TRACE
  %{
    int i,j,io,iline,iref,valid_region;
    double t0,t1,lam,theta2,d,cos2;
    double sinth,dt,p0,tau,v0,x0,y0,z0,dx,dy,sig,tof,dd;
    double tref,tc;
    double r8ln2=2.355;
    const double hm = 2*PI*K2V; // = h/m_n
    double L1;
    double th2_min=amin*PI/180;
    double th2_max=amax*PI/180;
    
    #ifndef OPENACC
    if (dbgn>20) {
      #pragma acc atomic write
      dbg=0;
    }
    #endif
    /* cross-section with the detector front face. Allow height = yheight+3*res_y to account for a random 
       displacement due to vertical resolution.
    */
    int cross=cylinder_intersect(&t0, &t1, x, y, z, vx, vy, vz, radius, yheight+3*res_y);	
    /* don't allow intersections with top/bottom cylinder walls,
       only neutrons from inside of the cylinder are allowed.
    */
    if ( (cross!=1) ||  (t0>0) || (t1<0) ) {
      RESTORE_NEUTRON(INDEX_CURRENT_COMP, x, y, z, vx, vy, vz, t, sx, sy, sz, p);
    } else {
      // go to cylinder surface = detector entry
      PROP_DT(t1);
      // save position before propagation through the detection volume
      x0=x;
      y0=y;
      z0=z;
      // add random penetration within the detector depth
      v0=sqrt(vx*vx+vy*vy+vz*vz);
      sig=100*mu*hm/v0; // mu is in 1/cm
      p0=1.0-exp(-sig*zdepth);
      tau=-1.0*log(1.0-p0*rand01())/sig/v0;
      PROP_DT(tau);
      p *= p0;
      // add random shift by y-resolution	
      dy = randnorm()*res_y/r8ln2;
      y0 += dy;
      // add random shift by x-resolution	(normal to radius)
      dx = randnorm()*res_x/r8ln2;
      cos2=z0/radius;
      z0 += dx*sqrt(1.-cos2*cos2);
      x0 += dx*cos2;
      // add random time resolution shift
      tof = t + randnorm()*res_t/r8ln2;
      // NOTE: detection coordinates are x0, y0, z0, tof
      // L1 = detected sample to detector distance:
      L1=sqrt(x0*x0 + z0*z0 + y0*y0);
      // Clip y on +- 0.5*yheight
      if (fabs(y)<(0.5*yheight)) {
	dd=(d_max-d_min)/nd;
	// get cos(2*theta), assume primary beam axis // [0 0 1]
	cos2=z0/L1;
	theta2=acos(cos2);
	dt = 0;
	if (theta2>th2_min && theta2< th2_max) {
	  sinth=sin(theta2/2);
	  valid_region=0;
	  int iex=-1;
	  /* Modulation mode: estimate the number of modulation periods and correct dt.
	     Exclude regions with overlaping diffraction lines
	  */
	  if (modulation) {
	    iline=getNearestLine(-1, sinth, tof, mod_twidth,n_dhkl,dhkl,mod_shift,Ltof);
	    if (iline>=0) {
	      io=getNearestLine(iline, sinth, tof, mod_twidth,n_dhkl,dhkl,mod_shift,Ltof);
	      if (io<0) {
		valid_region=1;
		/*
		  Correction for modulation in steps:
		  1) tref = reference time for given dhkl, theta and reference chopper window 
		  2) tc = neutron time minus tref
		  3) iref = new reference chopper window
		  4) dt = correction for tof
		*/
		tref=2*(1.0+mod_shift)*dhkl[iline]*sinth/hm*L0; 
		tc=tof-time0-tref;
		iref=(int)floor(tc/mod_dt+0.5);
		dt=iref*mod_dt+time0;
		#ifndef OPENACC
		if (iline==0 && dbg) {
		  #pragma acc atomic
		  dbgn = dbgn+1;
		  printf("iref=%d, dt=%g, t=%g, tcor=%g\n",
			 iref, iref*mod_dt*1000,tc*1000, (tof-tref-dt)*1000);
		}
		#endif
	      }
	    }
	    // Normal mode: get indices in the ToF - 2theta grid and accumulate plot data
	  } else {					
	    valid_region=1;
	    dt=time0;
	    i = (int)floor((theta2-grf_th1*DEG2RAD)/(grf_th2*DEG2RAD-grf_th1*DEG2RAD)*na+0.5);
	    j = (int)floor((t-time_min)/(time_max-time_min)*nt+0.5);
	    if ( j>=0 && j<nt && i>=0 && i<na ) {
	      double p2=p*p;
	      #pragma acc atomic
	      TOF_N[i][j] = TOF_N[i][j] + 1;
              #pragma acc atomic
	      TOF_p[i][j] = TOF_p[i][j] + p;
              #pragma acc atomic
	      TOF_p2[i][j] = TOF_p2[i][j] + p2;
	    }
	  }
	  /*  Valid event: calculate wavelength and dhkl "as recorded by the instrument", i.e.
	      including smearing by instrument resolution.
	      Record a list of detection event coordinates in EVENTS array.
	  */
	  if (valid_region) {
	    // subtract reference chopper time from tof and calculate wavelength
	    lam=hm*(tof-dt)/(L0-radius+L1);
	    d=lam/2/sinth;
	    #pragma acc atomic
	    iev = iev + 1;
	    if (iev<nev) {
	      double tmp;

	      tmp = x0*1e3;
              #pragma acc atomic write
	      EVENTS[iev][0] = tmp;

	      tmp = y0*1e3;
              #pragma acc atomic write
	      EVENTS[iev][1] = tmp;

	      tmp = z0*1e3;
              #pragma acc atomic write
	      EVENTS[iev][2] = tmp;

              tmp = tof*1e3;
              #pragma acc atomic write
	      EVENTS[iev][3] = tmp;

              #pragma acc atomic write
	      EVENTS[iev][4] = sinth;

	      #pragma acc atomic write
	      EVENTS[iev][5] = p;
	    }
	    i=(int)floor((d-d_min)/dd+0.5);
	    if (i>=0 && i<nd) {
	      double p2 = p*p;
	      #pragma acc atomic
	      D_N[i] = D_N[i] + 1;
              #pragma acc atomic
	      D_p[i] = D_p[i] + p;
              #pragma acc atomic
	      D_p2[i] = D_p2[i] + p2;
	    }
	  }
	}
      } 
    }
    if (restore_neutron) {
      RESTORE_NEUTRON(INDEX_CURRENT_COMP, x, y, z, vx, vy, vz, t, sx, sy, sz, p);
    }
%}
SAVE
%{
  
  if (nev>1) {
    saveEvents(EVENTS,"events.dat", nev, iev);
  }
  DETECTOR_OUT_1D(
		  "ToF to dhkl detector",
		  "dhkl [AA]",
		  "Intensity",
		  "d", d_min, d_max, nd,
		  &D_N[0],&D_p[0],&D_p2[0],
		  filename);
  if (modulation) {
    DETECTOR_OUT_2D(
		    "Map of overlapping regions",
		    "Scattering angle [deg]",
		    "Time-of-flight [ms]",
		    grf_th1, grf_th2, time_min*1000, time_max*1000, 
		    na, nt,
		    &TOF_N[0][0],&TOF_p[0][0],&TOF_p2[0][0],
		    "overlap_map.dat");
  }
%}
FINALLY
%{
  free(dhkl);
  destroy_darr1d(D_N);
  destroy_darr1d(D_p);
  destroy_darr1d(D_p2);
  destroy_darr2d(TOF_N);
  destroy_darr2d(TOF_p);
  destroy_darr2d(TOF_p2);
  destroy_darr2d(EVENTS);
%}
MCDISPLAY
%{
  magnify("xz");
  circle("xz", 0, 0, 0, radius );

%}

END
