/*******************************************************************************
*
* CAEN SpA - Front End Division
* Via Vetraia, 11 - 55049 - Viareggio ITALY
* +390594388398 - www.caen.it
*
***************************************************************************//**
* \note TERMS OF USE:
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation. This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The user relies on the
* software, documentation and results solely at his own risk.
******************************************************************************/

#include <stdio.h>
#include <inttypes.h>

//#include "FERSlib.h"
#include "console.h"
//#include "configure.h"
#include "FERSutils.h"
#include "JanusC.h"
#include "Statistics.h"
//#include "adapters.h"


// ****************************************************
// Global Variables
// ****************************************************
FILE *of_raw_b = NULL, *of_raw_a = NULL;
FILE *of_list_b = NULL, *of_list_a = NULL, *of_list_c = NULL;
FILE *of_sync = NULL;
FILE* of_servInfo = NULL;
uint8_t fnumFVer = 0;
uint8_t snumFVer = 0;
uint8_t datatype = 0x0;	
uint8_t fnumSW = 0;
uint8_t snumSW = 0;
uint8_t tnumSW = 0;
static uint64_t ascii_size = 0; // size of ascii file
static uint64_t bin_size = 0;	// size of bin file
static uint64_t csv_size = 0;
static int bin_srun = 0;
static int ascii_srun = 0;
static int csv_srun = 0;
static int LocalRunNum = 0;

// ****************************************************
// Local functions
// ****************************************************
static void CreateOutFileName(char *radix, int RunNumber, int type, char *filename) {
	if (RunNumber >= 0) {
		if (strcmp(radix, "list") == 0 && J_cfg.EnableMaxFileSize)
			sprintf(filename, "%sRun%d.%d_%s", J_cfg.DataFilePath, RunNumber, 0, radix);
		else
			sprintf(filename, "%sRun%d_%s", J_cfg.DataFilePath, RunNumber, radix);
	} else sprintf(filename, "%s%s", J_cfg.DataFilePath, radix);
	if (type == 0) strcat(filename, ".dat");
	else if (type == 1) strcat(filename, ".csv");
	else strcat(filename, ".txt");
}

// Type: 0=Binary, 1=CSV, 2=ASCII
static void IncreaseListSubRun(int type) {
	int srun;
	char filename[500];

	if (type == 0) {
		fclose(of_list_b);
		++bin_srun;
		srun = bin_srun;
		bin_size = 0;
	} else if (type == 1) {
		fclose(of_list_c);
		++csv_srun;
		srun = csv_srun;
		csv_size = 0;
	} else {
		fclose(of_list_a);
		++ascii_srun;
		srun = ascii_srun;
		ascii_size = 0;
	}
	if (LocalRunNum >= 0) sprintf(filename, "%sRun%d.%d_%s", J_cfg.DataFilePath, LocalRunNum, srun, "list");
	else sprintf(filename, "%s%s", J_cfg.DataFilePath, "list");
	if (type == 0) {
		strcat(filename, ".dat");
		of_list_b = fopen(filename, "wb");
	} else if (type == 1) {
		strcat(filename, ".csv");
		of_list_c = fopen(filename, "w");
	} else {
		strcat(filename, ".txt");
		of_list_a = fopen(filename, "w");
	}
}


// ****************************************************
// Open/Close Output Files
// ****************************************************
int OpenOutputFiles(int RunNumber)
{
	char filename[500];
	LocalRunNum = RunNumber;

	//if ((J_cfg.OutFileEnableMask & OUTFILE_RAW_DATA_BIN) && (of_raw_b == NULL)) {
	//	CreateOutFileName("raw_data", RunNumber, 1, filename);
	//	of_raw_b = fopen(filename, "wb");
	//}
	//if ((J_cfg.OutFileEnableMask & OUTFILE_RAW_DATA_ASCII) && (of_raw_a == NULL)) {
	//	CreateOutFileName("raw_data", RunNumber, 0, filename);
	//	of_raw_a = fopen(filename, "w");
	//}
	if ((J_cfg.OutFileEnableMask & OUTFILE_LIST_BIN) && (of_list_b == NULL)) {
		CreateOutFileName("list", RunNumber, 0, filename);
		of_list_b = fopen(filename, "wb");
	}
	if ((J_cfg.OutFileEnableMask & OUTFILE_LIST_ASCII) && (of_list_a == NULL)) {
		CreateOutFileName("list", RunNumber, 2, filename);
		of_list_a = fopen(filename, "w");
	}
	if ((J_cfg.OutFileEnableMask & OUTFILE_LIST_CSV) && (of_list_c == NULL)) {
		CreateOutFileName("list", RunNumber, 1, filename);
		of_list_c = fopen(filename, "w");
	}
	if ((J_cfg.OutFileEnableMask & OUTFILE_SYNC) && (of_sync == NULL)) {
		CreateOutFileName("sync", RunNumber, 2, filename);
		of_sync = fopen(filename, "w");
	}
	if ((J_cfg.OutFileEnableMask & OUTFILE_SERVICE_INFO) && (of_servInfo == NULL)) {
		CreateOutFileName("ServiceInfo", RunNumber, 2, filename);
		of_servInfo = fopen(filename, "w");
	}
	return 0;
}

int CloseOutputFiles()
{
	// char filename[500];
	if (of_raw_b != NULL) fclose(of_raw_b);
	if (of_raw_a != NULL) fclose(of_raw_a);
	if (of_list_b != NULL) fclose(of_list_b);
	if (of_list_a != NULL) fclose(of_list_a);
	if (of_list_c != NULL) fclose(of_list_c);
	if (of_sync != NULL) fclose(of_sync);
	if (of_servInfo != NULL) fclose(of_servInfo);
	of_raw_b = NULL;
	of_raw_a = NULL;
	of_list_b = NULL;
	of_list_a = NULL;
	of_list_c = NULL;
	of_sync = NULL;
	of_servInfo = NULL;
	bin_size = 0;
	bin_srun = 0;
	ascii_size = 0;
	ascii_srun = 0;
	csv_size = 0;
	csv_srun = 0;
	return 0;
}

// ****************************************************
// Save Raw data and Lists to Output Files
// ****************************************************

// CTIN: add check on file size and stop saving when the programmed limit is reached

int SaveRawData(uint32_t *buff, int nw)
{
	int i;
	if (of_raw_b != NULL) {
		fwrite(buff, sizeof(uint32_t), nw, of_raw_b);
	}
	if (of_raw_a != NULL) {
		for(i=0; i<nw; i++)
			fprintf(of_raw_a, "%3d %08X\n", i, buff[i]);
		fprintf(of_raw_a, "----------------------------------\n");
	}
	return 0;
}

