#! /usr/bin/perl -w

use strict;

########################################################################################
##########################PROGRAMA D'ALINIAMENT DE SEQUENCIES###########################
########################################################################################


# definim variables

my $seq1;
my $seq2;
my @seq1_v;
my @seq2_v;
my $i;
my $j;
my $m;
my $n;
my %msubs;
my $matriu_substitucio;
my @amin_v;
my $amin;
my @punt_parcials;
my @alinea;
my @ali1;
my @ali2;
my @ali3;
my @ali4;
my @rali1;
my @rali2;
my @rali3;
my $x = 0;
my $puntuacio = 0;





#si la crida del programa no te 4 arguments, imprimim el missatge i tanquem el programa 

if (scalar(@ARGV) < 4) {
  print "ali2.pl seq1.txt seq2.txt mat.txt penalitzacio-gap\n";
  exit(1);
}

my $penalitzacio_salt = <$ARGV[3]>;


#obrim els fitxers
 
open(FITXER1, "< $ARGV[0]" );
open(FITXER2, "< $ARGV[1]" );

#si algun dels dos fitxers no es pot obrir, imprimim el missatge i tanquem el programa

if (!open(FITXER1,"< $ARGV[0]")) {
  print "ali2.pl: impossible obrir seq1.txt\n";
  exit(1);
}

if (!open(FITXER2,"< $ARGV[1]")) {
  print "ali2.pl: impossible obrir seq2.txt\n";
  exit(1);
}

#########################################################################################
#######################LLEGIM LES SEQUENCIES EN FORMAT FASTA#############################
#########################################################################################

#llegim la primera linia de cada sequencia i la guardem a la variable $primeralinia

my $primeralinia1 = <FITXER1>;
my $primeralinia2 = <FITXER2>;

#canviem el marcador de registre '\n' per '>' perque puguem llegir la sequencia d'un sol cop 
#i l'emmagatzemem a la variable $seq

$/ = ">";

$seq1 = <FITXER1>;
$seq2 = <FITXER2>;


#tanquem els fitxers:

close(FITXER1);
close(FITXER2);

#cridem la funcio llegir_sequencia. Explicacio al final del programa 

@seq1_v = llegir_sequencia ($seq1);
@seq2_v = llegir_sequencia ($seq2);

########################################################################################
################################HASH DE HASHOS##########################################
########################################################################################


#obrim el fitxer de la matriu i si no es pot obrir imprimim el missatge d'error
#i tanquem el programa



open (MATRIU1, "<$ARGV[2]"); 

if (!open(MATRIU1,"< $ARGV[2]")) {
  print "ali2.pl: impossible obrir mat.txt\n";
  exit(1);
}

#tornem a canviar el marcador de registre perque ens llegeixi la matriu fila per fila

$/ = "\n";

#guardem la primera linia de la matriu (simbols) a la variable $amin

$amin = <MATRIU1>;

#cridem la funcio llegir_matriu_substitucio. Explicacio al final del programa

%msubs = llegir_matriu_substitucio($amin);



##########################################################################################
###############################MATRIU D'ALINIAMENT########################################
##########################################################################################

#aquesta matriu consta de dues matrius: una, la @punt_parcials, que emmagatzema els valors 
#de les puntuacions, i l'altra, la @alinea, on enregistrem la procedencia de l'alineament 
#parcial `optim amb tres etiquetes:'D' (diagonal), 'U' (dalt) i 'L' (esquerra)


$m = scalar(@seq1_v);
$n = scalar(@seq2_v);

#fem un 'unshift' a les sequencies per desplac,ar una posicio a la dreta tots
# els simbols per poder inserir un simbol de gap '-' a la posicio 0 

unshift(@seq1_v,"-");
unshift(@seq2_v,"-");

#enregistrem la matriu al fitxer matali.txt

open (MATALI, ">matali.txt");

#mostrem els simbols del vector de les columnes (@seq2_v)

