#!PERLPATH
use strict;

use Carp;

use File::Basename;

use FindBin;

use lib $FindBin::Bin;

use Getopt::Long;

use Gtk;

use Pod::Usage;

use ColorHandler::DefaultColorHandler;
use ColorHandler::FileColorHandler;

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}={type => 'POS', value => 1, display => 1 };
    $self->{map}=undef;
    $self->{revcomp}=0;
    $self->{hidden}=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 {
    my $self=shift;
    return $self->{anchor};
}

sub set_anchor  {
    my $self=shift;
    my $type=shift;
    my $value=shift;
    my $display=shift;
    $self->{anchor}={ type => $type, value => $value,
		     display => $display};
    
}

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();
    if (!$self->{revcomp}) {
	$self->{revcomp}=1;
	if ($self->{anchor}->{type} eq 'POS' &&
	    $self->{anchor}->{value} == 1) {
	    $self->{anchor}->{value}=$end;
	    $self->{anchor}->{display}=$end;
	}
    } else {
	$self->{revcomp}=0;
	if ($self->{anchor}->{type} eq 'POS' &&
	    $self->{anchor}->{value} == $end) {
	    $self->{anchor}->{value}=1;
	    $self->{anchor}->{display}=1;
	}
    }
}

sub is_reverse_complemented {
    my $self=shift;

    return $self->{revcomp};
}

sub set_hidden {
    my $self=shift;
    my $state=shift;

    $self->{hidden}=$state;
}

sub is_hidden {
    my $self=shift;
    
    return $self->{hidden};
    
}

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 _get_anchor_from_gene {
    my $self=shift;
    my %args={};
    my $key=shift;
    my $value=shift;
    $args{$key}=$value;
    my $genename=shift;

    my $res=1;
    my ($start,$end,$strand)=(-1,-1,-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'})) {
	    my $map=$mapinfo->get_map;
	    ($start,$end,$strand)=$map->get_feature_location('CDS','gene',
							     $genename);
	    if ($start>0) {
		if  (($strand!=-1 && $mapinfo->is_reverse_complemented) ||
		     ($strand==-1 && !$mapinfo->is_reverse_complemented)) {
		    $self->revcom_map('ID'=>$mapinfo->get_map_id);
		}
		$res=$start;
		$res=$end
		    if ($mapinfo->is_reverse_complemented);
	    }
	}
    }
    return $res;
}

sub _add_map {
    my $self=shift;
    my $map=shift;
    my $anchor=shift;
    my $anchortype='POS';
    my $anchorvalue=1;
    my $anchordisplay=1;

    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/\(Flipped\) //;
	    $existingname=~ s/\(Hidden\) //;
	    $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);

    my $map_id=$self->{drawingwindow}->append_map($map);
    $mapinfo->set_map_id($map_id);
    push @{$self->{mapinfos}},$mapinfo;

    if (defined $anchor) {
	$self->set_anchor('ID'=>$map_id,$anchor);
    }

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

    $self->{featurewindow}->build_highlights();


    return $map_id;
}

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

    if (defined $datasource) {
	my $map=new PhysicalMap($datasource);
	$self->_add_map($map);
    }
}

sub revcom_map {
    my $self=shift;
    my %args={};
    my $key=shift;
    my $value=shift;
    $args{$key}=$value;
    
    my $newname=undef;
    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ((defined $args{'NAME'} && $mapinfo->get_name eq $args{'NAME'}) ||
	    (defined $args{'ID'} && $mapinfo->get_map_id eq $args{'ID'})) {
	    $mapinfo->reverse_complement();
	    my $oldname=$mapinfo->get_name();
	    $newname=$oldname;
	    if ($newname !~ /\(Flipped\)\s/) {
		$newname='(Flipped) '.$newname;
	    } else {
		$newname =~ s/\(Flipped\)\s//;
	    }
	    $mapinfo->set_name($newname);
	}
    }   

    $self->{drawingwindow}->update_map_display();
    $self->{featurewindow}->build_highlights();
    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 shift_map_up {
    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}->shift_map_up($mapid);
	} 
    }
}

sub shift_map_down {
    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}->shift_map_down($mapid);
	} 
    }
}

