/* exp2gdf converts the EMG file from .EXP (Medelec export file) to .GDF */
// copyleft CC-BY-SA 3.0 by Petr Heřman aka Kychot
// v. 0.12 – NeuJahr 2011-01-01 im Wald - downsampling

int	program_vermajor = 0;
int	program_verminor = 12;
const char *program_name;

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <getopt.h>
#include <errno.h>
//#include <biosig.h>
#include "biosig.h"

/* defs */
#define MAXCHAN 	20
#define RBUFSIZE	1048576
#define WBUFSIZE	1048576


/* EXP header */
struct exphdrtype {
  unsigned short nchan;
  unsigned short sample_int_us;
  unsigned short gain[MAXCHAN];
};

/* functions */
void exphdrread( void);		// reads header of the EXP file
void hdrset( void);		// sets the values into hdr
void datablock0( void);		// copies the signal data
void datablock1( void);		// copies the signal data
void datablock2( void);		// copies and downsamples the signal data

/* global data */
int		VERBOSE = 0;
char		*infilename = NULL;
char		*outfilename = NULL;
FILE		*INFILE;
FILE		*OUTFILE;
HDRTYPE		*hdr;
struct exphdrtype exphdr;
struct stat	stbuf;
int		status;
int		downsampl = 1;

int64_t		nsamples;
int16_t		rbuf[ RBUFSIZE];		// buffer for fread
int16_t		wbuf[ WBUFSIZE];		// buffer for fwrite
long		datapos;			// beginning of the binary data
biosig_data_type databuf[ 100];



void print_help( FILE *stream)
{
  fprintf( stream, "\tUsage:\n\t%s options file.EXP file.gdf\n", program_name);
  fprintf( stream,
	  "\t\tfile.EXP = input file\n"
	  "\t\tfile.gdf = output file\n"
          "\t\t-h  --help         Display this help screen.\n"
          "\t\t-v  --version      Program version.\n"
          "\t\t-V  --verbose=v    Verbosity level.\n"
          "\t\t-d  --downsample=d  Downsampling d-times (1,2,...)\n"
	  "\t\t\n");
}

void print_version(FILE *stream)
{
  fprintf(stream, "\t%s %d.%d\n", program_name, program_vermajor, program_verminor);
}


/* main – evaluates args and STDIN */