// ****************************************************
// Write Header of list files
// ****************************************************
int WriteListfileHeader() {
	// Get software, data file and board version	
	char unit[2][10] = { "LSB", "ns" };
	char tunit[2][10] = { "LSB", "us" };

	sscanf(SW_RELEASE_NUM, "%" SCNu8 ".%" SCNu8 ".%" SCNu8, &fnumSW, &snumSW, &tnumSW);
	sscanf(FILE_LIST_VER, "%" SCNu8 ".%" SCNu8, &fnumFVer, &snumFVer);
	uint16_t brdVer;
#ifdef FERS_5203	
	//sscanf("52.03", "%" SCNu8 ".%" SCNu8, &fbrdVer, &sbrdVer);
	sscanf("5203", "%" SCNu16, &brdVer);
#else
	sscanf("5202", "%" SCNu16, &brdVer);
#endif	

	// write headers, common for all the list files
	char mytime[100];
	strcpy(mytime, asctime(gmtime(&Stats.time_of_start)));
	mytime[strlen(mytime) - 1] = 0;
	uint16_t acq_mode = 0;
	if (J_cfg.TestMode)
		acq_mode = (uint16_t)ACQMODE_TEST_MODE;
	else
		acq_mode = (uint16_t)(J_cfg.AcquisitionMode);
	uint8_t meas_mode = (uint8_t)(J_cfg.MeasMode);
	uint8_t t_unit = J_cfg.OutFileUnit;	// LSB or ns
	int16_t rn = (int16_t)RunVars.RunNumber;
	if (of_list_b != NULL) {
		uint8_t header_size = sizeof(uint8_t) + 2 * sizeof(fnumFVer) + 3 * sizeof(fnumSW)	// headersize + DataFormat (2*unt8) + SwVersion (3*uint8)
			+ sizeof(brdVer) + sizeof(rn)													// Brd version, Run number
			+ sizeof(acq_mode) + sizeof(meas_mode) + sizeof(t_unit)							// AcqMode, MeasMode, time unit
			+ 3 * sizeof(float) + sizeof(Stats.start_time);									// LSB value for ToA, ToT, Tstamp, StartTime
			
		//uint32_t tmask = J_cfg.ChEnableMask1[brd];	// see below	
		// In Data format 3.3 we will add the size of the header
		// fwrite(&header_size, sizeof(header_size), 1, of_list_b);
		fwrite(&fnumFVer, sizeof(fnumFVer), 1, of_list_b);
		fwrite(&snumFVer, sizeof(snumFVer), 1, of_list_b);
		
		fwrite(&fnumSW, sizeof(fnumSW), 1, of_list_b);
		fwrite(&snumSW, sizeof(snumSW), 1, of_list_b);
		fwrite(&tnumSW, sizeof(tnumSW), 1, of_list_b);
		

		fwrite(&brdVer, sizeof(brdVer), 1, of_list_b);
		
		fwrite(&rn, sizeof(int16_t), 1, of_list_b);
		
		fwrite(&acq_mode, sizeof(acq_mode), 1, of_list_b);
		fwrite(&meas_mode, sizeof(meas_mode), 1, of_list_b);
		fwrite(&t_unit, sizeof(t_unit), 1, of_list_b);
		
		float tmpf;
		tmpf = 1000 * J_cfg.LeadTrail_LSB_ns;
		fwrite(&tmpf, sizeof(tmpf), 1, of_list_b);
		tmpf = 1000 * J_cfg.ToT_LSB_ns;
		fwrite(&tmpf, sizeof(tmpf), 1, of_list_b);
		tmpf = (float)(CLK_PERIOD_5203 * 1000);
		fwrite(&tmpf, sizeof(float), 1, of_list_b); 

		fwrite(&Stats.start_time, sizeof(Stats.start_time), 1, of_list_b);
		bin_size += header_size;
	}
	if (of_list_a != NULL) {
		char sm0[20] = "", sm1[20] = "", sm2[20] = "";
		fprintf(of_list_a, "//************************************************\n");
		fprintf(of_list_a, "// File Format Version %s\n", FILE_LIST_VER);
		fprintf(of_list_a, "// Janus_%" PRIu16 " Release %s\n", brdVer, SW_RELEASE_NUM);
		// Board Info
		if (J_cfg.AcquisitionMode == ACQMODE_COMMON_START && !J_cfg.TestMode)		fprintf(of_list_a, "// Acquisition Mode: Common Start\n");
		else if (J_cfg.AcquisitionMode == ACQMODE_COMMON_START && J_cfg.TestMode)	fprintf(of_list_a, "// Acquisition Mode: Test Mode\n");
		if (J_cfg.AcquisitionMode == ACQMODE_COMMON_STOP)							fprintf(of_list_a, "// Acquisition Mode: Common Stop\n");
		if (J_cfg.AcquisitionMode == ACQMODE_TRG_MATCHING)							fprintf(of_list_a, "// Acquisition Mode: Trigger Matching\n");
		if (J_cfg.AcquisitionMode == ACQMODE_STREAMING)								fprintf(of_list_a, "// Acquisition Mode: Streaming\n");
		// Meas Mode
		if (J_cfg.MeasMode == MEASMODE_LEAD_ONLY)	fprintf(of_list_a, "// Measurement Mode: Lead Only\n");
		if (J_cfg.MeasMode == MEASMODE_LEAD_TRAIL)	fprintf(of_list_a, "// Measurement Mode: Lead Trail\n");
		if (J_cfg.MeasMode == MEASMODE_LEAD_TOT8)	fprintf(of_list_a, "// Measurement Mode: Lead TOT8\n");
		if (J_cfg.MeasMode == MEASMODE_LEAD_TOT11)	fprintf(of_list_a, "// Measurement Mode: Lead TOT11\n");

		if ((J_cfg.AcquisitionMode == ACQMODE_COMMON_START) || (J_cfg.AcquisitionMode == ACQMODE_COMMON_STOP)) {
			if (J_cfg.OutFileUnit == OF_UNIT_NS) strcpy(sm1, " deltaT_ns");
			else strcpy(sm1, "deltaT_LSB");
			fprintf(of_list_a, "// deltaT_LSB = %.3f ps", 1000 * J_cfg.LeadTrail_LSB_ns);
		} else {
			if (J_cfg.OutFileUnit == OF_UNIT_NS) strcpy(sm1, "    ToA_ns");
			else strcpy(sm1, "   ToA_LSB");
			fprintf(of_list_a, "// ToA_LSB = %.3f ps", 1000 * J_cfg.LeadTrail_LSB_ns);
		}
		if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) {
			fprintf(of_list_a, "; ToT_LSB = %.3f ps", 1000 * J_cfg.ToT_LSB_ns);
			if (J_cfg.OutFileUnit == OF_UNIT_NS) strcpy(sm2, " ToT_ns");
			else strcpy(sm2, "ToT_LSB");
		}
		if (J_cfg.OutFileUnit == OF_UNIT_NS)  strcpy(sm0, "       Tstamp_us");
		else {
			fprintf(of_list_a, "; Tstamp_LSB = %.1f ns", CLK_PERIOD_5203);
			strcpy(sm0, "      Tstamp_LSB");
		}

		fprintf(of_list_a, "\n");

		fprintf(of_list_a, "// Run%d start time: %s UTC\n", rn, mytime);
		fprintf(of_list_a, "//************************************************\n");
		if (J_cfg.AcquisitionMode == ACQMODE_STREAMING)
			fprintf(of_list_a, "Brd  Ch E %12s", sm1);
		else if (J_cfg.AcquisitionMode == ACQMODE_TRG_MATCHING)
			fprintf(of_list_a, "%s        TrgID   Brd  Ch E %12s", sm0, sm1);  //       Tstamp_us
		else
			fprintf(of_list_a, "%s        TrgID   Brd  Ch %12s", sm0, sm1);
		if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) fprintf(of_list_a, " %12s", sm2);
		fprintf(of_list_a, "\n");
		
		ascii_size = ftell(of_list_a);
	}
	if (of_list_c != NULL) {
		// Write Header
		fprintf(of_list_c, "//************************************************\n");
		fprintf(of_list_c, "//Board:5203\n//File_Format_Version:%s\n//Janus_Release:%s\n", FILE_LIST_VER, SW_RELEASE_NUM);
		// AcqMode
		if (J_cfg.AcquisitionMode == ACQMODE_COMMON_START && !J_cfg.TestMode)			fprintf(of_list_c, "//Acquisition_Mode:common_start\n");
		else if (J_cfg.AcquisitionMode == ACQMODE_COMMON_START && J_cfg.TestMode)		fprintf(of_list_c, "//Acquisition_Mode:test_mode\n");
		if (J_cfg.AcquisitionMode == ACQMODE_COMMON_STOP)								fprintf(of_list_c, "//Acquisition_Mode:common_stop\n");
		if (J_cfg.AcquisitionMode == ACQMODE_TRG_MATCHING)								fprintf(of_list_c, "//Acquisition_Mode:trigger_matching\n");
		if (J_cfg.AcquisitionMode == ACQMODE_STREAMING)									fprintf(of_list_c, "//Acquisition_Mode:streaming\n");
		// Meas Mode
		if (J_cfg.MeasMode == MEASMODE_LEAD_ONLY)	fprintf(of_list_c, "//Measurement_Mode: Lead_Only\n");
		if (J_cfg.MeasMode == MEASMODE_LEAD_TRAIL)	fprintf(of_list_c, "//Measurement_Mode: Lead_Trail\n");
		if (J_cfg.MeasMode == MEASMODE_LEAD_TOT8)	fprintf(of_list_c, "//Measurement_Mode: Lead_TOT8\n");
		if (J_cfg.MeasMode == MEASMODE_LEAD_TOT11)	fprintf(of_list_c, "//Measurement_Mode: Lead_TOT11\n");

		float tmp_clk_period = CLK_PERIOD_5203;
		fprintf(of_list_c, "//Time_unit:%s\n", unit[J_cfg.OutFileUnit]);
		fprintf(of_list_c, "//TStamp_unit:%s\n", tunit[J_cfg.OutFileUnit]);
		fprintf(of_list_c, "//ToA_LSB_value_ps:%1.3f\n", 1000 * J_cfg.LeadTrail_LSB_ns);
		fprintf(of_list_c, "//ToT_LSB_value_ps:%1.3f\n", 1000 * J_cfg.ToT_LSB_ns);
		fprintf(of_list_c, "//TStamp_LSB_value_ns:%.1f\n", CLK_PERIOD_5203);
		fprintf(of_list_c, "//Run#:%d\n", rn);
		fprintf(of_list_c, "//Start_Time_Epoch:%" PRIu64 "\n", Stats.start_time);
		fprintf(of_list_c, "//Start_Time_DateTime_UTC:%s\n", mytime);
		fprintf(of_list_c, "//************************************************\n");
		fprintf(of_list_c, "TStamp_%s,Trg_Id,Board_Id,Num_hits,Ch_Id", tunit[J_cfg.OutFileUnit]);
		if (J_cfg.AcquisitionMode == ACQMODE_COMMON_START && J_cfg.AcquisitionMode == ACQMODE_COMMON_STOP) fprintf(of_list_c, ",Edge");
		if (J_cfg.AcquisitionMode == ACQMODE_COMMON_START || J_cfg.AcquisitionMode == ACQMODE_COMMON_STOP) fprintf(of_list_c, ",deltaT_%s", unit[J_cfg.OutFileUnit]);
		else fprintf(of_list_c, ",ToA_%s", unit[J_cfg.OutFileUnit]);
		if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) fprintf(of_list_c, ",ToT_%s\n", unit[J_cfg.OutFileUnit]);
		else fprintf(of_list_c, "\n");
		csv_size = ftell(of_list_c);
	}
	if (of_servInfo != NULL) {
		fprintf(of_servInfo, "TStampPC\t");
		for (int j = 0; j < J_cfg.NumBrd; ++j) {
			fprintf(of_servInfo, "\tBrd\t\tTStamp_servEvt\t\tBrdTemp\t\tFPGATemp\tTDC0Temp\t");
			if (J_cfg.En_128_ch) fprintf(of_servInfo, "TDC1Temp\t");
			fprintf(of_servInfo, "BrdStatus\t");
		}
		fprintf(of_servInfo, "\n");
	}

	if (of_sync != NULL) {
		if (J_cfg.OutFileUnit == OF_UNIT_NS) fprintf(of_sync, "Brd    Tstamp_us      TrgID \n");
		else fprintf(of_sync, "Brd    Tstamp_LSB      TrgID \n");
	}
	return 0;
}

