#!/usr/bin/perl
use strict;

use Carp;

use File::Basename;

use FindBin;

use lib $FindBin::Bin;

use Getopt::Long;

use Gtk;

use Pod::Usage;

use PreferencesHandler;

use PhysicalMap;

use ExternalViewerLinker;

#
# Package holding info about the currently displayed maps.
#
package MapInfo;

use strict;

sub new {
    my $class=shift;

    my $self={};
    $self->{name}='Unknown';
    $self->{anchor}=1;
    $self->{datasource}=undef;
    $self->{map}=undef;
    $self->{revcomp}=0;
    bless $self,$class;

    return $self;
}

sub get_name {
    my $self=shift;
    return $self->{name};
}

sub set_name {
    my $self=shift;
    $self->{name}=shift;
}

sub get_datasource {
    my $self=shift;
    return $self->{datasource};
}

sub set_datasource {
    my $self=shift;
    $self->{datasource}=shift;
}

sub get_anchor_position {
    my $self=shift;
    return $self->{anchor};
}

sub set_anchor_position {
    my $self=shift;
    $self->{anchor}=shift;
}


sub get_map_id {
    my $self=shift;
    return $self->{map_id};
}

sub set_map_id {
    my $self=shift;
    my $map_id=shift;
    $self->{map_id}=$map_id;
}

sub get_map {
    my $self=shift;

    return $self->{map}
}

sub set_map {
    my $self=shift;
    $self->{map}=shift;
    $self->{revcomp}=0;
}

sub reverse_complement {
    my $self=shift;
    my ($start,$end)=$self->{map}->get_bounds();
    $self->{anchor}=$end-$self->{anchor}+1;
    if (!$self->{revcomp}) {
	$self->{revcomp}=1;
    } else {
	$self->{revcomp}=0;
    }
}

sub is_reverse_complemented {
    my $self=shift;

    return $self->{revcomp};
}

package MapManager;

use strict;

use Carp;

use Gui::FeatureWindow;
use Gui::MapDrawingWindow;

sub new {

   my $class=shift;
   my ($width,$height)=@_;

   my $self={};

   $self->{mapinfos}=[];
   $self->{drawingwindow}=new Gui::MapDrawingWindow($self,$width,$height);
   $self->{featurewindow}=new Gui::FeatureWindow($self);
   $self->{drawingwindow}->add_listener($self->{featurewindow},'ENTER');
   $self->{externalviewerlinker}=new ExternalViewerLinker;
   $self->{drawingwindow}->add_listener($self->{externalviewerlinker},'SELECT');
   $self->{listwindow}=new Gui::MapListWindow($self);
   bless $self,$class;

   return $self;
}

sub _add_map {
    my $self=shift;
    my $map=shift;
    my $anchorpos=shift;
    $anchorpos=1
	if (!defined $anchorpos || $anchorpos<=0);

    my $datasource=$map->get_datasource;
    my $basename=$map->get_organism;
    my $chromosome=$map->get_chromosome;
    $basename.=" - Chr. $chromosome"
	if (defined $chromosome);
    my $nameok=0;
    my $mapname=$basename;
    while (!$nameok) {
	$nameok=1;
	foreach my $mapinfo (@{$self->{mapinfos}}) {
	    my $existingname=$mapinfo->get_name;
	    $existingname=~ s/(Swicthed) //;
	    $nameok=0
		if ($mapname eq $existingname);
	}
	if (!$nameok) {
	    my $mapcounter=2;
	    if ($mapname =~ /\s\[(\d+)\]$/) {
		$mapcounter=$1+1;
	    }
	    $mapname=$basename." [$mapcounter]";
	}
    }
	
    my $mapinfo=new MapInfo;
    $mapinfo->set_name($mapname);
    $mapinfo->set_datasource($datasource);
    $mapinfo->set_map($map);
    $mapinfo->set_anchor_position($anchorpos);

    $self->{listwindow}->add_map($mapinfo);
    my $map_id=$self->{drawingwindow}->append_map($map);

    $mapinfo->set_map_id($map_id);
    push @{$self->{mapinfos}},$mapinfo;

    $self->{listwindow}->update_comp_anal_list($mapname);
}