print MATALI "  ";
$i = 0;
while ($i < $n+1) {

  print MATALI "$seq2_v[$i]\t";
  $i = $i + 1;
}
print MATALI "\n";


#mostrem el primer simbol del vector de les files (@seq1_v), que
#es un gap '-'

print MATALI "$seq1_v[0] ";

#a la posicio 0,0 la puntuacio sera 0

$punt_parcials[0][0]=0;
$alinea[0][0] = "final";
print MATALI "$punt_parcials[0][0]\t";

#per omplir la primera fila hem d'utilitzar obligatoriament el valor de l'esquerra ($alinea[0][$i] = 'L')
#i la puntuacio en cada posicio s'obte de la suma de la puntuacio anterior i el valor de la 
#penalitzacio_salt ($ARGV[3])

$i = 1;
while ($i < $n+1) {
  $punt_parcials[0][$i]=$punt_parcials[0][$i-1] + $penalitzacio_salt;
  $alinea[0][$i] = 'L';
  print MATALI "$punt_parcials[0][$i]$alinea[0][$i]\t";
  $i = $i + 1;
}
print MATALI "\n\n";
  

#ara, a partir de la segona fila i segona columna, anem construint
#totes les puntuacions parcials recorrent totes les files i per
#cada fila totes les columnes

$i = 1;
while ($i < $m+1) {

  #mostrem el simbol de la fila

  print MATALI "$seq1_v[$i] ";

  #per omplir la segona columna hem d'utilitzar obligatoriament el valor de dalt ($alinea[$i][0] = 'U')
  #i la puntuacio en cada posicio s'obte de la suma de la puntuacio anterior i el valor de la 
  #penalitzacio_salt


  $punt_parcials[$i][0] = $punt_parcials[$i-1][0] + $penalitzacio_salt;
  $alinea[$i][0] = 'U';
  print MATALI "$punt_parcials[$i][0]$alinea[$i][0]\t";

  $j = 1;
  while ($j < $n+1) {

    #per omplir la resta de la matriu hi ha 3 opcions de puntuacio possibles: alinear dos simbols
    #($puntuacio_diagonal), alinear un simbol de les files (@seq1_v) amb un gap ($puntuacio_dalt) 
    #i alinear un simbol de les columnes (@seq2_v) amb un gap ($puntuacio_esquerra). Ens quedarem
    #amb l'aliniament parcial que tingui la maxima puntuacio

    my $puntuacio_diagonal = $punt_parcials[$i-1][$j-1] + $msubs{$seq1_v[$i]}{$seq2_v[$j]};    
    my $puntuacio_dalt     = $punt_parcials[$i-1][$j] + $penalitzacio_salt;
    my $puntuacio_esquerra = $punt_parcials[$i][$j-1] + $penalitzacio_salt;

    if ($puntuacio_diagonal >= $puntuacio_dalt &&
        $puntuacio_diagonal >= $puntuacio_esquerra) {

      $punt_parcials[$i][$j] = $puntuacio_diagonal;
      $alinea[$i][$j] = 'D';

    }
    else {

      if ($puntuacio_dalt >= $puntuacio_diagonal &&
          $puntuacio_dalt >= $puntuacio_esquerra) {

        $punt_parcials[$i][$j] = $puntuacio_dalt;
        $alinea[$i][$j] = 'U';

      }
      else { 

        $punt_parcials[$i][$j] = $puntuacio_esquerra;
        $alinea[$i][$j] = 'L';

      }

    }

    #imprimim a cada posicio de la matriu el valor de la puntuacio parcial i d'on prove
    #aquest valor

    print MATALI "$punt_parcials[$i][$j]$alinea[$i][$j]\t";

    $j = $j + 1;

  }

  $i = $i + 1;

  print MATALI "\n\n";
}

#tanquem el fitxer

close (MATALI);



############################################################################################
###################################ALINIAMENT OPTIM#########################################
############################################################################################