sub hide_show_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;
	    if ($mapinfo->is_hidden()) {
		$self->{drawingwindow}->show_map($mapid);
		$mapinfo->set_hidden(0);
		$mapname=~ s/\(Hidden\) //;
	    } else {
		$self->{drawingwindow}->hide_map($mapid);
		$mapinfo->set_hidden(1);
		$mapname='(Hidden) '.$mapname;
	    }
	    $mapinfo->set_name($mapname);
	} 
    }
    return $mapname;
}


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();
	    if (defined $datasource) {
		$self->_add_map_from_datasource($datasource);
	    }
	}
    }   
}

sub save_map_to_file {
    my $self=shift;

    my $mapname=shift;
    my $filename=shift;

    my $newmapinfos=[];

    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ($mapinfo->get_name eq $mapname) {
	    my $map=$mapinfo->get_map;
	    my $seqobj=$map->get_seqobj();
	    my $seqio=new Bio::SeqIO ( -file => ">$filename",
				       -format => 'GenBank');
	    $seqio->write_seq($seqobj);
	    $seqio->close();
	}
    }

}

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;
    $self->{featurewindow}->build_highlights;
}

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);
	     $self->{featurewindow}->build_highlights();
	 }
     }
}

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;
	    $self->{listwindow}->update_comp_anal_list($mapname);
	    $self->{featurewindow}->build_highlights();
	} 
    }

}

sub get_mapinfo {

    my $self=shift;
    my %args=();
    my $key=shift;

    if (!defined $key) {
	return $self->{mapinfos};
    } 

    my $value=shift;
    $args{uc $key}=$value;
    my $resmapinfo=undef;

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

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'})) {
		my $anchor=$mapinfo->get_anchor();
		$anchorposition=$anchor->{value};
	    }

    }
    return $anchorposition;

}

sub set_anchor {
    my $self=shift;
    my %args={};
    my $key=shift;
    my $value=shift;
    $args{$key}=$value;
    my $anchor=shift;
    
    my $anchortype='POS';
    my $anchorpos=$anchor;
    my $anchordisplay=$anchor;

    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ((defined $args{'NAME'} && $mapinfo->get_name eq $args{'NAME'}) ||
	    (defined $args{'ID'} && $mapinfo->get_map_id == $args{'ID'})) {
	    
	    if ($anchor<=0) {
		$anchortype='GENE';
		$anchorpos=$self->_get_anchor_from_gene($key,$value,$anchor);
	    }
	    $mapinfo->set_anchor($anchortype,$anchorpos,$anchordisplay);
	}
    }
    $self->{drawingwindow}->set_start_position(1);
    $self->{drawingwindow}->update_map_display();
}

sub focus_on_highlight {
    my $self=shift;
    my $mapid=shift;
    my $companalid=shift;
    my $highlight=shift;

    foreach my $mapinfo (@{$self->{mapinfos}}) {
	if ($mapinfo->get_map_id eq $mapid) {
	    my $map=$mapinfo->get_map;
	    my ($hmin,$hmax)=$map->get_companal_highlight_bounds($companalid,
								 $highlight);
	    if ($hmin>0 && $hmax>$hmin) {
		my ($dispfirst,$displast,$bpl)=$self->{drawingwindow}->get_display_parameters;
		my $nlines=($displast-$dispfirst+1)/$bpl;
		my $anchor=$mapinfo->get_anchor()->{value};
		my $mapfirst=$dispfirst+$anchor-1;
		my $delta=$hmin-$mapfirst;
		$dispfirst+=$delta;
		$displast+=$delta;
		$self->{drawingwindow}->set_display_parameters($dispfirst,$displast,$bpl);
	    }
	}    
    }
}

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();

    main Gtk;

}


sub quit {
    my $self=shift;
    
    main_quit Gtk;

    return 1;

}

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 @datasources=();
my @accessnumbers=();
my @gbkfiles=();
my @companalresfiles=();
my @refpos=();
my $colorscheme=undef;

my $help=0;

my @maps=();
my @anchors=();
my @directions=();

my $version=0;