sub _add_map_from_datasource {
    my $self=shift;
    my $datasource=shift;

    my $map=new PhysicalMap($datasource);

    $self->_add_map($map);
}

sub revcom_map {
    my $self=shift;
    my $mapname=shift;
    
    my $newname=$mapname;

    
    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ($mapinfo->get_name eq $mapname) {
	    my $id=$mapinfo->get_map_id();
	    $mapinfo->reverse_complement();
	    if ($newname !~ /^\(Flipped\)\s/) {
		$newname='(Flipped) '.$newname;
	    } else {
		$newname =~ s/\(Flipped\)\s//;
	    }
	    $mapinfo->set_name($newname);
	}
    }   

    $self->{drawingwindow}->update_map_display();
    return $newname;
}

sub is_reverse_complemented {
    my $self=shift;
    my $mapid=shift;

    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ($mapinfo->get_map_id() eq $mapid) {
	    return $mapinfo->is_reverse_complemented();
	}
    }   
}

sub duplicate_map {
    my $self=shift;
    my $mapname=shift;
    
    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ($mapinfo->get_name eq $mapname) {
	    my $datasource=$mapinfo->get_datasource();
	    $self->_add_map_from_datasource($datasource);
	}
    }   
}

sub remove_map {
    my $self=shift;

    my $mapname=shift;

    my $newmapinfos=[];

    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ($mapinfo->get_name eq $mapname) {
	    my $mapid=$mapinfo->get_map_id;
	    $self->{drawingwindow}->remove_map($mapid);
	} else {
	    push @{$newmapinfos},$mapinfo;
	}
    }

    $self->{mapinfos}=$newmapinfos;

}

sub add_companal_from_file {
    my $self=shift;
    my $mapname=shift;
    my $file=shift;

     foreach my $mapinfo (@{$self->{mapinfos}}) {
	 if ($mapinfo->get_name eq $mapname) {
	     my $map=$mapinfo->get_map;
	     $map->load_companal_results($file);
	 }
     }
    $self->{drawingwindow}->render;
    $self->{listwindow}->update_comp_anal_list($mapname);
}

sub get_companal_names {
    my $self=shift;
    my $mapname=shift;

    my @res=();

    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ($mapinfo->get_name eq $mapname) {
	    my $map=$mapinfo->get_map;
	    @res=$map->get_companal_result_display_ids;
	}
    }

    return @res;
}


sub remove_companal_result {
    my $self=shift;
    my $mapname=shift;
    my $resultid=shift;

    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ($mapinfo->get_name eq $mapname) {
	    my $map=$mapinfo->get_map;
	    $map->remove_companal_result_with_display_id($resultid);
	} 
    }
    $self->{drawingwindow}->render;
}

sub get_anchor_position {
    my $self=shift;
    my %args=();
    my $key=uc shift;
    my $value=shift;
    $args{$key}=$value;

    my $mapname=shift;
    my $anchorposition=1;

    foreach my $mapinfo (@{$self->{mapinfos}}) {
	    if ((defined $args{'NAME'} && 
		 $mapinfo->get_name eq $args{'NAME'}) ||
		(defined $args{'ID'} &&
		 $mapinfo->get_map_id == $args{'ID'})) {
		$anchorposition=$mapinfo->get_anchor_position;
	    }

    }
    return $anchorposition;

}

sub set_anchor_position {
    my $self=shift;
    my %args={};
    my $key=shift;
    my $value=shift;
    $args{$key}=$value;
        my $newanchor=shift;

    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ((defined $args{'NAME'} && $mapinfo->get_name eq $args{'NAME'}) ||
	    (defined $args{'ID'} && $mapinfo->get_id == $args{'ID'})) {
	    $mapinfo->set_anchor_position($newanchor);
	}
    }
    $self->{drawingwindow}->update_map_display();
}