int main(int argc, char *argv[]){
  //size_t      count;
  //uint16_t    numopt = 0;
  //enum FileFormat SOURCE_TYPE, TARGET_TYPE=GDF;               // type of file format
  //int         COMPRESSION_LEVEL=0;

  program_name = argv[0];
  int			next_option;
  const char	       *short_options = "hvV:d:";
  const struct option	long_options[] = {
    //long option, arg?, NULL, short
    { "help",         0, NULL, 'h' },
    { "version",      0, NULL, 'v' },
    { "verbose",      1, NULL, 'V' },
    { "downsampling", 1, NULL, 'd' },
    { NULL,           0, NULL,  0  }
  };

  /* Jmeno souboru pro vystup nebo NULL pro stdout */
  //const char *output_file = NULL;

  /* Jmeno souboru pro vstup nebo NULL pro stdin */
  //const char *input_file = NULL;

  do {
    next_option = getopt_long(argc, argv, short_options, long_options, NULL);

    switch(next_option)
      {
      case 'h': /* -h  --help */
	print_help(stdout);
	exit(1);
      case 'v': /* -v  --version */
	print_version(stdout);
	exit(1);
      case 'V': /* -V  --verbose */
	errno = 0;
	VERBOSE = strtol(optarg, NULL, 10);
	if(errno !=0) {perror("strtol"); print_help(stdout); exit(1);}
	break;
      case 'd': /* -d  --downsampling */
	errno = 0;
	downsampl = strtol(optarg, NULL, 10);
	if(errno !=0) {perror("strtol"); print_help(stdout); exit(1);}
	break;
      case '?': /* illegal option */
	print_help(stderr);
	exit(1);
      case -1 : /* end loop */
	break;
      default:
	abort();
      }
  } while(next_option != -1);

  if(argc-optind > 2){
      fprintf(stderr,"\tToo much arguments: ");
      while (optind < argc) fprintf(stderr, "%s ", argv[optind++]); printf("\n");
      print_help(stdout);
      exit(1);
  }

  switch (argc-optind) {
    case 0:
      fprintf(stderr,"\tMissing input file name!\n");
    case 1:
      fprintf(stderr,"\tMissing output file name!\n");
      print_help(stdout);
      exit(1);
    case 2:
      infilename   = argv[optind++];
      outfilename  = argv[optind++];
  }

  if (VERBOSE) printf("verbosity=%d, downsampling=%d\n", VERBOSE, downsampl);
  if (VERBOSE>=1) printf( "%s -> %s\n", infilename, outfilename);

  stat( infilename, &stbuf);
  if (VERBOSE>8) printf( "filesize = %lu\n", stbuf.st_size);
  INFILE = fopen( infilename, "rb");

  exphdrread();				// read the EXP header into exphdr
  nsamples = ((stbuf.st_size/2) - 2 - exphdr.nchan)/exphdr.nchan;
  hdr = constructHDR( exphdr.nchan, 0);	// NS channels, N_EVENT event elements
  //hdr2ascii(hdr,stdout,1);
  hdrset();				// set the appropriate values into hdr
  //hdr2ascii(hdr,stdout,3);

  //printf("tell position: INFILE = %ld, OUTFILE = %ld\n", ftell( INFILE), ftell( OUTFILE));

  //hdr = sopen(outfilename, "w", hdr);
  sopen(outfilename, "w", hdr);
  if ((status=serror())) { destructHDR(hdr); exit(status);}

  if (VERBOSE>7)
    printf("\n[221] File %s opened. AS.bpb (bytes per block)=%i NS=%i NRec=%Li Des=%i\n",
	   hdr->FileName, hdr->AS.bpb, hdr->NS, hdr->NRec, hdr->FILE.Des);

  if(VERBOSE>=2) hdr2ascii(hdr, stdout, VERBOSE-2);

  //swrite(databuf, hdr->NRec, hdr);
  //swrite(databuf, 100, hdr);
  //if (VERBOSE>7) printf("[231] SWRITE finishes\n");
  //if ((status=serror())) { destructHDR(hdr); exit(status);}
  sclose(hdr);
  if (VERBOSE>7) printf("[241] SCLOSE finished\n");

  //exit(serror());

  OUTFILE = fopen( outfilename, "ab");
  datablock2();
  fclose( OUTFILE);
  fclose( INFILE);
  destructHDR(hdr);
  return( 0);
}

void exphdrread( void) {
  size_t readed;
  readed = fread( &exphdr, 2, 2, INFILE);		  // 2*2 bytes: nchan, sample_int_us
  if(VERBOSE>7) printf("nchan = %d, sample_int_us = %d\ngain: ",exphdr.nchan, exphdr.sample_int_us);
  readed = fread( exphdr.gain, 2, exphdr.nchan, INFILE);  // 2*nchan bytes: gain
  int ch;
  for( ch=0; ch<exphdr.nchan; ch++) {
    if (VERBOSE>7) printf( "  %d", exphdr.gain[ch]);
  }
  printf( "\n");
}

