#!/usr/bin/perl
# beformat1.pl: ergaenzt begaze-file um ggf. fehlende messages (aus dem
#   Rohdatenfile)
#
# FIXME:
#   > abschliessender Sicherheitscheck nutzt unix-system-commandos in 
#     `...` -> funktioniert nur in linux, msys, djgpp
#
# assert: 
# 1. strlen(timestamp) in rawfile == 10
# 2. rawfile enthaelt daten fuer alle sets, die auch in begfile vorkommen,
#    in gleicher reihenfolge
#
# Uli
#
#
#
# Formatting done by Begaze (Column/Value):
# 
# Fixations:
# 0  Event Type
# 1  Set
# 2  Start
# 3  End
# 4  Duration
# 5  Location X
# 6  Location Y
# 7  Dispersion X
# 8  Dispersion Y
# 9  Object hit
my $FIXCOLS = 9;		#-=1 weil es auf die Tabs ankommt

#
# Saccades:
# 0  Event Type
# 1  Set
# 2  Start
# 3  End
# 4  Duration 
# 5  Start Pos X
# 6  Start Pos Y	
# 7  End Pos X	
# 8  End Pos Y	
# 9  Amplitude
# 10 Peak Speed
# 11 Peak Speed At
# 12 Average Speed
# 13 Peak Accel.
# 14 Peak Decel
# 15 Average Accel
#
my $SACCOLS = 15;		#-=1 weil es auf die Tabs ankommt

# Blinks:
# 0  Event Type
# 1  Set
# 2  Start 
# 3  End
# 4  Duration

use Term::ReadKey;

#Variablen


#Konstante, die spaeteren hack aktiviert (=1); 
# fuer workaround um ivx-bug (setnr in 1st line == 0) 
my $IVX_ZERO_LINE_BUG = 0;


# Define seperator (TAB!)
my $sep = "\t";
#my $sep = ";";
my $flag=0;
my $done=0;

my @begbuffer;			#Buffer for one set out of begaze-file
my @rawbuffer;			#Buffer for one set out of raw-file

my $nextbegline;		#zwischenspeichern der ersten Zeile des naechsten sets
my $curline;			#aktuelle Zeile, wird aufgebaut und in begbuffer gepusht
my @begbuffer;
my $begstart=0;			#Startzeitpunkt des aktuellen Sets!
my $begset=-1;			#aktuelle setnum

#subs

sub showusage
  {
    print STDERR "usage: $0 BEGAZE_FILE RAWDATA_FILE OUTPUT_FILE\n";
    exit 1;
  }

sub message_contained		#($message)
  {
    my $message = $_[0];
    $message =~ s/\?/\\\?/;
    #    $message = qr/$message/;
    #    print STDERR "dbg: message_contained: started for $message\n";
    #run through begbuffer
    foreach $i (@begbuffer) {
      #	chomp($i);
      #	print STDERR "dbg: processing $i\n" if ($begset eq 9);
      @begcols = split /\t/, $i;
	
      if (($i =~ /^User Event/) && ($begcols[3] =~ /$message/)) { #wenn fuer Vgl. relevante Zeile gefunden
	#	    print STDERR "dbg: true $i\n" if ($begset eq 9);
	return 1;
      }
    }
    #    print STDERR "dbg: false $i\n" if ($begset eq 9);
    return 0;
  }