GetOptions('help|?' => \$help,
	   'datasource|d=s' => \@datasources,
	   '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' => \@companalresfiles,
	   'refpos|r=s' => \@refpos,
	   'colorscheme|e=s'=>\$colorscheme,
	   'version|v'=>\$version);
	

if ($version) {
    print "This is mugenv release 20030103.\n";
    exit(0);
}

pod2usage( -exitval => 0,
	   -verbose => 2) if $help;

die "$0 : at least one of --datasource, --accessnumber or --gbkfile must be specified"
    if (@accessnumbers==0 && @gbkfiles==0 && @datasources==0);

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

if (defined $colorscheme) {
    $FeatureWidget::ColorHandler=new ColorHandler::FileColorHandler;
    $FeatureWidget::ColorHandler->load_colors($colorscheme);
} else {
    $FeatureWidget::ColorHandler=new ColorHandler::DefaultColorHandler;
}

init Gtk;

my $manager=new MapManager($width);

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

my $bounds_ok=0;

foreach my $filesource (@gbkfiles) {
    push @datasources,"file:$filesource";
}

foreach my $micadosource (@accessnumbers) {
    push @datasources,"micado:$micadosource";
}



foreach my $datasourcename (@datasources) {

    my $datasource=undef;

    my $protocol='file';
    my $resource=$datasourcename;

    if ($datasourcename =~ /:/) {
	($protocol,$resource)=split ':',$datasourcename;
    } 

    my $direction='fw';
    if ($resource =~ /^!/) {
	$direction='rev';
	$resource =~ s/^!//;
    }
    push @directions, $direction;

    my $protocol_ok=0;
    if ($protocol eq 'micado') {
	$protocol_ok=1;
	eval { require FeatureDataSource::MicadoFeatureDataSource; 
	       FeatureDataSource::MicadoFeatureDataSource::init(); };
	my %args=(accessnumber=>$resource,
		  qualifiers=>1);
	$datasource=new FeatureDataSource::MicadoFeatureDataSource(%args);
    }
    
    if ($protocol eq 'file') {
	$protocol_ok=1;
	eval { require FeatureDataSource::FileFeatureDataSource; };
	$datasource=new FeatureDataSource::FileFeatureDataSource($resource);
    }

    if ($protocol eq 'genbank') {
	$protocol_ok=1;
	eval { require FeatureDataSource::GenBankFeatureDataSource; };
	my %args=(accessnumber=>$resource);
	$datasource=new FeatureDataSource::GenBankFeatureDataSource(%args);
    }

    if ($protocol eq 'embl') {
	$protocol_ok=1;
	eval { require FeatureDataSource::EMBLFeatureDataSource; };
	my %args=(accessnumber=>$resource);
	$datasource=new FeatureDataSource::EMBLFeatureDataSource(%args);
    }


    if ($protocol eq 'xembl') {
	$protocol_ok=1;
	eval { require FeatureDataSource::XEMBLFeatureDataSource; };
	my %args=(accessnumber=>$resource);
	$datasource=new FeatureDataSource::XEMBLFeatureDataSource(%args);
    }

    warn "Unknown protocol : $protocol.\n"
	if (!$protocol_ok);
    
    if (defined $datasource) {

	my $map=new PhysicalMap($datasource);
	
	if (!$bounds_ok) {
	    $first=1
		if ($first<=0);
	    
	    if ($last<=$first) {
		(my $dummy,$last)=$map->get_bounds();
	    }
	    
	    $step=($last-$first)+1
		if ($step<=0);
	    
	    if ($step>($last-$first+1)) {
		$step=$last-$first+1;
	    }
	    
	    $last=$last+$step
		unless (($last-$first+1)%$step==0);
	

	    if ($first<0 && $last<0) {
		($first,$last)=$map->get_bounds();
	    }

	    $bounds_ok=1;
	}
	
	my $anchorpos=shift @refpos;
	push @anchors,$anchorpos;
	push @maps,$map;

    }
}

foreach my $companalresfile (@companalresfiles) {
    my $mapindex=0;
    if ($companalresfile =~ /,(\d+)$/) {
	$mapindex=$1-1;
	$companalresfile =~ s/,\d+$//;
    }
    
    $maps[$mapindex]->load_companal_results($companalresfile)
	if (defined $maps[$mapindex]);
}