sub set_gene_focus {
    my $self=shift;
    my %args={};
    my $key=shift;
    my $value=shift;
    $args{$key}=$value;
        my $genename=shift;

    my ($start,$end)=(-1,-1);
    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ((defined $args{'NAME'} && $mapinfo->get_name eq $args{'NAME'}) ||
	    (defined $args{'ID'} && $mapinfo->get_id == $args{'ID'})) {
	    my $map=$mapinfo->get_map;
	    ($start,$end)=$map->get_feature_location('gene',$genename);
	    if ($start>0) {
		$mapinfo->set_anchor_position($start);
		$self->{drawingwindow}->set_start_position(1);
	    }
	}
    }
    return $start;
}

sub get_map_drawing_window  {
    my $self=shift;
    return $self->{drawingwindow};
}

sub run {
    my $self=shift;
    
    $self->{drawingwindow}->show_all();
    $self->{featurewindow}->show_all();
    $self->{listwindow}->show_all();
  Gtk::main($self->{listwindow});
}


package main;

use strict;


use FeatureDataSource::FileFeatureDataSource;

use PreferencesHandler;

# Default range parameters for map display.

my $help= 0;
my $prefsfile="$ENV{HOME}/.mugenrc";
my $first=1;
my $last=20000;
my $step=5000;
my $width=800;
my @accessnumbers=();
my @gbkfiles=();
my @companalfiles=();
my @refpos=();

GetOptions('help|?' => \$help,
	   'accessnumber|a=s',\@accessnumbers,
	   'gbkfile|g=s',\@gbkfiles,
	   'first|f=i' => \$first,
	   'last|l=i' => \$last,
	   'step|s=i' => \$step,
	   'width|w=i' => \$width,
	   'prefsfile|p=s' => \$prefsfile,
	   'companalresultfile|c=s' => \@companalfiles,
	   'refpos|r=i' => \@refpos);

pod2usage(1) if $help;

die "$0 : only one of --accessnumber or --gkbfile must be specified"
    if (@accessnumbers>0 && @gbkfiles>0);

Gtk::init('dummy');

PreferencesHandler::set_preferences_file($prefsfile);
PreferencesHandler::load_preferences();

my $manager=new MapManager($width);

my $drawingwindow=$manager->get_map_drawing_window();
$drawingwindow->set_display_parameters($first,$last,$step);

my $map=undef;

do {
    $map=undef;

    my $gbkfile=shift @gbkfiles;

    $map=new PhysicalMap(new FeatureDataSource::FileFeatureDataSource($gbkfile,'GenBank'))
    if (defined $gbkfile);


    my $accessnumber=shift @accessnumbers;
    if (defined $accessnumber) {
      FeatureDataSource::MicadoFeatureDataSource::init();
	$map=new PhysicalMap(new FeatureDataSource::MicadoFeatureDataSource(accessnumber=>$accessnumber,qualifiers=>1));
    }

    my $companalfile=shift @companalfiles;
    if (defined $companalfile && defined $map) {
	$map->load_companal_results($companalfile);
    }

    
    my $anchorpos=shift @refpos;
    $manager->_add_map($map,$anchorpos)
	if (defined $map);

} while (defined $map);


$manager->get_map_drawing_window()->update_map_display();

$manager->run();

__END__

=head1 NAME

MuGeN - Multi-Genome Navigator


=head1 DESCRIPTION

MuGeN is a for exploring multiple annotated genomes
simultaneously.  It provides two commands: B<mugenv> an interactive
navigation tool and B<mugenb> a batch mode multi-genome map generator.
Extensive documentation for MuGen is available at :

http://www-mig.versailles.inra.fr/bdsi/MuGeN

=head1 SYNOPSIS

mugenv [options]

mugenb [options] mapfile

=head1 USAGE

