#!/usr/bin/perl -w
use strict;

package      bnf079_sub1;
require      Exporter;

our @ISA       = qw(Exporter);  

# Symbols to be exported by default
our @EXPORT    = qw(adja2links
		    components
		    degrees
		    dist
		    find_FF_loops
		    get_sub_network
		    my_component
		    print_sif_file_links
		    prune_links_to_symmetric
		    randomize_directed
		    randomize_symmetric
		    read_sif_file
		    symmetrize
		    );
 
# Symbols to be exported on request
our @EXPORT_OK = qw();

# Version number
our $VERSION   = 1.00;      




####################
####################
####################

sub adja2links{

    my $adja_hash_ref = shift or die('died in symmetrize, as no adjacency hash reference was specified'); 

    my @links;

    while ((my $nodeF,my $row_ref) = each %{$adja_hash_ref}){
	next if !defined $row_ref; 
	while ((my $nodeT,my $int_ref)= each %{$row_ref}){
	    push @links, {'nodeF' => $nodeF,'nodeT' => $nodeT};
	};
    };
    return \@links;
};
	    

sub components {
# It is assumed the adjacency matrix hash is symmetric.
#Uses sub my_component;
#Returns a reference to a hash and a reference to an array.
#The hash elements are pointers to the array elements.
#The array elements are lists of nodes belonging to the same 
#component. The elements are sorted by size.
#The largest component is in element 0 and so forth.
#The hash keys are all the nodes and its values are references to
#the component the node belongs to. Note all nodes in a component 
#has the same value.
#So if you change the array array elements the component listing 
#changes for all nodes assigned to it.
    my @components;
    my %node2component;

    my $adja_sym_hash_ref = shift or die('died in components, as no adjacency hash reference was specified');    


    foreach my $nodeF (keys %{$adja_sym_hash_ref}){
	#Go through each node as a seed for a component;
	next if defined $node2component{$nodeF};
	unshift @components, my_component($adja_sym_hash_ref,$nodeF);
	foreach my $c_node (@{$components[0]}){
	    $node2component{$c_node}=$components[0];
	};
    };
    @components =sort { scalar @{$b} <=> scalar @{$a} } @components;
#    print "$#components\n";
    return (\%node2component,\@components);
};

sub degrees {
    my %degree;

    my $links_ref = shift or die('died in degree, as no link array reference was specified');    
    my $node_key = shift or die('died in degree, as only one argument was specified');
    foreach my $link (@{$links_ref}){
	next if !exists($link->{$node_key});
	$degree{$link->{$node_key}}++;
    };
    return \%degree;
};

sub dist {

    my @dist;

    my $degree_hash_ref = shift or die('died in dist, as no degree hash reference was specified');    

    foreach my $degree (values %{$degree_hash_ref}){
	$dist[$degree]++;
    };
    return \@dist;
};

sub find_FF_loops{
#NOT FINISHED
    
    my $num_FF=0;
    my %FF_loops;

    my $adja_hash_ref = shift or die('died in find_FF_loops, as no adjacency hash reference was specified');    

    my $adja_ref= {%{$adja_hash_ref}};  #Make a copy.

    foreach my $topnode (keys %{$adja_ref}){
	if (!defined $adja_ref->{$topnode}) {
	    delete $adja_ref->{$topnode};
	    next;
	};
	delete $adja_ref->{$topnode}{$topnode}; #Remove diagonal;
    };
    
#    {my @degree_dist = @{dist(degrees(adja2links($adja_ref),'nodeF'))};
#    foreach my $degree (0..$#degree_dist){
#	if (defined $degree_dist[$degree]) {
#	    print STDOUT "$degree_dist[$degree] nodes have degree $degree\n";
#	};
#    };};

    
    foreach my $topnode (keys %{$adja_ref}){
	#Any node might be the top node;
	#Make a hash of my distance 2 neighbours
	#print "Top: $topnode\n";
	
	my %neighbours2;
	foreach my $neig (keys %{$adja_ref->{$topnode}}){
	    next if !exists($adja_ref->{$neig});
	    foreach my $neigh2 (keys %{$adja_ref->{$neig}}){
		$neighbours2{$neigh2}++;
	    };
	};
	delete $neighbours2{$topnode}; #Not necessary
	#{my @tmp= keys %{$adja_ref->{$topnode}};
	 #print "n1: @tmp \n";};
	 #{my @tmp= keys %neighbours2;
	 #print "n2: @tmp \n";};
	 
    
	foreach my $neig (keys %{$adja_ref->{$topnode}}){
	    next if !exists($neighbours2{$neig});
	    $num_FF=$num_FF+$neighbours2{$neig};
	    $FF_loops{$topnode}{$neig}=$neighbours2{$neig};
	#    print "$topnode , $neig , $FF_loops{$topnode}{$neig}\n";
	};
    };
    return $num_FF;
    #return ($num_FF,\%FF_loops);
};