void hdrset( void) {		// set appropriate values into hdr:
  hdr->TYPE = GDF;
  hdr->VERSION = 2.10;
  hdr->FileName = outfilename;
  hdr->NS = exphdr.nchan;			// number of channels
  //  hdr->HeadLen = 256*exphdr.nchan + 512;	// the length of all headers [unit = bytes]
  hdr->SPR = 1;					// samples per record
  // hdr->NRec = ((stbuf.st_size/2) - 2 - exphdr.nchan)/exphdr.nchan;	// Number of records
  hdr->NRec = nsamples/downsampl;				// Number of records
  hdr->SampleRate = 1000000/(exphdr.sample_int_us*downsampl);	// Sampling frequency [Hz]
  //hdr->T0 = 							// starttime of recording
  //hdr->tzmin =				// time zone (minutes of difference to UTC


  strncpy (&(hdr->ID.Recording[0]), "00", MAX_LENGTH_RID+1);
  strncpy (&(hdr->ID.Technician[0]), "K.", MAX_LENGTH_TECHNICIAN+1);
  hdr->ID.Manufacturer.Name = "Medelec";
  hdr->ID.Manufacturer.Model = "EMG";

  strncpy (&(hdr->Patient.Name[0]), "XY", MAX_LENGTH_NAME+1);
  strncpy (&(hdr->Patient.Id[0]), "00", MAX_LENGTH_PID+1);

  /* channels */
  int ch;
  printf( "Ch |OnOff|Sam/Rec|\n");  
  for (ch = 0; ch < hdr->NS; ch++) {
    //hdr->CHANNEL[ch].OnOff  = 1;
    hdr->CHANNEL[ch].GDFTYP   = 3;	// int16
    //hdr->CHANNEL[ch].GDFTYP   = 4;	// uint16
    hdr->CHANNEL[ch].SPR      = 1;
    hdr->CHANNEL[ch].PhysMin  = -50;
    hdr->CHANNEL[ch].PhysMax  = 50;
    hdr->CHANNEL[ch].DigMin   = -2047;
    hdr->CHANNEL[ch].DigMax   = 2048;
    hdr->CHANNEL[ch].Cal      = 10;
    hdr->CHANNEL[ch].Off      = 0;
    hdr->CHANNEL[ch].LowPass  = 100;
    hdr->CHANNEL[ch].HighPass = 1000;
    hdr->CHANNEL[ch].Notch    = 0;	// does not allow empty value
    //hdr->CHANNEL[ch].PhysDim  = "uV";	// deprecated
    hdr->CHANNEL[ch].PhysDimCode  = 4275; // uV
    hdr->CHANNEL[ch].LeadIdCode = 0;
  }
  strncpy (&(hdr->CHANNEL[0].Transducer)[0], "",  MAX_LENGTH_TRANSDUCER+1);
  strncpy (&(hdr->CHANNEL[1].Transducer)[0], "",  MAX_LENGTH_TRANSDUCER+1); 
  strncpy (&(hdr->CHANNEL[2].Transducer)[0], "",  MAX_LENGTH_TRANSDUCER+1); 
  strncpy (&(hdr->CHANNEL[3].Transducer)[0], "",  MAX_LENGTH_TRANSDUCER+1);

  strncpy (&(hdr->CHANNEL[0].Label)[0], "#1",  MAX_LENGTH_LABEL+1);
  strncpy (&(hdr->CHANNEL[1].Label)[0], "#2",  MAX_LENGTH_LABEL+1); 
  strncpy (&(hdr->CHANNEL[2].Label)[0], "#3",  MAX_LENGTH_LABEL+1); 
  strncpy (&(hdr->CHANNEL[3].Label)[0], "#4",  MAX_LENGTH_LABEL+1);
}

void datablock0( void) {	// copy only
  unsigned readed;
  while(( readed = fread( rbuf, 2, RBUFSIZE, INFILE) )) {
    fwrite( rbuf, 2, readed, OUTFILE);
  }
}

// <string.h>
// bcopy,
// memccpy, memcpy, memmove, mempcpy,
// strdup, strpcpy, strcpy, strncpy,
// wcscpy, wcsncpy,  wmemcpy, wmempcpy