#un cop tenim la matriu d'aliniament, obtindrem l'aliniament optim comenc,ant
# per l'ultima fila i columna, i seguint les anotacions de 'D' (diagonal)
# 'U' (dalt) i 'L' (esquerra) que tenim a la matriu @alinea.
#Fem servir 3 vectors (@ali1,@ali2 i @ali3): @ali1 contindra els valors de la seq1, @ali2
#contindra els valors de la seq2 i @ali3 contindra els simbols '*','.' o ' '. Posarem un '*'
#si els 2 aa son iguals, un '.' si el valor corresponent de la matriu d'identitats es >= 0, i 
#' ' si aquest valor es < 0 o aliniem l'aa amb un gap.
#Paral.lelament anem calculant la puntuacio de l'aliniament optim pas per pas ($puntuacio)

#declarem variables

$i = $m;
$j = $n;





while ($i > 0 || $j > 0) {

  #si la puntuacio maxima venia de la diagonal alineem el simbol
  #$i de seq1_v amb el simbol $j de seq2_v i posem '*', '.' o ' ' 
  #segons correspongui

  if ($alinea[$i][$j] eq 'D') {

    $ali1[$x] = $seq1_v[$i];
    $ali2[$x] = $seq2_v[$j];
    if ($seq1_v[$i] eq $seq2_v[$j]) {
	$ali3[$x] = "*";}
    else {
	if ($seq1_v[$i] ne $seq2_v[$j] && $msubs{$seq1_v[$i]}{$seq2_v[$j]} >= 0) {   
	    $ali3[$x] = ":";}
	if ($seq1_v[$i] ne $seq2_v[$j] && $msubs{$seq1_v[$i]}{$seq2_v[$j]} < 0 ) {   
	    $ali3[$x] = " ";}
    }
    #en aquest cas el valor de la puntuacio sera el valor anterior mes el valor corresponent a
    #la matriu d'identitats

    $puntuacio = $puntuacio + $msubs{$seq1_v[$i]}{$seq2_v[$j]};                     
    $i = $i - 1;
    $j = $j - 1;
    $x = $x + 1;

  }
  else {

    # si la puntuacio maxima venia de dalt, alineem el simbol
    # $i de seq1_v amb un gap i per tant el valor de $ali3[$i] sera ' '

    if ($alinea[$i][$j] eq 'U') {

      $ali1[$x] = $seq1_v[$i];
      $ali2[$x] = "-";
      $ali3[$x] = " ";
      #aqui, el valor de la puntuacio sera la suma del seu valor anterior mes el de la 
      #penalitzacio per gap

      $puntuacio = $puntuacio + $penalitzacio_salt;
      $i = $i - 1;
      $x = $x + 1;

    }

    else {

      # si la puntuacio maxima venia de l'esquerra, alineem un
      # gap amb el simbol $j de seq2_v. El valor de $ali3[$i] sera tambe ' '

      $ali1[$x] = "-";
      $ali2[$x] = $seq2_v[$j];
      $ali3[$x] = " ";
      #aqui, el valor de la puntuacio sera la suma del seu valor anterior mes el de la 
      #penalitzacio per gap

      $puntuacio = $puntuacio + $penalitzacio_salt;
      $j = $j - 1;
      $x = $x + 1;

    }

  }

}

#fem un reverse dels 3 vectors per tenir l'aliniament en el mateix ordre que el de les 
#sequencies inicials

@rali1 = reverse(@ali1);
@rali3 = reverse(@ali3);
@rali2 = reverse(@ali2);

#imprimim l'aliniament de 60 en 60 aa com el format fasta. Per fer-ho creem una matriu (@ali4) de
#tres files on la primera sera el vector rali1, la segona el vector rali3 i la tercera el vector rali2. 
#El numero de columnes sera la llargada de l'aliniament. 

my $volta = 1;
$j = 0;
$i = 0;
$x = 0;
my $l = 0;

#enviarem l'aliniament al fitxer resultat.txt

open (RESULTAT, ">resultat.txt");
print RESULTAT "\n\n$primeralinia1\n$primeralinia2\n\n";