sub get_sub_network{
        
    my $sub_adja_hash_ref;

    my $adja_hash_ref = shift or die('died in get_sub_network, as no adjacency hash reference was specified');    
    my $node_list_ref = shift or die('died in get_sub_network, as no node list was specified');    
    
    my %sub_node_hash;
    foreach my $n (@{$node_list_ref}){
	$sub_node_hash{$n}=undef;
	$sub_adja_hash_ref->{$n}=undef; #Make sure all nodes are included.
    };
    foreach my $s_node (@{$node_list_ref}){
	if (!exists($adja_hash_ref->{$s_node})|| 
	           !defined $adja_hash_ref->{$s_node}){
	    next;
	};
	foreach my $n_t (keys %{$adja_hash_ref->{$s_node}}){
	    #next if !exists($adja_hash_ref->{$s_node}{$n_t});
	    next if !exists($sub_node_hash{$n_t});
	    $sub_adja_hash_ref->{$s_node}{$n_t}= 
	          {%{$adja_hash_ref->{$s_node}{$n_t}}}; #We make a copy!!
	};
    };
    return (adja2links($sub_adja_hash_ref),$sub_adja_hash_ref)
};





sub my_component {
#my_component($sym_adja_hash_ref,@node_list);
#
# It is assumed the adjacency matrix hash is symmetric.
# For a list of nodes it then gives all the nodes (reference to an array)
# nodes is connected to.

    my %component;
    my %node2component;
    my @neighbor;

    my $sym_adja_hash_ref = shift or die('died in my_components, as no symmetric adjacency hash reference was specified');    
    @neighbor=@_;
    $#neighbor>=0 or die('died in my_components, as no node as second argument was specified was specified');    

    while (my $node=shift @neighbor){
	next if exists($component{$node});
	$component{$node}=undef;
#	print "$node\n";
	push @neighbor, keys %{$sym_adja_hash_ref->{$node}}
    };
    return [ keys %component ];

       
};

sub print_sif_file_links{
    
    my $links_ref = shift or die('died in print_sif_file, as no link reference was specified. ');    
    my $sif_file = shift or die('died in print_sif_file, as no file name (second argument) was specified. ');    
    

    open(SIFFILE_OUT,">$sif_file") or die "Fail to open $sif_file in print_sif_file_links\n"; 
    foreach my $link (@{$links_ref}){
	print SIFFILE_OUT "$link->{'nodeF'} ".
	    "(link) $link->{'nodeT'}\n";
    };
    #close(SIFFILE_OUT);
};

sub prune_links_to_symmetric{
##Remove links that appear twice between two nodes like
##a b
##b a
##Becomes just a b;

    my @pruned_links;

    my $links_ref=shift or die 'Died in prune_links_to_symmetric as no argument was supplied (link_reference)';

    my %links;

    foreach my $link (@{$links_ref}){
	next if exists($links{$link->{'nodeF'}}{$link->{'nodeT'}});
	next if exists($links{$link->{'nodeT'}}{$link->{'nodeF'}});
	$links{$link->{'nodeF'}}{$link->{'nodeT'}}=undef;
	push @pruned_links,{%{$link}};
    };
    return \@pruned_links;
};