void datablock1( void) {
  unsigned readed;
  int nbuf = 0;
  while(( readed = fread( rbuf, 2, RBUFSIZE, INFILE) )) {
    if( VERBOSE>=2){ nbuf++; printf( "%d,", nbuf); fflush(stdout);}
    memcpy( wbuf, rbuf, 2*readed);
    fwrite( wbuf, 2, readed, OUTFILE);
  }
  if( VERBOSE>=2) printf(" done\n");
}

void datablock2( void) {		// downsampling
  int readed;				// [integers] readed into readbuffer
  int nbuf      = 0;			// how much times is the buffer used
  int nchan     = exphdr.nchan;		// number of channels
  int blocksize = nchan*downsampl; 	// [integers] one source block will be reduced to one dest. timepoint
  int bcopy     = 2*nchan;		// how much bytes to copy at one timepoint
  int maxblocks = RBUFSIZE / blocksize;	// [integers] how much blocks fits into the readbuffer
  int amount    = maxblocks*blocksize;	// [integers] max. amount of which will be read into the buffer
  int nblocks, blkc;			// number of blocks actualy readed, block counter
  int16_t *src, *dest;			// source pointer, destination pointer
  while(( readed = fread( rbuf, 2, amount, INFILE) )) {
    if( VERBOSE>=2) {nbuf++; printf("%d,", nbuf); fflush( stdout);}
    blkc = nblocks = readed / blocksize;
    src  = rbuf;
    dest = wbuf;
    while( blkc--){
      memcpy( dest, src, bcopy);
      src  += blocksize;
      dest += nchan;
    }
    fwrite( wbuf, 2, nblocks*nchan, OUTFILE);
  }
  if( VERBOSE>=2) printf(" done\n");
}

/*
void datablock9( int ch) {	// read all channels, no seek - more quick, about 0.7s
  long initial_pos = datapos+2*ch;  
  //int skip = 2*(exphdr.nchan-1);
  unsigned short buf[MAXCHAN];

  printf( "ch = %d, initial_pos = %ld\n", ch, initial_pos);
  fseek( INFILE, initial_pos, SEEK_SET);
  while( fread( buf, 2, exphdr.nchan, INFILE)) {
    fwrite( &buf[0], 2, 1, OUTFILE);
  }
}
*/


/*----------- původní soubor -------------*/
#if 0
/*   exp2gdf – adapted by Petr Heřman aka Robot from:
    $Id: save2gdf.c 2360 2010-03-06 00:37:00Z schloegl $
    GNU General Public License.
v.05 - zatím je to jen zjednoduššený save2gdf bez přepínačů,
konvertuje z GDF do GDF a vypisuje různé věci z hlaviček
*/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "biosig-dev.h"

#define MIN2HHMM(m) (sprintf( "%+02u00", (m)/60))

//VERBOSE_LEVEL = 9;

char        *source, *dest, tmp[1024];