// ****************************************************
// Save List
// ****************************************************
int SaveList(int brd, double ts, uint64_t trgid, void* generic_ev, uint64_t* of_ToA, uint16_t* of_ToT, int dtq) {
	// Increase sub run due to file size limit
	if (bin_size > J_cfg.MaxOutFileSize && J_cfg.EnableMaxFileSize)
		IncreaseListSubRun(0);
	if (ascii_size > J_cfg.MaxOutFileSize && J_cfg.EnableMaxFileSize)
		IncreaseListSubRun(2);
	if (csv_size > J_cfg.MaxOutFileSize && J_cfg.EnableMaxFileSize)
		IncreaseListSubRun(1);

	// Append event data to list file
	ListEvent_t* ev = (ListEvent_t*)generic_ev;
	if (of_list_b != NULL) {
		datatype = 0x0;
		uint32_t i;
		uint8_t b8 = brd, ch, edge;
		uint16_t size, size_hit, nh = 0;

		// Hit size :
		// Brd + Ch + ToA(64bit in Streaming, 32bit for the other) + ToT(uint16_t or float)
		size_hit = sizeof(b8) + sizeof(ch);
		if (J_cfg.OutFileUnit == OF_UNIT_LSB) {		// Not so nice
			if (J_cfg.AcquisitionMode == ACQMODE_STREAMING) size_hit += sizeof(uint64_t); // ToA 64 bit
			else size_hit += sizeof(uint32_t); // ToA 32 bit
			if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) size_hit += sizeof(uint16_t);	// ToT 16 bit
		} else {
			if (J_cfg.AcquisitionMode == ACQMODE_STREAMING) size_hit += sizeof(double); // ToA double
			else size_hit += sizeof(float); // ToA float
			if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) size_hit += sizeof(float);	// ToT float
		}

		if ((J_cfg.AcquisitionMode == ACQMODE_COMMON_START) || (J_cfg.AcquisitionMode == ACQMODE_COMMON_STOP)) {
			uint64_t tmp_ToA[MAX_NCH] = {};	// Variables to write binary files  
			uint32_t tmp_ToT[MAX_NCH] = {};	// with the same structures
			uint8_t tmp_ch[MAX_NCH] = {};

			for (i = 0; i < MAX_NCH; ++i) {	// To get an array with no empty positions
				if (of_ToA[i] > 0) {
					tmp_ToA[nh] = of_ToA[i];
					tmp_ToT[nh] = of_ToT[i];
					tmp_ch[nh] = i;
					++nh;
				}
			}

			if (nh == 0) return 1;
			size = sizeof(size) + sizeof(ev->tstamp_clk) + sizeof(trgid) + sizeof(ev->nhits) + nh * size_hit;	// TstampClk and tstampUs have the same size
			bin_size += size;

			fwrite(&size, sizeof(size), 1, of_list_b);
			//fwrite(&ts, sizeof(ts), 1, of_list_b);
			if (J_cfg.OutFileUnit == OF_UNIT_LSB) fwrite(&ev->tstamp_clk, sizeof(ev->tstamp_clk), 1, of_list_b);
			else fwrite(&ev->tstamp_us, sizeof(ev->tstamp_us), 1, of_list_b);
			fwrite(&trgid, sizeof(trgid), 1, of_list_b);
			fwrite(&nh, sizeof(nh), 1, of_list_b);
			for (i = 0; i < nh; ++i) {
				uint32_t toa_lsb;
				uint16_t tot_lsb;
				float toa_ns, tot_ns;

				if (J_cfg.OutFileUnit == OF_UNIT_LSB) {
					toa_lsb = (uint32_t)tmp_ToA[i];
					tot_lsb = (uint16_t)tmp_ToT[i];
				} else {
					toa_ns = (float)tmp_ToA[i] * J_cfg.LeadTrail_LSB_ns;
					tot_ns = (float)tmp_ToT[i] * J_cfg.ToT_LSB_ns;
				}
				ch = tmp_ch[i];
				fwrite(&b8, sizeof(b8), 1, of_list_b);
				fwrite(&ch, sizeof(uint8_t), 1, of_list_b);
				if (J_cfg.OutFileUnit == OF_UNIT_LSB) fwrite(&toa_lsb, sizeof(toa_lsb), 1, of_list_b);
				else fwrite(&toa_ns, sizeof(toa_ns), 1, of_list_b);
				if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) {
					if (J_cfg.OutFileUnit == OF_UNIT_LSB) fwrite(&tot_lsb, sizeof(tot_lsb), 1, of_list_b);
					else fwrite(&tot_ns, sizeof(tot_ns), 1, of_list_b);
				}
			}

		} else if (J_cfg.AcquisitionMode == ACQMODE_TRG_MATCHING) {
			size_hit += sizeof(edge);
			nh = ev->nhits;
			if (nh == 0) return 1;

			size = sizeof(size) + sizeof(ev->tstamp_us) + sizeof(trgid) + sizeof(ev->nhits) + nh * size_hit;
			bin_size += size;

			fwrite(&size, sizeof(size), 1, of_list_b);
			if (J_cfg.OutFileUnit == OF_UNIT_LSB) fwrite(&ev->tstamp_clk, sizeof(ev->tstamp_clk), 1, of_list_b);
			else fwrite(&ev->tstamp_us, sizeof(ev->tstamp_us), 1, of_list_b);
			fwrite(&trgid, sizeof(trgid), 1, of_list_b);
			fwrite(&nh, sizeof(nh), 1, of_list_b);
			for (i = 0; i < nh; ++i) {
				uint32_t toa_lsb;
				uint16_t tot_lsb;
				float toa_ns, tot_ns;
				int tmp_c;
				FERS_ChIndex_tdc2ada(ev->channel[i], &tmp_c, brd);
				ch = (uint8_t)tmp_c;
				edge = (ev->edge[i] & 1) ^ J_cfg.InvertEdgePolarity[brd][ch];	

				if (J_cfg.OutFileUnit == OF_UNIT_LSB) {
					toa_lsb = (uint32_t)of_ToA[i];
					tot_lsb = (uint16_t)of_ToT[i];
				} else {
					toa_ns = (float)of_ToA[i] * J_cfg.LeadTrail_LSB_ns;
					tot_ns = (float)of_ToT[i] * J_cfg.ToT_LSB_ns;
				}
				fwrite(&b8, sizeof(b8), 1, of_list_b);
				fwrite(&ch, sizeof(ch), 1, of_list_b);
				fwrite(&edge, sizeof(edge), 1, of_list_b);
				if (J_cfg.OutFileUnit == OF_UNIT_LSB) fwrite(&toa_lsb, sizeof(toa_lsb), 1, of_list_b);
				else fwrite(&toa_ns, sizeof(toa_ns), 1, of_list_b);
				if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) {
					if (J_cfg.OutFileUnit == OF_UNIT_LSB) fwrite(&tot_lsb, sizeof(tot_lsb), 1, of_list_b);
					else fwrite(&tot_ns, sizeof(tot_ns), 1, of_list_b);
				}
			}
		} else if (J_cfg.AcquisitionMode == ACQMODE_STREAMING) {
			size_hit += sizeof(edge);
			nh = ev->nhits;
			if (nh == 0) return 1;

			size = sizeof(size) + sizeof(ev->tstamp_us) + sizeof(ev->nhits) + nh * size_hit;
			bin_size += size;

			fwrite(&size, sizeof(size), 1, of_list_b);
			if (J_cfg.OutFileUnit == OF_UNIT_LSB) fwrite(&ev->tstamp_clk, sizeof(ev->tstamp_clk), 1, of_list_b);
			else fwrite(&ev->tstamp_us, sizeof(ev->tstamp_us), 1, of_list_b);
			fwrite(&nh, sizeof(nh), 1, of_list_b);
			for (i = 0; i < nh; ++i) {
				uint64_t toa_lsb;
				uint16_t tot_lsb;
				double toa_ns;
				float tot_ns;

				int tmp_c;
				FERS_ChIndex_tdc2ada(ev->channel[i], &tmp_c, brd);
				ch = (uint8_t)tmp_c;
				edge = (ev->edge[i] & 1) ^ J_cfg.InvertEdgePolarity[brd][ch];

				if (J_cfg.OutFileUnit == OF_UNIT_LSB) {
					toa_lsb = of_ToA[i];
					tot_lsb = (uint16_t)of_ToT[i];
				} else {
					toa_ns = (double)of_ToA[i] * J_cfg.LeadTrail_LSB_ns;
					tot_ns = of_ToT[i] * J_cfg.ToT_LSB_ns;
				}
				fwrite(&b8, sizeof(b8), 1, of_list_b);
				fwrite(&ch, sizeof(ch), 1, of_list_b);
				fwrite(&edge, sizeof(edge), 1, of_list_b);
				if (J_cfg.OutFileUnit == OF_UNIT_LSB) fwrite(&toa_lsb, sizeof(toa_lsb), 1, of_list_b);
				else fwrite(&toa_ns, sizeof(toa_ns), 1, of_list_b);
				if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) {
					if (J_cfg.OutFileUnit == OF_UNIT_LSB) fwrite(&tot_lsb, sizeof(tot_lsb), 1, of_list_b);
					else fwrite(&tot_ns, sizeof(tot_ns), 1, of_list_b);
				}
			}
		}
	}

	if (of_list_a != NULL) {
		uint32_t i;
		char m1[20] = "", m2[20] = "";
		uint8_t ch;

		if ((J_cfg.AcquisitionMode == ACQMODE_COMMON_START) || (J_cfg.AcquisitionMode == ACQMODE_COMMON_STOP)) {
			if (J_cfg.OutFileUnit == OF_UNIT_LSB) fprintf(of_list_a, "%16" PRIu64 "  %12" PRIu64, ev->tstamp_clk, ev->trigger_id);
			else fprintf(of_list_a, "%16.4lf %12" PRIu64 " ", ev->tstamp_us, ev->trigger_id);
			for (i = 0; i < MAX_NCH; i++) {
				if (J_cfg.OutFileUnit == OF_UNIT_NS) {
					double toa_in_ns = (double)of_ToA[i] * J_cfg.LeadTrail_LSB_ns;
					sprintf(m1, "%12.3f", toa_in_ns);
				} else sprintf(m1, "%12d", (uint32_t)of_ToA[i]);
				if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) {
					if (of_ToT[i] > 0) {
						if (of_ToT[i] == 0xFFFF) sprintf(m2, "%12s", "OVF");
						else if (J_cfg.OutFileUnit == OF_UNIT_NS) sprintf(m2, "%12.3f", of_ToT[i] * J_cfg.ToT_LSB_ns);
						else sprintf(m2, "%12" PRIu32, of_ToT[i]);
					} else sprintf(m2, "           - ");
				}
				if (i == 0) {
					if (of_ToA[i] == 0) fprintf(of_list_a, "\n");
					else fprintf(of_list_a, "   %02d  %02d %s %s \n", brd, 0, m1, m2);
				} else {
					if (of_ToA[i] == 0) continue;
					fprintf(of_list_a, "                                 %02d  %02d %s %s \n", brd, i, m1, m2);
				}
			}
			ascii_size = ftell(of_list_a);
		} else if (J_cfg.AcquisitionMode == ACQMODE_TRG_MATCHING) {
			if (J_cfg.OutFileUnit == OF_UNIT_LSB) fprintf(of_list_a, "%16" PRIu64 "  %12" PRIu64, ev->tstamp_clk, ev->trigger_id);
			else fprintf(of_list_a, "%16.4lf %12" PRIu64 " ", ev->tstamp_us , ev->trigger_id);
			if (ev->nhits == 0) fprintf(of_list_a, "\n");
			for (i = 0; i < ev->nhits; i++) {
				int tmp_c;
				FERS_ChIndex_tdc2ada(ev->channel[i], &tmp_c, brd);
				ch = (uint8_t)tmp_c;
				char edge = ((ev->edge[i] & 1) ^ J_cfg.InvertEdgePolarity[brd][ch]) == EDGE_LEAD ? 'L' : 'T';
				if (i > 0) fprintf(of_list_a, "                              ");	
				if (J_cfg.OutFileUnit == OF_UNIT_NS) {
					double toa_in_ns = (double)of_ToA[i] * J_cfg.LeadTrail_LSB_ns;
					sprintf(m1, "%12.3f", toa_in_ns);
				} else sprintf(m1, "%12" PRIu64, of_ToA[i]);
				if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) {
					if (of_ToT[i] > 0) {
						if (of_ToT[i] == 0xFFFF) sprintf(m2, "%12s", "OVF");
						else if (J_cfg.OutFileUnit == OF_UNIT_NS) sprintf(m2, "%12.3f", of_ToT[i] * J_cfg.ToT_LSB_ns);
						else sprintf(m2, "%12" PRIu16, of_ToT[i]);
					} else sprintf(m2, "             - ");
				}
				fprintf(of_list_a, "   %02d  %02" PRIu8 " %c %s %s \n", brd, ch, edge, m1, m2);
			}
			ascii_size = ftell(of_list_a);
		} else { // Streaming
			if (ev->nhits == 0) fprintf(of_list_a, "\n");
			for (i = 0; i < ev->nhits; i++) {
				int tmp_c;
				FERS_ChIndex_tdc2ada(ev->channel[i], &tmp_c, brd);
				ch = (uint8_t)tmp_c;
				char edge = ((ev->edge[i] & 1) ^ J_cfg.InvertEdgePolarity[brd][ch]) == EDGE_LEAD ? 'L' : 'T';
				if (J_cfg.OutFileUnit == OF_UNIT_NS) {
					double toa_in_ns = (double)of_ToA[i] * J_cfg.LeadTrail_LSB_ns;
					sprintf(m1, "%12.3lf", toa_in_ns);
				} else sprintf(m1, "%12" PRIu64, of_ToA[i]);
				if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) {
					if (of_ToT[i] > 0) {
						if (of_ToT[i] == 0xFFFF) sprintf(m2, "%12s", "OVF");
						else if (J_cfg.OutFileUnit == OF_UNIT_NS) {
							double tot_in_ns = (double)of_ToT[i] * J_cfg.ToT_LSB_ns;
							sprintf(m2, "%12.3f", tot_in_ns);
						} else sprintf(m2, "%12" PRIu16, of_ToT[i]);
					} else sprintf(m2, "           - ");
				}
				fprintf(of_list_a, " %02d  %02" PRIu8 " %c %s %s \n", brd, ch, edge, m1, m2);
			}
			ascii_size = ftell(of_list_a);
		}
	}
	if (of_list_c != NULL) {
		int i = 0;
		char m1[20] = "", m2[20] = "";
		uint8_t ch;
		int nh = 0;
		uint64_t tmp_ToA[MAX_NCH] = {};	// Variables to write binary files  
		uint32_t tmp_ToT[MAX_NCH] = {};	// with the same structures
		uint8_t tmp_ch[MAX_NCH] = {};

		if ((J_cfg.AcquisitionMode == ACQMODE_COMMON_START) || (J_cfg.AcquisitionMode == ACQMODE_COMMON_STOP)) {
			for (i = 0; i < MAX_NCH; ++i) {	// To get an array with no empty positions
				if (of_ToA[i] > 0) {
					tmp_ToA[nh] = of_ToA[i];
					tmp_ToT[nh] = of_ToT[i];
					tmp_ch[nh] = i;
					++nh;
				}
			}

			for (i = 0; i < nh; ++i) {
				uint32_t toa_lsb;
				uint16_t tot_lsb;
				float toa_ns, tot_ns;

				if (J_cfg.OutFileUnit == OF_UNIT_LSB) {
					toa_lsb = (uint32_t)tmp_ToA[i];
					tot_lsb = (uint16_t)tmp_ToT[i];
				} else {
					toa_ns = (double)tmp_ToA[i] * J_cfg.LeadTrail_LSB_ns;
					tot_ns = (double)tmp_ToT[i] * J_cfg.ToT_LSB_ns;
				}
				ch = tmp_ch[i];
				if (J_cfg.OutFileUnit == OF_UNIT_LSB) fprintf(of_list_c, "%" PRIu64 ",%" PRIu64, ev->tstamp_clk, ev->trigger_id);
				else fprintf(of_list_c, "%.4lf,%" PRIu64, ev->tstamp_us, ev->trigger_id);
				fprintf(of_list_c, ",%d,%" PRIu16 ",%d", brd, ev->nhits, ch);
				if (J_cfg.OutFileUnit == OF_UNIT_LSB) fprintf(of_list_c, ",%" PRIu32, toa_lsb);
				else fprintf(of_list_c, ",%lf", toa_ns);
				if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) {
					if (J_cfg.OutFileUnit == OF_UNIT_LSB) fprintf(of_list_c, ",%" PRIu16, tot_lsb);
					else fprintf(of_list_c, ",%f", tot_ns);
				}
				fprintf(of_list_c, "\n");
			}
			csv_size = ftell(of_list_c);
		} else if (J_cfg.AcquisitionMode == ACQMODE_TRG_MATCHING) {
			nh = ev->nhits;
			if (nh == 0) return 1;

			for (i = 0; i < nh; ++i) {
				if (J_cfg.OutFileUnit == OF_UNIT_LSB) fprintf(of_list_c, "%" PRIu64, ev->tstamp_clk);
				else fprintf(of_list_c, "%.4lf", ev->tstamp_us);
				fprintf(of_list_c, ",%" PRIu64 ",%d,%d", trgid, brd, nh);
				uint32_t toa_lsb;
				uint16_t tot_lsb;
				double toa_ns, tot_ns;
				int tmp_c;
				FERS_ChIndex_tdc2ada(ev->channel[i], &tmp_c, brd);
				ch = (uint8_t)tmp_c;
				int edge = (ev->edge[i] & 1) ^ J_cfg.InvertEdgePolarity[brd][ch];

				if (J_cfg.OutFileUnit == OF_UNIT_LSB) {
					toa_lsb = (uint32_t)of_ToA[i];
					tot_lsb = (uint16_t)of_ToT[i];
				} else {
					toa_ns = (double)of_ToA[i] * J_cfg.LeadTrail_LSB_ns;
					tot_ns = of_ToT[i] * J_cfg.ToT_LSB_ns;
				}
				fprintf(of_list_c, ",%" PRIu8 ",%d", ch, edge);

				if (J_cfg.OutFileUnit == OF_UNIT_LSB) fprintf(of_list_c, ",%" PRIu32, toa_lsb);
				else fprintf(of_list_c, ",%lf", toa_ns);
				if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) {
					if (J_cfg.OutFileUnit == OF_UNIT_LSB) fprintf(of_list_c, ",%" PRIu16, tot_lsb);
					else fprintf(of_list_c, ",%f", tot_ns);
				}
				fprintf(of_list_c, "\n");
			}
			csv_size = ftell(of_list_c);
		} else if (J_cfg.AcquisitionMode == ACQMODE_STREAMING) { // Streaming
			nh = ev->nhits;
			if (nh == 0) return 1;

			for (i = 0; i < nh; ++i) {
				if (J_cfg.OutFileUnit == OF_UNIT_LSB) fprintf(of_list_c, "%" PRIu64, ev->tstamp_clk);
				else fprintf(of_list_c, "%.4lf", ev->tstamp_us);
				uint64_t toa_lsb;
				uint16_t tot_lsb;
				double toa_ns;
				float tot_ns;

				int tmp_c;
				FERS_ChIndex_tdc2ada(ev->channel[i], &tmp_c, brd);
				ch = (uint8_t)tmp_c;
				int edge = (ev->edge[i] & 1) ^ J_cfg.InvertEdgePolarity[brd][ch];

				fprintf(of_list_c, ",%" PRIu64 ",%d,%d,%" PRIu8 ",%d", trgid, brd, nh, ch, edge);

				if (J_cfg.OutFileUnit == OF_UNIT_LSB) {
					toa_lsb = of_ToA[i];
					tot_lsb = (uint16_t)of_ToT[i];
				} else {
					toa_ns = (double)of_ToA[i] * J_cfg.LeadTrail_LSB_ns;
					tot_ns = (float)of_ToT[i] * J_cfg.ToT_LSB_ns;
				}

				if (J_cfg.OutFileUnit == OF_UNIT_LSB) fprintf(of_list_c, ",%" PRIu64, toa_lsb);
				else fprintf(of_list_c, ",%lf", toa_ns);
				if (J_cfg.MeasMode != MEASMODE_LEAD_ONLY) {
					if (J_cfg.OutFileUnit == OF_UNIT_LSB) fprintf(of_list_c, ",%" PRIu16, tot_lsb);
					else fprintf(of_list_c, ",%f", tot_ns);
				}
				fprintf(of_list_c, "\n");
			}
			csv_size = ftell(of_list_c);
		}
	}


	if (of_sync != NULL) {
		if (J_cfg.OutFileUnit == OF_UNIT_NS) fprintf(of_sync, "%3d %12.4lf %10" PRIu64 "\n", brd, ev->tstamp_us, trgid);
		else fprintf(of_sync, "%3d %12" PRIu64 " %10" PRIu64 "\n", brd, ev->tstamp_clk, trgid);
	}

	return 0;
}