B<mugenv> and B<mugenb> share a large set of options. B<mugenb> provides some extra options to control map generation and needs an additionnal argument, B<mapfile>, specifying the filename of the resulting image.

=over

=item Options common to B<mugenv> and B<mugenb>:

=over

=item B<--help or -h or -?>

Display help message.

=item B<--gbkfile or -g filename>

Display the annotated sequence stored in F<filename> (GenBank format).

=item B<--accessnumber or -a an>

Display the annotated sequence having access number B<an> stored in the Micado database (the preferences file has to be configured with a Micado username/password to use this feature).

=item B<--companalresfile or -c filename>

Defines a computational analysis results file to be drawn on the map.

=item B<--first or -f n>

Map display will start at base position B<n> (default value 1)

=item B<--last or -l m>

Map display will end at base position B<m> (default value 20000).

=item B<--step or -s k>

Map display will have B<k> bases per display line (default value 5000).

=item B<--width or -w p>

Width of map drawing area (in pixels). This option works for B<mugenv> and for B<mugenb> with 'PNG' or 'IMAP' output formats.

=item B<--prefsfile or -p filename>

Load preferences from F<filename> (default is F<$HOME/.mugenrc>).

=back

=item B<--refpos or -r n>

Anchor positions for loaded maps. When loading multiple maps, their
relative positioning is obtained through the repeated use of the B<-r>
option.

=back

=item B<mugenb> specific options:

=over

=item B<-o> or B<--outputformat>

Graphics format for output file. One of 'PNG', 'IMAP', 'PS', 'EPS' or 'XFIG'.

=item B<-u> or B<--urlprefix>

URL prefix used for image maps. Image maps are client side maps where each feature determines a clickable area. The URL pointed by each feature is composed of the URL prefix followed by 3 additionnal parameters : the primary tag of the feature, as value of the tag parameter, the start and end positions of the feature (in base positions) as values of the start and end parameters. Ex, a 'CDS' feature positioned between bases 100 and 200 will give rise to the following map area :
    
    <area shape="rect" coords="<image coordinates>" 
    qhref="<urlprefix>tag=CDS&start=100&end=200">

=item B<-m> or B<--media>

Media specification for XFig and PostScript or encapsulated PostScript maps ('XFIF', 'PS' and 'EPS' output formats). Values can be a4, a3, a2, a1 and a0.

=back

=back

=head1 EXAMPLES

=over

=item Viewing a map stored in the GenBank formatted file F<myfile.map.gbk>.

C<mugenv -g map.gbk>

=item Viewing a map stored in the Micado database with accession number AB021978.

C<mugenv -a AB021978>

=item Viewing a portion of 20kb starting at position 100001 on 4 lines of 5kb each.

C<mugenv -g myfile.gbk -f 100001 -l 120000 -s 5000>

=item Viewing a map using the display preferences stored in the F<myprefs.xml> file

C<mugenv -g myfile.gbk -p myprefs.xml>

=item Generating a PNG image of the GenBank entry contained in file F<myfile.gbk>

C<mugenb -g myfile.gbk -o PNG myfile.png>

=item Generating an image map image of the GenBank entry contained in file F<myfile.gbk>, the HTML description of the map components will be stored in F<myfile.html>, and the image map will be found in F<myfile.map>.

C<mugenb -g myfile.gbk -o IMAP -u http://localhost/Maps/map.html? myfile.map E<gt> myfile.html>

=item Generating an encapsulated PostScript file of the complete genome stored at accession number CG0031, 100 Kbp per line and a3-sized. Output will be stored in file F<myfile.eps>.

C<mugenb -a CG0031 -o EPS -s 100000 -m a3 myfile.eps>


=item Generating an XFig file of two maps. The second map will be
positioned 2Mb downstream of the first map.

C<mugenb -a CG0031 -a CG0057 -r 1 -r 2000000 -o XFIG multimap.fig>

=back

=head1 CONTACT

Comments, contributions, bug-reports can be sent to:

hoebeke@versailles.inra.fr

=cut