while ($i < scalar(@rali1)) {
    $x = $i;
    $j = 0;
    $l = $l + 1;
   
    #mostrem la posicio del primer aa de la fila

    print RESULTAT "$l\t\t";
	
    
    
    while ($j < 3) {
	$i = $x;
	if ($j > 0) {
	       print RESULTAT "\t\t"; 
	     
	}

        #anem mostrant cada element de la matriu separats en blocs de 60 columnes  
	
	while ($i < 60*$volta && $i < scalar(@rali1)) {
	
	    if ($j == 0) {
		$ali4[$j][$i] = $rali1[$i];
	    }
	    if ($j == 1) {
		
		$ali4[$j][$i] = $rali3[$i];
	    }
	    if ($j == 2) {
		
		$ali4[$j][$i] = $rali2[$i];
	    }
	    print RESULTAT $ali4[$j][$i];
	   
	    $i = $i + 1;
	}

	#imprimim la posicio l'ultim aa del bloc
	if ($j == 0) {
	    
	   
	    if ($i < (scalar(@rali1)-1)) {
		$l = $l + 59;
	        print RESULTAT "\t\t$l";
	    }
	    else {
		$l = scalar(@rali1);
		print RESULTAT "\t\t$l";
	    }
	}


	print RESULTAT "\n";
	$j = $j + 1;
	
       
    }
    print RESULTAT "\n\n";
    $volta = $volta + 1;
}


print RESULTAT "\n";

#mostrem la puntuacio total de l'aliniament



print RESULTAT "score: $puntuacio\n\n";



print "\n\nEl resultat de l'aliniament el trobaras al fitxer: resultat.txt\n\n";

#tanquem el fitxer

close (RESULTAT);



##########################################################################################
################################FUNCIONS##################################################
##########################################################################################

#FUNCIO: llegir_sequencia

#OBJECTIU: transformar la sequencia en format fasta en un vector on a cada posicio hi hagi
#un aminoacid. 
#En primer lloc, transferim el contingut de la variable $seq a un vector @v de forma que cada 
#element del vector sera una fila de la sequencia. En segon lloc, ajuntem tots els elements 
#del vector @v en una nova variable $seq_v i transferim el contingut de $seq_v al vector 
#@seq_v, on cada posicio sera un aminoacid de la sequencia

#PARAMETRE: la sequencia en format fasta ($seq)

#RETURN: retornem el vector on cada posicio es un aa

sub llegir_sequencia {
   
   my $x=$_[0];
   my @v = split(/\n/,$x);
   my $seq_v = "";
   $i = 0;
   while ($i < scalar(@v)) {
        $seq_v = $seq_v . $v[$i];
        $i = $i + 1;
      } 

   my @seq_v = split (//, $seq_v);
   return @seq_v;
}


#FUNCIO: llegir_matriu_substitucio

#OBJECTIU: construim un hash a partir de la matriu de substitucio.
#En primer lloc, agafem la primera linia de la matriu i la passem a un vector
#(@amin_v) on cada posicio hi haura un aa. 
#En segon lloc, construim el hash:
#anem recorrent totes les columnes per cada fila.
#guardem cada fila al vector @v
#assignem el valor de la matriu ($v[$i]) a la parella d'aminoacids corresponents ($v[0] i $amin_v[$i-1]) 
 
#PARAMETRE: la primera linia de la matriu ($amin)

#RETURN: el hash (%msubs)

sub llegir_matriu_substitucio {
    
    $amin = $_[0];
    chomp($amin);
    @amin_v = split(/ +/,$amin);
    shift(@amin_v);
    my $i = 0;
    
    while (<MATRIU1>) {

      chomp;
      my @v = split(/ +/);
      my $j = 0;
      while ($j < scalar(@amin_v)) {
         
	 $msubs{$amin_v[$i]}{$amin_v[$j]} = $v[$j+1];
         $j = $j + 1;
	    
	}
       $i = $i + 1;
	 
      
    }
    return %msubs;

   
}
