sub appendLog() {
     my $logfile = shift(@_);
     my $logstring = shift(@_);
     open(LOG, ">>$logfile");
     print LOG $logstring;
     close(LOG);
}

sub sampleL {
     my @states = @{$_[0]};
     my @pL = @{$_[1]};
     my $sum = sum(@pL);
     foreach my $a (@pL) {  # normalize
	  $a/= $sum;
     }	
     my $r = rand();

     foreach my $s (@states) {
	 if($r <= sum(@pL[0..$s])) {return($s)}  # returning will cause release from loop
     }
}

sub printResults() {
     my $loglike = $_[0];
     my $M = $_[1];
     my @states = @{$_[2]};
     my @current_params = @{$_[3]};
     my @proba = @{$_[4]};
     my %files = %{$_[5]};
     my $n_event_states = $#states; # the last index of @states gives the number of event states 
     # print all final parameter values to file #
     open(FINAL_PARAMS, ">" . $files{"paramsfile"});
     print FINAL_PARAMS join("\t", (join("\t", map{"alpha" . $_} @states), "p_stay_normal", "p_stay_event"));
     print FINAL_PARAMS "\n" . join("\t", @current_params) . "\n";	
     close(FINAL_PARAMS);

     my $probdata_h1 = $loglike;
     my $probdata_h0 = -1 * $M * log(2);
     
     open(PP, ">" . $files{"postprobsfile"});
     print PP join("\t", map{ "S" . $_ } @states) . "\n"; # print header
     foreach my $i (0..$#{$proba[0]}) {
	  my @probsthisline = map { sprintf("%.10f", $proba[$_][$i]) } @states; # grab postprobs at this interval for all states
	  print PP (join("\t", @probsthisline) . "\n");
     }
     close(PP);
}

sub fillForward() {
     my $M = shift(@_);
     my @states = @{$_[0]};
     my @sta = @{$_[1]};
     my @pi = @{$_[2]};
     my @alpha = @{$_[3]};
     my @y = @{$_[4]};
     my @phi=(); # forward probabilities
     foreach my $a (@states) { 
	  $phi[$a][0] =  $sta[$a]; 
     }	
     
     foreach my $m (1..($M)){
	  foreach my $a (@states){ 
	       $phi[$a][$m] = 0; 
	  }
     }	
     
     my $lognorm = 0;
     foreach my $m (1..($M)){
	  my $norm = 0;
	  foreach my $b (@states){
	       my $sum = 0;
	       foreach my $a (@states){ 
		    $sum += $phi[$a][$m-1] * $pi[$a][$b]; 
	       }	
	       my $pdat = $alpha[$b]*($y[$m] == 1) + (1 - $alpha[$b])*($y[$m] == 0); 
	       $phi[$b][$m] = $sum * $pdat;
	       $norm += abs($phi[$b][$m]);
	  }
	  $lognorm += log($norm);
	 
	  foreach my $b (@states){
	       $phi[$b][$m] /= $norm; 
	  }	  
     }
     return($lognorm, @phi);
}

sub calcForwardProbs() {
     my($loglike, @phi) = &fillForward(@_);
     return(@phi);
}

sub calcLogLikelihood() {
     my($loglike, @phi) = &fillForward(@_);
     return($loglike);
}

sub calcBackwardProbs() {
     my $M = shift(@_);
     my @states = @{$_[0]};
     my @pi = @{$_[1]};
     my @alpha = @{$_[2]};
          my @y = @{$_[3]};
     my @beta=();  # backward probabilities
     
     foreach my $m (0..($M+1)){ # initialize, since we're starting from the end
	  foreach my $a (@states){
	       $beta[$a][$m] = 0;
	  }	
     }
     
     foreach my $a (@states){
	  $beta[$a][$M+1] = 1.;
     }			
     
     for (my $m = $M ; $m > 0 ; $m--){
	  my $norm = 0;
	foreach my $b (@states){
	     my $sum = 0;
		foreach my $a (@states){
			my $pdat;
			if ($m == $M){ 
			     $pdat = 1.; 
			}
			else {
				$pdat = $alpha[$b]*($y[$m+1] == 1) + (1 - $alpha[$b])*($y[$m+1] == 0);
			}
			$sum += $pdat * $beta[$a][$m+1] * $pi[$b][$a];
		}
		$beta[$b][$m] = $sum;
	     $norm += $beta[$b][$m];
	}
	  
	foreach my $b (@states){
		$beta[$b][$m] /= $norm;
	}
     }
     return(@beta);
}