sub randomize_directed{

    my %ran_adja;
    my %loops;

    my @in_num2node;
    my @out_num2node;


    my $adja_hash_ref = shift or die('died in randomize_directed, as no adjacency hash reference was specified');    

    my $num_ite = shift  or die('died in randomize_directed, as number of iterations was not specified');    


    foreach my $nodeF (keys %{$adja_hash_ref}){
	next if !defined $adja_hash_ref->{$nodeF};
	foreach my $nodeT (keys %{$adja_hash_ref->{$nodeF}}){
	    if ($nodeT eq $nodeF){
		$loops{$nodeF}={%{$adja_hash_ref->{$nodeF}{$nodeT}}};
		#print "$nodeF = $nodeT\n";
		next;
	    };
	    $ran_adja{$nodeF}{$nodeT} = {%{$adja_hash_ref->{$nodeF}{$nodeT}}}; #Supposed to be a copy;
	    push @in_num2node, $nodeT;
	    push @out_num2node, $nodeF;
	};
    };

    my $num_links=scalar @in_num2node;


    my $ite =0;
    my $node1F;
    my $node1T;
    my $node2F;
    my $node2T;

    while ($ite<$num_ite){
 	
	my @nT;

	#print "Iteration number $ite\n";

	#Pick Link 1
	$node1F=$out_num2node[int(rand($num_links))];
	@nT= keys %{$ran_adja{$node1F}};
 	$node1T=$nT[int(rand(scalar @nT))];
	#print "Link1 $node1F to $node1T\n";
	die if !exists( $ran_adja{$node1F}{$node1T} );
	
	#Pick Link 2
 	$node2F=$out_num2node[int(rand($num_links))];
	@nT= keys %{$ran_adja{$node2F}};
 	$node2T=$nT[int(rand(scalar @nT))];
	#print "Link2 $node2F to $node2T\n";
	die if !exists( $ran_adja{$node2F}{$node2T} );
	
	#Check for double link
	redo if exists($ran_adja{$node1F}{$node2T});
	redo if exists($ran_adja{$node2F}{$node1T});

	#Check for loop
	redo if ($node1T eq $node2F);
	redo if ($node2T eq $node1F);

	#Exchange links
	$ran_adja{$node1F}{$node2T}=delete $ran_adja{$node2F}{$node2T};
	$ran_adja{$node2F}{$node1T}=delete $ran_adja{$node1F}{$node1T};

       	$ite++;
    };

    while ((my $l_n, my $l_l)= each %loops){
	$ran_adja{$l_n}{$l_n}=$l_l;
    };

    return \%ran_adja;

}


sub randomize_symmetric{

    my %ran_sym_adja;
    my %loops;

    my @in_num2node;
    my @out_num2node;


    my $sym_adja_hash_ref = shift or die('died in randomize_directed, as no adjacency hash reference was specified');    

    my $num_ite = shift  or die('died in randomize_directed, as number of iterations was not specified');    


    foreach my $nodeF (keys %{$sym_adja_hash_ref}){
	next if !defined $sym_adja_hash_ref->{$nodeF};
	foreach my $nodeT (keys %{$sym_adja_hash_ref->{$nodeF}}){
	    if ($nodeT eq $nodeF){
		$loops{$nodeF}={%{$sym_adja_hash_ref->{$nodeF}{$nodeT}}};
		#print "$nodeF = $nodeT\n";
		next;
	    };
	    $ran_sym_adja{$nodeF}{$nodeT} = {%{$sym_adja_hash_ref->{$nodeF}{$nodeT}}}; #Supposed to be a copy;
	    push @in_num2node, $nodeT;
	    push @out_num2node, $nodeF;
	};
    };

    my $num_links=scalar @in_num2node;


    my $ite =0;
    my $node1F;
    my $node1T;
    my $node2F;
    my $node2T;

    while ($ite<$num_ite){
 	
	my @nT;

#	print "Iteration number $ite\n";

	#Pick Link 1
	$node1F=$out_num2node[int(rand($num_links))];
	@nT= keys %{$ran_sym_adja{$node1F}};
 	$node1T=$nT[int(rand(scalar @nT))];
	#print "Link1 $node1F to $node1T\n";
	die if !exists( $ran_sym_adja{$node1F}{$node1T} );
	
	#Pick Link 2
 	$node2F=$out_num2node[int(rand($num_links))];
	@nT= keys %{$ran_sym_adja{$node2F}};
 	$node2T=$nT[int(rand(scalar @nT))];
	#print "Link2 $node2F to $node2T\n";
	die if !exists( $ran_sym_adja{$node2F}{$node2T} );
	
	#Check for double link
	redo if exists($ran_sym_adja{$node1F}{$node2T});
	redo if exists($ran_sym_adja{$node2F}{$node1T});

	#Check for loop
	redo if ($node1T eq $node2F);
	redo if ($node2T eq $node1F);

#	print "Old Link1 $node1F to $node1T\n";
#	print "Old Link2 $node2F to $node2T\n";
	#Exchange links
	$ran_sym_adja{$node1F}{$node2T}=delete $ran_sym_adja{$node2F}{$node2T};
	$ran_sym_adja{$node2T}{$node1F}=delete $ran_sym_adja{$node2T}{$node2F};
	$ran_sym_adja{$node2F}{$node1T}=delete $ran_sym_adja{$node1F}{$node1T};
	$ran_sym_adja{$node1T}{$node2F}=delete $ran_sym_adja{$node1T}{$node1F};

       	$ite++;
    };

    while ((my $l_n, my $l_l)= each %loops){
	$ran_sym_adja{$l_n}{$l_n}=$l_l;
    };

    return \%ran_sym_adja;

}