sub insert_message		#($message)
  {
    my $line = $_[0];
    my $aktindex = -1;
    my $reltime = 0;
    
    my @columns = split /\t/, $line;
    my @begcols;
    my $mess_time =  substr($columns[0], 0, 7); #Zeitstempel der raw-message
    my $message;
    $message = ($columns[2] =~ /^Scene Image/) ? $columns[2] : substr($columns[2], 11); # get content only

    #    print STDERR "dbg: insert_message: started for $message\n" if ($begset eq 1);
    #run through begbuffer
    foreach $i (@begbuffer) {
      $aktindex++;
      #	print STDERR "dbg: processing $i\n";
      @begcols = split /\t/, $i;
      if ($i =~ /^Fixation|^User Event|(\t){$FIXCOLS}Saccade|(\t){$FIXCOLS+$SACCOLS}Blink/) { #wenn fuer Vgl. relevante Zeile gefunden
	$aktsetnum = defined($begcols[27]) ? $begcols[27] : (defined($begcols[11]) ? $begcols[11] : $begcols[1]);
	$reltime = defined($begcols[28]) ? $begcols[28] : (defined($begcols[12]) ? $begcols[12] : $begcols[2]);;

	#	    print STDERR "dbg1: start: $begstart; messtime= $mess_time; akttime = ($reltime+$begstart)\n" if ($begset eq 1);  

	if ($mess_time > ($reltime+$begstart)) { # solange rawtime <= reltime
	  #		$first_loop = 0;
	  #		print STDERR "dbg: skipping line: $i;\n" if ($begset eq 1);
	  next;			#naechste Zeile
	}
	    
	# gefunden! --> an diesem Index einfuegen:
	#	    if ($mess_time > ($reltime+$begstart))
	#	    {  #wenn message-time > akt-time --> einfuegen
	#		print STDERR "dbg: rawtime bigger: $mess_time > ($reltime+$begstart) on index $aktindex\n" if ($begset eq 1) ;
	#--> ab einschl. aktuellem alle folgenden El. um 1 nach 
	# hinten schieben
	$#begbuffer++;
	for ($j=$#begbuffer-1; $j>=$aktindex; $j--) {
	  #		print STDERR "dbg3: shifting ($j) to ($j+1) : '$begbuffer[$j]' --> '$begbuffer[$j+1]'\n";
	  $begbuffer[$j+1]=$begbuffer[$j];
	}
	# message hier einfuegen:
	$new_time = $mess_time - $begstart;
	$new_time = $new_time >= 0 ? $new_time : 0;
	$newmessage="User Event\t" . $aktsetnum . "\t" . "$new_time" . "\t" . $message . "\n";
	$begbuffer[$aktindex]=$newmessage;
	#		print STDERR "dbg: inserted newmessage ($newmessage) on indexnum $aktindex\n";
	return $aktindex;
	#	    }
      }				#if relevante Zeile
      #	$aktindex++;
      # end foreach bufferline}
    }
    #am Ende angekommen --> hier einfuegen:
    $#begbuffer++;
    $new_time = $mess_time - $begstart;
    $new_time = $new_time >= 0 ? $new_time : 0;
    $newmessage="User Event\t" . $aktsetnum . "\t" . "$new_time" . "\t" . $message . "\n";
    $begbuffer[$#begbuffer]=$newmessage;

    return $#begbuffer;
  }

###########################
#
#main
#
############################

#print STDERR "$#ARGV\n";

showusage() if ( $#ARGV ne 2 );

#print STDERR "0: $ARGV[0]   1:$ARGV[1]";

#Open inputfiles
open(BEGAZE, "< $ARGV[0]")
  or die "beformat.pl: Couldn't open $ARGV[0]!\n";
open(RAW, "< $ARGV[1]")
  or die "beformat.pl: Couldn't open $ARGV[1]!\n";
open(OUT, "> $ARGV[2]")
  or die "beformat.pl: Couldn't open $ARGV[2]!\n";


my $raw_filename = $ARGV[1];
my $out_filename = $ARGV[2];

#skip raw-header:
my $done=0; 
while (! $done) {
  $rawpos=tell RAW;
  $_ = <RAW>;
  #    print;
  if (/^\d+/) {
    $done = 1;
  }
}
seek RAW, $rawpos, 0;		#eine Zeile zurueckspringen

#Jump to first "^Set" in Begaze-file
chomp($line=<BEGAZE>);
while ($line !~ /^Set/ ) {
  print OUT "$line\n";
  chomp($line=<BEGAZE>);
}
$nextbegline = $line;

#write Header
#print OUT "Header for Fixation/Saccade/Blink:\n";
#print OUT "Fix. Event Type\tFix. Set\tFix. Start\tFix. End\tFix. Duration\tFix. Location X\tFix. Location Y\tFix. Dispersion X\tFix. Dispersion Y\tFix. Object hit\tSac. Event Type\tSac. Set\tSac. Start\tSac. End\tSac. Duration\tSac. Start Pos X\tSac. Start Pos Y\tSac. End Pos X\tSac. End Pos Y\tSac. Amplitude\tSac. Peak Speed\tSac. Peak Speed At\tSac. Average Speed\tSac. Peak Accel.\tSac. Peak Decel\tSac. Average Accel\tBlink Event Type\tBlink Set\tBlink Start\tBlink End\tBlink Duration\n";

$done=0;
# ein Schleifendurchlauf = Bearbeitung eines Sets
while (! $done) {
  ######################
  #
  # 1. ein set aus begaze-Datei lesen 
  #    -> keine Modifikationen in beformat1 (weiter vereinfachen?)
  ######################

  # erste Zeile ("Set..") schreiben
  push (@begbuffer, "\n" . $nextbegline . "\n");
  $begset=-1;
  #Read next set in BEGAZE into begbuffer
  chomp($line=<BEGAZE>);
  #    print $line;
  
  # komplettes Set in begbuffer laden (und einige begaze-Zeilen skippen)
  while ($line !~ /^Set/ && defined($line)) {
    #lines to keep unmodified
    if ($line =~ /^Start \[ms\]:\t(\d+)/ ) {
      push (@begbuffer, $line . "\n");
      $begstart=$1;
    }
    if ( $line =~ /^Duration \[ms\]:/) {
      push (@begbuffer, $line . "\n");
    } elsif ($line =~ /^User Event\t(\d+)/) {
      if ($begset eq -1) {
	$begset = $1;
	print "\nProcessing Set # $begset ...\n";
	print "... reformatting BEGAZE-data\n";
      }
      push (@begbuffer, $line . "\n"); #und userevent pushen
    } elsif ($line =~ /^Fixation L\t(\d+)/) {
      if ($begset eq -1) {
	$begset = $1;
	print "\nProcessing Set # $begset ...\n";
	print "... reformatting BEGAZE-data\n";
      }
	    
      push (@begbuffer, $line . "\n"); #und userevent pushen
    } elsif ($line =~ /^Saccade L\t(\d+)/) {
      if ($begset eq -1) {
	$begset = $1;
	print "\nProcessing Set # $begset ...\n";
	print "... reformatting BEGAZE-data\n";
      }
      push (@begbuffer, $line . "\n"); #und userevent pushen
    } elsif ($line =~ /^Blink/) {
      push (@begbuffer, $line . "\n"); #und userevent pushen
    }				#end if Blink
	
				#	if ($line =~ /^Table Header/ || /^$/ || /^Event Type\tSet\tStart\tEnd/)
    else			#Header
      {
	#	    push (@begbuffer, $line . "\n");  #und userevent pushen
      }
	
    #read next line;
    chomp($line=<BEGAZE>);
  } #end while "reading begbuffer"
    
  if (!defined($line))		#Wenn wegen EOF abgebrochen
    {
      $done=1;
    } else			#Zeile "Set ..." schreiben
      {
	$nextbegline=$line;
      }
    
  if ($begset == -1) #Wenn leeres Set (d.h. nur "Set ..." + "Duration..."
    {
      print "\ncheck: begset =-1";
      print OUT @begbuffer;
      #begbuffer leeren
      @begbuffer = undef;
      next;
    }
  #    print @begbuffer; 
  #    ReadLine(0);

  #######################
  #
  #raw-set einlesen und fehlende user-Events einfuegen...
  #
  ###########################################

  print "... comparing BEGAZE data and RAW data\n";

  #Pruefen, ob im richtigen set
  chomp($line = <RAW>);
  @columns = split "\t", $line;

  #hack: workaround ivx-bug (setnr in 1st line == 0) ueber Konstanten zu aktivieren.
  if ($IVX_ZERO_LINE_BUG)
    {
      $columns[2]++ if ($columns[1] =~ /SMP|MSG/);
    }

  while (defined($line) && (($columns[1] =~ /SMP/ && $columns[2] lt $begset) || ($columns[1] =~ /MSG/))) {
    chomp($line = <RAW>);
    @columns = split "\t", $line;
  }
  if ($columns[2] gt $begset)  
    #schon zu weit -> abbrechen
    {
      #	  print "\ncheck\n";
      print STDERR "Achtung: set-Nr. im Rohdatenfile ($columns[2]) > set-Nr. im Begaze-File ($begset)\n";
      $rawdone = 1;	
      next; #oder ganz abbrechen?
    }
  
  $rawdone=0;
  $inside_flag=0;
  my $tcounter = 0;
  while (! $rawdone) {
    ++$tcounter;
    $rawpos=tell RAW;

    #	print STDERR "in line: $line\n";
#    if (! $line =~ /^(\d+)\t([A-Z]{3})/) #no data line --> end
      #diese Pruefung gebraucht?
#      {
#	#	    print STDERR "dbg2\n";
#	$rawdone = 1;
#	next;
#      }
#    @columns = split "\t", $line;

    if (! defined($line)) {
#      print STDERR "Achtung: reached EOF(rawfile) $tcounter\n";
      $rawdone = 1;
      next;
    }

#    print STDERR "check: $line";
    #Wenn bei naechster "Scene Image"-Meldung angekommen.
    if ($inside_flag && $columns[1] =~ /MSG/ && $columns[2] =~ /^Scene/) {
      seek RAW, $rawpos, 0;	#eine Zeile zurueckspringen
      $rawdone = 1;
      next;
    }
	
    if ($columns[1] =~ /MSG/)	#found message
      {
	$inside_flag = 1;
	$message = ($columns[2] =~ /^Scene Image/) ? $columns[2] : substr($columns[2], 11); # get content only
#	print STDERR "dbg: found MSG '$message'\n" if ($begset eq 1);
	if (! &message_contained($message)) {
	  if ($begset eq 10)
	    {
	      print "\n in 10: '$message'\n";
	    }
	  
	  $insret=&insert_message($line);
	  if ($insret) {
	    print "Inserted at index $insret: '$message'\n";
	    #		    print STDERR "dbg: have inserted MSG '$line' --> new buffer:\n";
	    #		    print @begbuffer;
	  } else {
	    die "Error while trying to add '$line' to begbuffer! (%insert_message returned false)\n";
	  }
	}			#end if contained
	else {
	  #		print STDERR "dbg: message '$message' already contained!\n" if ($begset eq 9);
	}			# end else
      }				#end if MSG in $line.

    chomp($line = <RAW>);
    @columns = split "\t", $line;
    
  }				#end while not rawdone
				# fertig --> schreiben
  print OUT @begbuffer;
  
  #begbuffer leeren
  @begbuffer = undef;
}

close(BEGAZE);
close(RAW);
close(OUT);

print "\nComparing number of messages in $raw_filename and $out_filename ... ";
my $msgcount_raw = `cat $raw_filename | grep '# Message' | wc -l`;
my $msgcount_out = `cat $out_filename | grep 'User Event' | wc -l`;

chomp($msgcount_raw);
chomp($msgcount_out);

if ($msgcount_raw ne $msgcount_out)
  {
    printf "\nError: MSG-number in $raw_filename ($msgcount_raw) != MSG-number in $out_filename ($msgcount_out)! Error in script $0";
    exit 1;
  }
print "ok: $msgcount_raw";

exit 0;

#EOF