sub multiplyForwardBackward() {
     my $M = shift(@_);
     my @states = @{$_[0]};
     my @phi = @{$_[1]};
     my @beta = @{$_[2]};
     
my @proba;
     foreach my $a (@states) {
	  foreach my $m (0..($M-1)) {
	       $proba[$a][$m] = 0;
	  }
     }

     foreach my $m (1..($M)) {
	  my $sum = 0;
	  my @p = ();
	  foreach my $a (@states) {
	       $p[$a] = $phi[$a][$m] * $beta[$a][$m];
	       $sum += $p[$a];
	  }

          if ($sum == 0) {
              print STDERR "$m: Zero sum of phi";
          }
	  foreach my $a (@states) {
	       $p[$a] /= $sum;
	       $proba[$a][($m-1)] = $p[$a];
	  }
     }
     return(@proba);
}

sub updateAlpha() {
     my $M = shift(@_);
     my @states = @{$_[0]};
     my @alpha = @{$_[1]};
     my @proba = @{$_[2]};
     my @y = @{$_[3]};
     foreach my $a (@states) {  # estimate emission probabilities for all states, including normal states
	  my $phat_est_sum = 0;  # initialize
	  foreach my $m (0..($M-1)) {
	       $phat_est_sum += ($proba[$a][$m] * $y[$m+1] );
	  }
          
	  my $phat_est = sum(@{$proba[$a]}) ? $phat_est_sum / sum(@{$proba[$a]}) : 0;
	  $alpha[$a] = $phat_est; 
     }
     return(@alpha);
}