foreach my $map (@maps) {
    my $id=$manager->_add_map($map,shift @anchors);
    my $direction=shift @directions;
    $manager->revcom_map('ID'=>$id)
	if ($direction eq 'rev');
}


$manager->run();
__END__

=head1 NAME

MuGeN

=head1 SYNOPSIS


The Multi-Genome Navigator, or MuGeN, is a bioinformatics software package providing tools for exploring multiple annotated genomes along with in silico analysis results. It offers two distinct programs, one for interactive vizualization and navigation and another for the generation of images in various formats. Both programs can load annotated sequence data from a local file or retrieve it from databases across the network. Most of the parameters governing the way annotations and analysis results are displayed are customizable, either through the the graphical user interface or with command-line parameters. The following sections show how to install and to use MuGeN before describing how to format home-made analysis results in order to integrate them in MuGeN.


=head1 USAGE

B<Options common to mugenb and mugenv>

=over
=item B<-d source:id>

Specifies a resource from which to load annotated genome maps. Each resource consists of two parts, a source and an id. The source can be one of file, genbank, embl, xembl or micado. When no source is specified, file is taken as default. The id points to the specific map in the source. When the latter is a file, the id is simply the filename (in GenBank, EMBL, BSML or fasta format). When the source is a database (genbank, embl, xembl, micado) the id is the access number of the database entry. Maps will be displayed from top to bottom in the order they are entered on the command line. If the id start with a "!" the map will be flipped.

=item B<-f firstbase>

Specifies the starting point of the image to build. In the absence of any reference points, this is the first base of the map that will be located in the upper left corner of the image. If a reference point is given, the upper left corner will be the reference point offset by the amount specified by this option.

=item B<-l lastbase>

Specifies the ending point of the image to build. In the absence of any reference points, this is the last base of the map that will be located in the upper lower right corner of the image. If a reference point is given, the lower right corner will be the reference point offset by the amount specified by this option.

=item B<-s step>

Specifies the number of bases per display line.

=item B<-r refpos>

Specifies a reference position or anchor for a genome map. If the reference position is an integer, the start of the displayed image will be computed by adding the value of the -f option to the integer. If the reference position is a string, MuGeN will look for a CDS feature having a gene qualifier whose value equals the given string. If such a CDS is found, it's start base will be used to compute the start of de displayed image as explained above. Moreover, if the gene is on the reverse strand, the map will be flipped. The genome map for which the reference position is defined is determined by the index of the -r option wrt. the -d option (i.e. the first -r option will be applied to the map defined by the first -doption, the second -r applies to the second -d and so on).

=item B<-c filename[,index]>

Specifies a computational analysis results file to display with a genome map. If a comma and an index are appended to the filename, the result will be applied to the genome map of the corresponding index. Index 1 is the genome map loaded by the first -d option, index 2 the map corresponding to the second -d and so on.

=item B<-e filename>

Specifes a file containing a color scheme to apply to displayed features.

=item B<-w n>

Specifes the width in pixels of the drawing area

=item B<-p filename>

Specifes the preferences file to load. If no -p option is given, the preferenes file will be set to ${HOME}/.mugenrc.


=back


B<Options specific to mugenb>

=over
=item B<-o format>

Specifies the output format of the image file to be generated. Valid formats are : PNG, IMAP, PS, EPS, XFIG.

=item B<-m mediatype>

Specifies the media type, for PS or EPS output files. Valid types are : a7, a6, a5, a4, a3, a2, a1, a0, b7, b6, b7, b4, b3, b2, b1, b0, lettern legal, executive, ledger.

=item B<-u urlprefix>

Specifies the root URL for client-side image maps in IMAP format. Parameters relative to dislayed features will be appended to this root URL. For instance, given a root URL of http://www.somewhere.org/cgi-bin/myscript.pl?myid=xyz&, and an image containing a CDS feature, whose name is abcX positioned from base 1234 to base 5678, the URL generated for it's clickable area will be http://www.somewhere.org/cgi-bin/myscript.pl?myid=xyz&tag=CDS&name=abcX&start=1234&end=5678.


=back



=head1 CONTACT

Mark Hoebeke (mhoebeke@jouy.inra.fr)