// ****************************************************
// Save Run Temp HV list
// ****************************************************
int WriteTemp(uint64_t pc_tstamp, ServEvent_t *sev) {
	if (of_servInfo == NULL) return 0;
	fprintf(of_servInfo, "%" PRIu64 "", pc_tstamp);
	for (int i = 0; i < J_cfg.NumBrd; ++i) {
		if (J_cfg.En_128_ch)
			fprintf(of_servInfo, "\t\t%02d\t\t%013" PRIu64 "\t\t%02.2f\t\t%02.2f\t\t%02.2f\t\t%02.2f\t\t0x%04" PRIX16 "\t\t",
				i, sev[i].update_time, sev[i].tempBoard, sev[i].tempFPGA, sev[i].tempTDC[0], sev[i].tempTDC[1], sev[i].Status);
		else
			fprintf(of_servInfo, "\t%02d\t\t%013" PRIu64 "\t\t%02.2f\t\t%02.2f\t\t%02.2f\t\t0x%04" PRIX16 "\t\t",
				i, sev[i].update_time, sev[i].tempBoard, sev[i].tempFPGA, sev[i].tempTDC[0], sev[i].Status);
	}
	fprintf(of_servInfo, "\n");
	return 0;
}




// ****************************************************
// Save Histograms
// ****************************************************
int SaveHistos()
{
	//kv 0
	//	#SPECTRUM_NAME Spettro_Prova
	//	#REAL_TIME 123.000000
	//	#LIVE_TIME 122.000000
	//	#CALIB_COEFF 0.000000 1.000000 0.000000 0.000000
	//	#UNITS keV
	//	#NUM_CHANNELS 16384
	//	#DATA
	int ch, b, i;
	char fname[500];
	char header_Lead[1000], header_ToT[1000];
	FILE *hf;

	// Create header for offline reprocessing
	sprintf(header_Lead, "#VER 1\n");  // Version
	strcpy(header_ToT, header_Lead);
	if (J_cfg.OutFileEnableMask & OUTFILE_LEAD_HISTO) {
		sprintf(header_Lead, "%s#TYPE %x\n", header_Lead, OUTFILE_LEAD_HISTO);
		sprintf(header_Lead, "%s#NBIN %d\n", header_Lead, J_cfg.LeadTrailHistoNbin);
		sprintf(header_Lead, "%s#A0 %f\n", header_Lead, J_cfg.LeadHistoMin);
		sprintf(header_Lead, "%s#A1 %f\n", header_Lead, J_cfg.LeadTrail_LSB_ns * J_cfg.LeadTrailRebin);
		sprintf(header_Lead, "%s#DATA\n", header_Lead);
	} 
	if (J_cfg.OutFileEnableMask & OUTFILE_TOT_HISTO) {
		sprintf(header_ToT, "%s#TYPE %x\n", header_ToT, OUTFILE_TOT_HISTO);
		sprintf(header_ToT, "%s#NBIN %d\n", header_ToT, J_cfg.ToTHistoNbin);
		sprintf(header_ToT, "%s#A0 %f\n", header_ToT, J_cfg.ToTHistoMin);
		sprintf(header_ToT, "%s#A1 %f\n", header_ToT, J_cfg.ToT_LSB_ns * J_cfg.ToTRebin);
		sprintf(header_ToT, "%s#DATA\n", header_ToT);
	}

	for(b=0; b<J_cfg.NumBrd; b++) {
		for(ch=0; ch<J_cfg.NumCh; ch++) {
			if (J_cfg.OutFileEnableMask & OUTFILE_LEAD_HISTO) {
				if (Stats.H1_Lead[b][ch].H_cnt > 0) {

					sprintf(fname, "%sRun%d_LeadHisto_%d_%d.txt", J_cfg.DataFilePath, RunVars.RunNumber, b, ch);
					hf = fopen(fname, "w");
					if (hf != NULL) {
						fprintf(hf, "%s", header_Lead);
						for (i=0; i<(int)Stats.H1_Lead[b][ch].Nbin; i++)	// J_cfg.ToTHistoNbin
							fprintf(hf, "%" PRId32 "\n", Stats.H1_Lead[b][ch].H_data[i]);
						fclose(hf);
					}
				}
			}
			if (J_cfg.OutFileEnableMask & OUTFILE_TRAIL_HISTO) {
				if (Stats.H1_Trail[b][ch].H_cnt > 0) {
					sprintf(fname, "%sRun%d_TrailHisto_%d_%d.txt", J_cfg.DataFilePath, RunVars.RunNumber, b, ch);
					hf = fopen(fname, "w");
					if (hf != NULL) {
						fprintf(hf, "%s", header_Lead);
						for (i=0; i<(int)Stats.H1_Trail[b][ch].Nbin; i++)	// J_cfg.ToTHistoNbin
							fprintf(hf, "%" PRId32 "\n", Stats.H1_Trail[b][ch].H_data[i]);
						fclose(hf);
					}
				}
			}
			if (J_cfg.OutFileEnableMask & OUTFILE_TOT_HISTO) {
				if (Stats.H1_ToT[b][ch].H_cnt > 0) {

					sprintf(fname, "%sRun%d_ToTHisto_%d_%d.txt", J_cfg.DataFilePath, RunVars.RunNumber, b, ch);
					hf = fopen(fname, "w");
					if (hf != NULL) {
						fprintf(hf, "%s", header_ToT);
						for (i=0; i<(int)Stats.H1_ToT[b][ch].Nbin; i++)
							fprintf(hf, "%" PRId32 "\n", Stats.H1_ToT[b][ch].H_data[i]);
						fclose(hf);
					}
				}
			}
		}
	}
	return 0;
}