sub updateTransitions() {
     my $M = shift(@_);
     my $gamma = shift(@_);
     my @states = @{$_[0]};
     my @phi = @{$_[1]};
     my @pi = @{$_[2]};
     my @c = @{$_[3]};
     my @p_stay;

     #sample L at each marker #
     my (@stoL, @pL);
     foreach my $m (0..$M-1) {	# initialize array
	  $stoL[$m] = "NA";
     }
     
     foreach my $a (@states) {
	  $pL[$a] = $phi[$a][$M-1];
     }
     
     $stoL[$M-1] = &sampleL(([@states], [@pL]));
     
     for (my $m = $M-2; $m >= 0; $m--) {  # sample L at each marker from second-to-last to first
	  foreach my $a (@states) {
	       $pL[$a] = $phi[$a][$m] * $pi[$a][$stoL[$m+1]]; # forward probability * transition probability
	  }
	  
	  $stoL[$m] = &sampleL(([@states], [@pL]));	       
     }
     
     # estimate tpm using sEM #
     my @lengths = ([()],[()]);
     my $m = 0; 
     my $runninglength = 1;
     my $l; # binary indicator for LOH
     while($m != $M-1) {  # continue if we're not at end of chain
	  $m++;
	  if ($stoL[$m] == $stoL[$m-1]) {  # still in the same state, increment length
	       $runninglength++;
	  }
	  else {	# have changed states, store length and reset length to 1
	       $l = 1*($stoL[$m-1] > 0);
	       push(@{$lengths[$l]}, $runninglength);
	       $runninglength = 1; 
	  }
     }
     $l = 1*($stoL[$m] > 0); # this is the LOH status indicator at the last marker
     push(@{$lengths[$l]}, $runninglength);
     
     foreach my $s (0,1) {  
	  my $n_transitions_out = 0;
	  my $n_possible_transitions = 0;
	  my $n_events = ($#{$lengths[$s]}+1);
	  if ($n_events != 0) {
	       if ($l != $s) {
		    $n_transitions_out = $n_events;
		    $n_possible_transitions = sum(@{$lengths[$s]});
	       }
	       if ($l == $s) {
		    $n_transitions_out = $n_events - 1;
		    $n_possible_transitions = ( sum(@{$lengths[$s]}) - 1 );
	       }
	  }
	            
	  $p_stay[$s] = (( ($n_possible_transitions - $n_transitions_out) + $c[$s] ) / 
			 ( $n_possible_transitions + $gamma) );   # including pseudocounts
     }
     return(@p_stay);
}

sub runEM() {
     my $estimateTPM = shift(@_);
     my $gamma = shift(@_);
     my $M = shift(@_);
     my @states = @{$_[0]};
     my @curr_params = @{$_[1]};
     my @y = @{$_[2]};
     my @c = @{$_[3]};
     my @alpha = splice(@curr_params, 0, $#states+1); # first $states elements are alpha values
     my @pstays = @curr_params;
     
     # assign parameter values for this iteration #
     my @sta = ();  # initial distribution
     $sta[0] = (1/(1-$pstays[0])) / ( (1/(1-$pstays[0])) + (1/(1-$pstays[1])) );
     foreach my $s (1..$#states) {
	  $sta[$s] = (1 - $sta[0]) / $#states;
     }
     
     my @pi = (); # transition probabilities
     $pi[0][0] = $pstays[0];
     foreach my $s (1..$#states) {
	  $pi[$s][$s] = $pstays[1];
	  $pi[0][$s] = (1 - $pi[0][0]) / $#states;
	  $pi[$s][0] = 1 - $pi[$s][$s];
	  foreach my $s2 (1..$#states) {
	       if($s != $s2) { $pi[$s][$s2] = 0 }  # no transitions between event states
	  }
     }
     
     my ($loglike, @phi, @beta, @proba);
     @phi = &calcForwardProbs(($M, [@states], [@sta], [@pi], [@alpha], [@y]));
     @beta = &calcBackwardProbs(($M, [@states], [@pi],[@alpha], [@y] ));
     @proba = &multiplyForwardBackward(($M, [@states], [@phi], [@beta]));
     @alpha = &updateAlpha(($M, [@states], [@alpha], [@proba], [@y]));
     
     if($estimateTPM) {
	  @pstays = &updateTransitions(($M, $gamma, [@states], [@phi], [@pi], [@c]));
     }

     $loglike = &calcLogLikelihood(($M, [@states], [@sta], [@pi], [@alpha], [@y]));
     
     return(([@alpha, @pstays], [@proba], $loglike));
}

sub calc_beginning_log_likelihood_and_margprobs() {
     my $M = shift(@_);
     my @states = @{$_[0]};
     my @curr_params = @{$_[1]};
     my @y = @{$_[2]};
     my @alpha = splice(@curr_params, 0, $#states+1); # first $states elements are alpha values
     my @pstays = @curr_params;
     
     # assign parameter values for this iteration #
     my @sta = ();  # initial distribution
     $sta[0] = (1/(1-$pstays[0])) / ( (1/(1-$pstays[0])) + (1/(1-$pstays[1])) );
     foreach my $s (1..$#states) {
	  $sta[$s] = (1 - $sta[0]) / $#states;
     }
     
     my @pi = (); # transition probabilities
     $pi[0][0] = $pstays[0];
     foreach my $s (1..$#states) {
	  $pi[$s][$s] = $pstays[1];
	  $pi[0][$s] = (1 - $pi[0][0]) / $#states;
	  $pi[$s][0] = 1 - $pi[$s][$s];
	  foreach my $s2 (1..$#states) {
	       if($s != $s2) { $pi[$s][$s2] = 0 }  # no transitions between event states
	  }
     }

     my ($loglike, @phi, @beta, @proba);
     @phi = &calcForwardProbs(($M, [@states], [@sta], [@pi], [@alpha], [@y]));
     @beta = &calcBackwardProbs(($M, [@states], [@pi],[@alpha], [@y] ));
     @proba = &multiplyForwardBackward(($M, [@states], [@phi], [@beta]));
          
     $loglike = &calcLogLikelihood(($M, [@states], [@sta], [@pi], [@alpha], [@y]));
     return($loglike, @proba);
}

1;