void hdrread( void);
void hdrprint( void);


  tzset();

  if (VERBOSE>8) printf("[002] hdr->TYPE = %d, hdr->VERSION = %f\n",hdr->TYPE,hdr->VERSION);

  hdrread();
  if (VERBOSE>1) hdrprint();

  if (dest!=NULL) count = sread(NULL, 0, hdr->NRec, hdr);
     // (biosig_data_type* DATA, size_t START, size_t LEN, HDRTYPE* hdr);
     /* LEN data segments are read from file associated with hdr, starting from
        segment START. The data is copied into DATA; if DATA == NULL, a
        sufficient amount of memory is allocated, and the pointer to the data
        is available in hdr->data.block.

	In total, LEN * hdr->SPR * NS samples are read and stored in 
	data type of biosig_data_type (currently double). 
	NS is the number of channels with non-zero hdr->CHANNEL[].OnOff. 
	The number of successfully read data blocks is returned.

 	A pointer to the data block is also available from hdr->data.block, 
	the number of columns and rows is available from 
	hdr->data.size[0] and hdr->data.size[1] respectively. 
 
	The following flags will influence the result.
 	hdr->FLAG.UCAL = 0 	scales the data to its physical values
 	hdr->FLAG.UCAL = 1 	does not apply the scaling factors

 	hdr->FLAG.OVERFLOWDETECTION = 0 does not apply overflow detection 
 	hdr->FLAG.OVERFLOWDETECTION = 1: replaces all values that exceed 
		the dynamic range (defined by Phys/Dig/Min/Max)

	hdr->FLAG.ROW_BASED_CHANNELS = 0 each channel is in one column 	
	hdr->FLAG.ROW_BASED_CHANNELS = 1 each channel is in one row 	 */


  biosig_data_type* data = hdr->data.block;

  if ((VERBOSE>8) && (hdr->data.size[0]*hdr->data.size[1]>500))
    printf("[122] UCAL=%i %e %e %e \n", hdr->FLAG.UCAL, data[100], data[110], data[500+hdr->SPR]);

  if ((status=serror())) { destructHDR(hdr); exit(status);};

  if (VERBOSE>8)
    printf("\n[129] SREAD on %s successful [%i,%i].\n", hdr->FileName, hdr->data.size[0], hdr->data.size[1]);

  if (VERBOSE>8)
    printf("\n[130] File  %s =%i/%i\n", hdr->FileName, hdr->FILE.OPEN, hdr->FILE.Des);

  sclose(hdr);

  SOURCE_TYPE = hdr->TYPE;


  /*** write file ***/
  strcpy(tmp,dest);

}


void hdrread( void) {
  int status, k;

  hdr->FileName = source;
  hdr = sopen(source, "r", hdr);	// (char* FileName, char* MODE, HDRTYPE* hdr)

  //if (VERBOSE>8) printf("[112] SOPEN-R finished\n");
  if ((status=serror())) { destructHDR(hdr); exit(status);}
  if (VERBOSE>8) printf("[113] SOPEN-R finished\n");

  // all channels are converted - channel selection currently not supported
  for (k = 0; k < hdr->NS; k++) {
    if (! hdr->CHANNEL[k].OnOff && hdr->CHANNEL[k].SPR) {
      if ((hdr->SPR/hdr->CHANNEL[k].SPR)*hdr->CHANNEL[k].SPR != hdr->SPR)
	printf("Warning: channel %i might be decimated!\n",k+1);
    };
    // hdr->CHANNEL[k].OnOff = 1;   // convert all channels
  }
}

void hdrprint( void) {		   // prints some items from the header
  printf ("[[HDRPRINT:]]\n");
  printf(
    "[114] hdr->TYPE = %d, hdr->VERSION = %f\n",hdr->TYPE,hdr->VERSION);

  printf("HeadLen = %zu, NS = %zu, SPR = %zu, NRec = %llu, SampleRate = %lf\n",
    hdr->HeadLen, hdr->NS, hdr->SPR, hdr->NRec, hdr->SampleRate);
    // 2 headers; Nuber of channelS; SamplesPerRecord (block);
    // Number of Records/blocks (-1 indicates length is unknown)
  printf ("HeadLen=%zu, NS=%u, SPR=%zu, NRec=%llu, SampleRate=%lf\n",
	 hdr->HeadLen, hdr->NS, hdr->SPR, hdr->NRec, hdr->SampleRate );  

  /* starttime of recording */
  struct tm* T0tm;
  char timestr[21+6];
  T0tm = gdf_time2tm_time(hdr->T0);
  // T0tm->__tm_gmtoff;
  strftime( timestr, 20, "%F %T ", T0tm);	// ISO 8601: %Y-%m-%dT%H:%M:%S
  if (VERBOSE>8) printf( "[116] T0 = %s %+03d00\n", timestr, hdr->tzmin/60);


  printf ("[[END(HDRPRINT)]]\n");

  //hdr2ascii(hdr,stdout,VERBOSE);
  hdr2ascii(hdr,stdout,3);	// verbosity: 1=basic, 2=channels, 3=eventtable
}

#endif