sub read_sif_file{
#Called with a file name of a sif file.
#Returns an array with 2 elements
#The first element is a reference to an array of hashes.
#Each element in the array corresponds to a unique link.
#The link is specified by the hash, which has two keys
#'nodeF' and 'nodeT'
#The link goes from the value of nodeF to the value of nodeT.
#Links mentioned multiple times in the file are only included once.
#The second element in the return array is a reference to
#an hash of hashes of hashes corresponding roughly to the 
#adjacency matrix.
#The keys in the top-level hash is all the 'nodeF' values in the 
#above link array. For each 'nodeF' value all the nodes the 
#'nodeF' node links to are the keys in the next level.
#In the last hash level the keys are the different 
#interaction types and the values are how many time
#a link with that interaction type appears in the file.

    my @links;
    my %links;


    my %adjacency;


    my $sif_file= shift or die('died in read_sif_file, because no sif filename was specified'); 


    open(SIF_FILE,"<$sif_file") or die "Fail to open $sif_file in read_sif_file\n"; ;

    foreach my $link (<SIF_FILE>){
	chomp($link);
	my %fields;
	my $type;
	my @words=split(/\s+/,$link);
	$#words+1>=3 || 
	    die "could not find node, link, and node in line ".($#links+2).
	    " of sif file\n".
	    "The line is '$link'\n";
	($fields{'nodeF'},$type,$fields{'nodeT'})=@words[0..2];
#	print "$fields{'nodeF'}\n";
	if (!(exists($adjacency{$fields{'nodeF'}}) && 
	      exists($adjacency{$fields{'nodeF'}}{$fields{'nodeT'}}))){
	    push @links, {%fields};
	};
	$adjacency{$fields{'nodeF'}}{$fields{'nodeT'}}{$type}++;
    };
#    close(SIF_FILE);
    return (\@links,\%adjacency);
};

sub symmetrize{

    my $adja_hash_ref = shift or die('died in symmetrize, as no adjacency hash reference was specified'); 

    my %sym_adja;

    while ((my $nodeF,my $row_ref) = each %{$adja_hash_ref}){
	while ((my $nodeT,my $int_ref)= each %{$row_ref}){
	    while ((my $int,my $lines)= each %{$int_ref}){
		if (!exists($sym_adja{$nodeF}{$nodeT}{$int})){
		    $sym_adja{$nodeF}{$nodeT}{$int}=0;
		    $sym_adja{$nodeT}{$nodeF}{$int}=0;
		};
		$sym_adja{$nodeF}{$nodeT}{$int}= 
		    $sym_adja{$nodeF}{$nodeT}{$int}+
		    $adja_hash_ref->{$nodeF}{$nodeT}{$int};
		$sym_adja{$nodeT}{$nodeF}{$int}= 
		    $sym_adja{$nodeF}{$nodeT}{$int};
	    };
	};
    };   
    return \%sym_adja;
};