/******************************************************
* Save Measurements
******************************************************/
int SaveMeasurements()
{
	int b, ch;
	char fname[500];
	FILE *mf;

	if (J_cfg.OutFileEnableMask & OUTFILE_LEAD_MEAS) {
		sprintf(fname, "%sRun%d_LeadMeas.txt", J_cfg.DataFilePath, RunVars.RunNumber);
		mf = fopen(fname, "w");
		if (mf != NULL) {
			fprintf(mf, "BRD  CH    MEAN(ns)    RMS(ns)\n");
			for(b=0; b<J_cfg.NumBrd; b++) {
				for(ch=0; ch<J_cfg.NumCh; ch++) {
					fprintf(mf, "%3d %3d  %10.3f %10.3f\n", b, ch, Stats.LeadMeas[b][ch].mean, Stats.LeadMeas[b][ch].rms);
				}
			}
			fclose(mf);
		}
	}
	if (J_cfg.OutFileEnableMask & OUTFILE_TRAIL_MEAS) {
		sprintf(fname, "%sRun%d_TrailMeas.txt", J_cfg.DataFilePath, RunVars.RunNumber);
		mf = fopen(fname, "w");
		if (mf != NULL) {
			fprintf(mf, "BRD  CH    MEAN(ns)    RMS(ns)\n");
			for(b=0; b<J_cfg.NumBrd; b++) {
				for(ch=0; ch<J_cfg.NumCh; ch++) {
					fprintf(mf, "%3d %3d  %10.3f %10.3f\n", b, ch, Stats.TrailMeas[b][ch].mean, Stats.TrailMeas[b][ch].rms);
				}
			}
			fclose(mf);
		}
	}
	if (J_cfg.OutFileEnableMask & OUTFILE_TOT_MEAS) {
		sprintf(fname, "%sRun%d_ToTMeas.txt", J_cfg.DataFilePath, RunVars.RunNumber);
		mf = fopen(fname, "w");
		if (mf != NULL) {
			fprintf(mf, "BRD  CH    MEAN(ns)    RMS(ns)\n");
			for(b=0; b<J_cfg.NumBrd; b++) {
				for(ch=0; ch<J_cfg.NumCh; ch++) {
					fprintf(mf, "%3d %3d  %10.3f %10.3f\n", b, ch, Stats.ToTMeas[b][ch].mean, Stats.ToTMeas[b][ch].rms);
				}
			}
			fclose(mf);
		}
	}
	return 0;
}


/******************************************************
* Save Run Info
******************************************************/
int cnc_write(char* path, char cncp[MAX_NCNC][200])
{
	for (int i = 0; i < FERSLIB_MAX_NCNC; i++)
		if (strcmp(cncp[i], path) == 0) return 1;
	return 0;
}

int SaveRunInfo()
{
	char str[200];
	char fname[500];
	char read_cnc[MAX_NCNC][200];
	struct tm* t;
	time_t tt;
	int b;
	FILE* cfg;
	FILE* iof;
	uint32_t FPGArev = 0, MICrev = 0, pid = 0;

	sprintf(fname, "%sRun%d_Info.txt", J_cfg.DataFilePath, RunVars.RunNumber);
	iof = fopen(fname, "w");

	uint8_t rr;

	fprintf(iof, "********************************************************************* \n");
	fprintf(iof, "Run n. %d\n\n", RunVars.RunNumber);
	tt = (time_t)(Stats.start_time / 1000); //   RO_Stats.StartTime_ms / 1000);
	t = localtime(&tt);
	strftime(str, sizeof(str) - 1, "%d/%m/%Y %H:%M", t);
	fprintf(iof, "Start Time: %s\n", str);
	tt = (time_t)(Stats.stop_time / 1000); //   RO_Stats.StopTime_ms / 1000);
	t = localtime(&tt);
	strftime(str, sizeof(str) - 1, "%d/%m/%Y %H:%M", t);
	fprintf(iof, "Stop Time:  %s\n", str);
	fprintf(iof, "Elapsed time = %.3f s\n", Stats.current_tstamp_us[0] / 1e6); //     RO_Stats.CurrentTimeStamp_us / 1e6);
	fprintf(iof, "********************************************************************* \n");
	fprintf(iof, "\n\n********************************************************************* \n");
	fprintf(iof, "Setup:\n");
	fprintf(iof, "********************************************************************* \n");
	fprintf(iof, "Software Version: Janus %s\n", SW_RELEASE_NUM);
	fprintf(iof, "Output data format version: %s\n", FILE_LIST_VER);
	FERS_BoardInfo_t BoardInfo;
	FERS_CncInfo_t CncInfo;
	char* cc, cpath[100];
	int cnc = 0;

	for (b = 0; b < J_cfg.NumBrd; b++) {
		char* cc, cpath[100];
		if (((cc = strstr(J_cfg.ConnPath[b], "tdl")) != NULL)) {  // TDlink used => Open connection to concentrator (this is not mandatory, it is done for reading information about the concentrator)
			FERS_Get_CncPath(J_cfg.ConnPath[b], cpath);
			if (!cnc_write(cpath, read_cnc)) {
				rr = FERS_GetCncInfo(cnc_handle[cnc], &CncInfo);
				sprintf(read_cnc[cnc], "%s", cpath);
				if (rr == 0) {
					fprintf(iof, "Concentrator %d:\n", cnc);
					fprintf(iof, "\tFPGA FW revision = %s\n", CncInfo.FPGA_FWrev);
					fprintf(iof, "\tSW revision = %s\n", CncInfo.SW_rev);
					fprintf(iof, "\tPID = %d\n\n", CncInfo.pid);
					if (CncInfo.ChainInfo[0].BoardCount == 0) { 	// Rising error if no board is connected to link 0
						Con_printf("LCSm", "ERROR: read concentrator info failed in SaveRunInfo\n");
						return -2;
					}
					for (int l = 0; l < 8; l++) {
						if (CncInfo.ChainInfo[l].BoardCount > 0)
							fprintf(iof, "Found %d board(s) connected to TDlink n. %d\n", CncInfo.ChainInfo[l].BoardCount, l);
					}
				} else {
					fprintf(iof, "ERROR: Cannot read concentrator %02d info\n", cnc);
					return -2;
				}
				++cnc;
			}
		}

		rr = FERS_GetBoardInfo(handle[b], &BoardInfo); // Read Board Info
		if (rr != 0)
			return -1;
		char fver[100];
		//if (FPGArev == 0) sprintf(fver, "BootLoader"); DNIN: mixed with an old version checker with register.
		//else 
		sprintf(fver, "%d.%d (Build = %04X)", (BoardInfo.FPGA_FWrev >> 8) & 0xFF, (BoardInfo.FPGA_FWrev) & 0xFF, (BoardInfo.FPGA_FWrev >> 16) & 0xFFFF);
		fprintf(iof, "Board %d:\n", b);
		fprintf(iof, "\tModel = %s\n", BoardInfo.ModelName);
		fprintf(iof, "\tPID = %" PRIu32 "\n", BoardInfo.pid);
		fprintf(iof, "\tFPGA FW revision = %s\n", fver);
		fprintf(iof, "\tuC FW revision = %08X\n", BoardInfo.uC_FWrev);
	}

	if (!offline_conn && FERS_GetParam_int(handle[0], "HighResClock") != HRCLK_DISABLED) {
		fprintf(iof, "\n********************************************************************* \n");
		fprintf(iof, "Phase between TDC clk and TDlink recovered clk:\n");
		for (b = 0; b < J_cfg.NumBrd; b++) {
			int no_clk;
			double mean = 0, rms = 0;
			ReadClockPhase(handle[b], &no_clk, &mean, &rms);
			if (no_clk) {
				fprintf(iof, "  Brd %02d: Missing Ext Clk\n", b);
			} else {
				fprintf(iof, "  Brd %02d: %5.1lf deg = %5.2lf ns (rms = %.2lf ns)\n", b, (mean / 65536) * 180, (mean / 65536) * TDC_CLK_PERIOD / 2, (rms / 65536) * TDC_CLK_PERIOD / 2);
			}
		}
	}
	fprintf(iof, "********************************************************************* \n");

	// CTIN: save event statistics
	/*
	fprintf(iof, "\n\n********************************************************************* \n");
	fprintf(iof, "Statistics:\n");
	fprintf(iof, "********************************************************************* \n");
	fprintf(iof, "Total Acquired Events: %lld (Rate = %.3f Kcps)\n", (long long)RO_Stats.EventCnt, (float)RO_Stats.EventCnt/(RO_Stats.CurrentTimeStamp_us/1000));
	for (b = 0; b < J_cfg.NumBrd; b++) {
		fprintf(iof, "\nBoard %d (s.n. %d)\n", b, DGTZ_SerialNumber(handle[b]));
		fprintf(iof, "Lost Events: %lld (%.3f %%)\n", (long long)RO_Stats.LostEventCnt[b], PERCENT(RO_Stats.LostEventCnt[b], RO_Stats.LostEventCnt[b] + RO_Stats.EventCnt));
	}
	*/
	if (J_cfg.EnableJobs) {
		sprintf(fname, "%sJanus_Config_Run%d.txt", J_cfg.DataFilePath, RunVars.RunNumber);
		cfg = fopen(fname, "r");
		if (cfg == NULL) 
			sprintf(fname, "%s", CONFIG_FILENAME);
	} else 
		sprintf(fname, "%s", CONFIG_FILENAME);

	fprintf(iof, "\n\n********************************************************************* \n");
	fprintf(iof, "Config file: %s\n", fname);
	fprintf(iof, "********************************************************************* \n");
	cfg = fopen(fname, "r");
	if (cfg != NULL) {
		while (!feof(cfg)) {
			char line[500];
			fgets(line, 500, cfg);
			fputs(line, iof);
		}
	}

	fclose(iof);
	return 0;
}

