package Scaffold;

use strict;
require Exporter;

use GenomicsUtils;
use DBI;
use Gene;

use vars qw(@ISA @EXPORT);
 
 
@ISA = qw(Exporter);	
@EXPORT = qw( fetchGenesList fetchGenesListRef fetchGenesMap fetchGenesMapRef );


sub new;
# Creates scaffold object.
# Parameters:
#   scaffoldId => ID, preload => [FEATURES]
#---------------------------------------------------------
# Private variables
#
#     $self->{scaffoldId_} 
#     $self->{chrNumber_} 
#     $self->{taxonomy_} 
#     $self->{length_} 
#     $self->{file_} 
#     $self->{sequence_} 
#     $self->{genes_} - list of genes in order
#     $self->{genesMap_} - hash of genes keyed on locusId
#

#---------------------------------------------------------
#The following functions just retrieve basic info, and should
#be self-explanatory
sub comment();
sub chrNumber(); # Returns number of the chromosome
sub taxonomyId();
sub taxonomyName();
sub length(); # Returns length of the scaffold sequence in bp
sub isCircular();
sub isPartial();
sub isGenomic();
sub file(); # Returns file name where this scaffold is located
sub sequenceRef();# Returns a reference to genomic DNA sequence
sub sequence();
#---------------------------------------------------------

sub genesRef();
sub genes();
#Returns (reference to) a list of all genes in this scaffold

#---------------------------------------------------------
sub fetchGenesList;
sub fetchGenesListRef;
#Returns (reference to) a list of gene objects sorted by start
#position.
#fetchGenesListRef is the engine behind all of the gene-creating
#functions in this section.  All the preload options are handled here,
#and all of the SQL queries to load gene objects are handled by this
#function. Relies on Gene::new() to actually create gene objects.
#This is a seriously long and comlpicated function - avoid it at all
#costs.

sub fetchGenesMap;
sub fetchGenesMapRef;
#Returns (reference to) a map of gene objects keyed on locusId.  Uses
#fetchGenesListRef to collect gene objects.

#---------------------------------------------------------

sub genes();
sub genesRef();
#These functions return a list of genes found on this scaffold.
#makeGenesList is used to create the list, and relies on
#fetchGenesListRef to actually make the gene objects.

sub genesMap();
sub genesMapRef();
#These functions return the same genes as the other functions in this
#section, but in the form of a hash indexed by locusId.

sub makeGenesList;
#Creates the internal list of gene objects for the scaffold -
#$self->{genes_}.  Used by the preceding functions.

#---------------------------------------------------------

sub toString;
# Returns string describing the scaffold. 

sub getScaffoldList;
sub getScaffoldMap;
#Returns a list or hash of all scaffold objects


my %scaffolds;
my %defaultPreload;

sub new{
    my %params = @_;
    my $scaffoldId = $params{'scaffoldId'};
    my @preload;
    @preload = @{$params{'preload'}} if defined $params{'preload'};

    my $existing =  $scaffolds{$scaffoldId};
    if( defined($existing) ) {
#fix this logic to load only when preload is defined for synonym
	$existing->preloadSynonym() if ( @preload && !defined( $existing->{preloadSynonym_} ) );
	return $existing;
    }
    my $self = {};
    bless $self;
    my ($chrNumber, $length, $file,
	$taxonomyId, $taxonomyName, $taxShortName, $isCircular,
	$isPartial, $isGenomic,
	$comment ) =  GenomicsUtils::dbh()->selectrow_array( qq/ SELECT chr_num, length, file, s.taxonomyId, t.name, t.shortName,
							     isCircular, isPartial, isGenomic, comment
							     FROM Scaffold s, Taxonomy t WHERE scaffoldId='$scaffoldId' AND s.taxonomyId=t.taxonomyId / );

    $self->{scaffoldId_} = $scaffoldId;
    $self->{chrNumber_} = $chrNumber;
    $self->{taxonomyId_} = $taxonomyId;
    $self->{taxonomyName_} = $taxonomyName;
    $self->{taxonomyShortName_} = $taxShortName;
    $self->{length_} = $length;
    $self->{file_} = $file;
    $self->{isCircular_} = $isCircular;
    $self->{isPartial_} = $isPartial;
    $self->{isGenomic_} = $isGenomic;
    $self->{comment_} = $comment;
    $self->{preloadSynonym_}=0;

    $scaffolds{$scaffoldId} = $self;

    $self->makeGenesList( preload => \@preload ) if( @preload );
    return $self;
}

sub comment()
{
    my $self = shift;
    return $self->{comment_};
}

sub scaffoldId(){
    my $self = shift;
    return $self->{scaffoldId_};
}

sub chrNumber() {
    my $self = shift;
    return $self->{chrNumber_};
}

sub taxonomyId(){
    my $self = shift;
    return $self->{taxonomyId_};
}
sub taxonomyName(){
    my $self = shift;
    return $self->{taxonomyName_};
}
sub taxonomyShort(){
    my $self = shift;
    return $self->{taxonomyShortName_};
}
sub length(){
    my $self = shift;
    return $self->{length_};
}

sub file() {
    my $self = shift;
    return $self->{file_};
}

sub isCircular(){
    my $self = shift;
    return $self->{isCircular_};
}

sub isPartial(){
    my $self = shift;
    return $self->{isPartial_};
}

# Compute the distance from the $pos1 to $pos2, corrected for circularity
# can return a negative value (never goes more than half-way round the chromosome if it's circular)
sub distance($ $) {
    my $self = shift;
    my $pos1 = shift;
    my $pos2 = shift;
    return ($pos2-$pos1)
	if ($self->isPartial() || !$self->isCircular() || abs($pos2-$pos1) < $self->length()/2);
    return $self->length()+$pos2-$pos1 if $pos2 < $pos1;
    return -$self->length() + $pos2-$pos1;
}

sub isGenomic(){
    my $self = shift;
    return $self->{isGenomic_};
}

sub sequence(){
    my $self = shift;
    return ${$self->sequenceRef()};
}

sub sequenceRef(){
    my $self = shift;
    if ( !defined( $self->{sequence_} ) ) {
	my $scaffoldId = $self->{scaffoldId_}; 
	$self->{sequence_} =
	    \&query( qq/SELECT sequence FROM ScaffoldSeq
		     WHERE scaffoldId=$scaffoldId/ )->[0][0];
    } 
    return $self->{sequence_}; 
}

sub genes(){
    return @{&genesRef(@_)};
}

sub genesRef(){
    my $self = shift;
    
    if(! defined $self->{genes_}) {
	$self->makeGenesList(@_);
    }
    return $self->{genes_};
}

sub genesMap(){
    my $self = shift;
    return %{$self->genesMapRef()};
}

sub genesMapRef(){
    my $self = shift;

    if(! defined $self->{genesMap_}){
	$self->{genesMap_} = {map {$_->locusId(),$_} @{$self->genesRef()}};
    }
    return $self->{genesMap_};
}

sub makeGenesList{
    my $self = shift;

    if( !defined($self->{genes_}) ) {
	my $genes = fetchGenesListRef( scaffoldId =>
				       [$self->{scaffoldId_}], @_);
	my $i = 0;
	$self->{genes_} = [ sort { $a->begin <=> $b->begin() }
			    map { # $_->setScaffold( $self ); # not needed b/c scaffold is preloaded
				  $_->setIndex( $i++ ); $_  } @$genes];
    }
}

sub fetchGenesMap{
    return %{&fetchGenesMapRef(@_)};
}

sub fetchGenesMapRef {
    my %params = @_;
    my $self = $params{self};
    my %genesMap;

    my $genes = &fetchGenesListRef(@_);
    foreach my $gene (@$genes){
 	$genesMap{$gene->locusId()}=$gene;
    }
    return \%genesMap;
}

sub fetchGenesList{
    return @{&fetchGenesListRef(@_)};
}

sub fetchGenesListRef{
    my %params = @_;
    my @query = ();

    if (defined($params{taxonomyId})) {
	my $tax = $params{taxonomyId};
	my $restriction = join " , ", @$tax;
	$restriction = " s.taxonomyId in (" . $restriction  . ") ";
	push @query, $restriction;
    }
    if ( defined($params{scaffoldId}) ) {
	my $scaffolds = $params{scaffoldId};
	my $restriction = join " , ", @$scaffolds;
	$restriction = " s.scaffoldId in (" . $restriction  . ") ";
	push @query, $restriction;
    }
    if (defined($params{chrNumber})){
	push @query, qq{s.chr_num="$params{chrNumber}"};
    }
    if (defined($params{begin})) {
	push @query, qq{p.start > $params{begin}};
    }
    if (defined($params{end})) {
	push @query, qq{p.stop < $params{end}};
    }
    if (defined($params{locusId})) {
	my $locuss = $params{locusId};
	my $nOrfs = scalar( @$locuss ); # number of elements in the list
	if( defined($params{version}) ) {
	    my $versions = $params{version};
	    die "Number of locuss is not equal to number of versions\n" if scalar(@$versions)!=$nOrfs;
	    my @conditions;
	    for(my $i=0; $i < $nOrfs; ++$i) {
		push @conditions, qq/ (o.locusId=$$locuss[$i] and o.version=$$versions[$i])/;
	    }
	    my $restriction = join " or ", @conditions;
	    $restriction = "( " . $restriction . " )";
	    push @query, $restriction;
	}
	else {
	    my $restriction = join " , ", @$locuss;
	    $restriction = "o.locusId in (" . $restriction  . ")";
	    push @query, $restriction;
	}
    }

    push @query, "o.priority=1" unless defined($params{version});

    my $query = join " AND ", @query;
    my $statement = qq/SELECT begin, end, strand, o.locusId, o.version, o.posId, c.cogInfoId, s.scaffoldId FROM Scaffold s, Position p, Locus o  LEFT JOIN COG c ON (o.locusId=c.locusId AND o.version=c.version) WHERE $query AND p.scaffoldId=s.scaffoldId AND o.posId=p.posId ORDER BY s.scaffoldId, begin, o.locusId/;
    my $refGenes = GenomicsUtils::query( $statement );

    my %preloaded;
    if (defined($params{preload})) {
	my $preloadsRef = $params{preload};
	foreach my $feature (@$preloadsRef) {
	    if( $feature eq 'synonym' ) {
		$statement = qq/SELECT o.locusId, sy.name, sy.type  FROM Scaffold s, Position p, Locus o LEFT JOIN Synonym sy ON (o.locusId=sy.locusId AND o.version=sy.version) WHERE $query AND p.scaffoldId=s.scaffoldId AND o.posId=p.posId  ORDER BY s.scaffoldId, begin, locusId/;
		my $synonymRef = GenomicsUtils::query( $statement );
		$preloaded{synonym} =  $synonymRef;
	    } elsif( $feature eq 'annotation' ) {
		$statement = qq/SELECT o.locusId, o.version, a.descriptionId, a.description, a.created, a.source  FROM Scaffold s, Position p, Locus o LEFT JOIN Description a ON (o.locusId=a.locusId AND o.version=a.version) WHERE $query AND p.scaffoldId=s.scaffoldId AND o.posId=p.posId  ORDER BY s.scaffoldId, begin, locusId/;
		my $annotationRef =  GenomicsUtils::query( $statement );
		$preloaded{annotation} =  $annotationRef;
	    } elsif ( $feature eq 'ortholog' ) {
		$statement = qq/SELECT o.locusId,
	                               p2.begin, p2.end, p2.strand, o2.locusId, o2.version,
		                       o2.posId, c2.cogInfoId, p2.scaffoldId
				FROM Scaffold s, Position p, Locus o,
				     Position p2, Locus o2, Ortholog orth
				LEFT JOIN COG c2 ON (o2.locusId=c2.locusId AND o2.version=c2.version)
				WHERE $query AND p.scaffoldId=s.scaffoldId AND o.posId=p.posId
				AND o2.posId=p2.posId
				AND orth.locus1 = o.locusId AND orth.locus2 = o2.locusId
				ORDER BY p2.scaffoldId/;
		my $orthologsRef = {};
		foreach (@{ GenomicsUtils::query($statement) }) {
		    my ($locusId, $start2, $stop2, $strand2, $locusId2,
			$version2, $posId2, $cog2, $scaffold2) = @{$_};
		    my $gene = Gene::new( begin => $start2, end => $stop2,
					  strand => $strand2, locusId => $locusId2,
					  version => $version2, posId => $posId2,
					  cogInfoId => $cog2, scaffoldId => $scaffold2 );
		    addToHashList($orthologsRef, $locusId, $gene);
		}
		$preloaded{ortholog} = $orthologsRef;
	    } else {
		die "Unknown feature $feature";
	    }
	}
    }

    my @genes = ();
    foreach my $refRow (@$refGenes){
	my ( $start, $stop, $strand,
	     $locusId, $version, $posId, $cog, $scaffoldId ) = @$refRow;
	my @params = ( begin => $start, end => $stop,
		       strand => $strand, locusId => $locusId,
		       version => $version, posId => $posId,
		       cogInfoId => $cog, scaffoldId => $scaffoldId );
	push @params, ( synonym => &createSynonymHash( $preloaded{synonym},
						       $locusId  ) ) if defined $preloaded{synonym};
	if (exists $preloaded{ortholog}) {
	    push @params, ( orthologListRef => ( exists $preloaded{ortholog}->{$locusId} ?
						 $preloaded{ortholog}->{$locusId} : [] ) );
	}
	my $gene = Gene::new( @params );
	push @genes, $gene;
    }
    return \@genes;
}

sub toString{
    my $self = shift;
    return "[".$self->{scaffoldId_}."] ".$self->{file_}; 
}

sub getScaffoldList{
    return map { &Scaffold::new( scaffoldId => $_ ) }
    sort {$a<=>$b} &queryList( qq/ SELECT scaffoldId FROM
			       Scaffold WHERE isActive=1/ );
}

sub getScaffoldMap{
    return map { $_->scaffoldId(), $_ } &getScaffoldList;
}

#---------------------------------------------------------------------
#Preload features
#This crap needs to be cleaned up!!!!

sub preloadSynonym(){
    my $self = shift;
    my $scaffoldId = $self->{scaffoldId_};

# Trying to recreate the statement 
    my $statement = qq/SELECT o.locusId, sy.name, sy.type  FROM Scaffold s, Position p, Locus o LEFT JOIN Synonym sy ON (o.locusId=sy.locusId AND o.version=sy.version) WHERE s.scaffoldId=$scaffoldId AND p.scaffoldId=s.scaffoldId AND o.posId=p.posId  ORDER BY begin, locusId/;
    my $synonymRef = GenomicsUtils::query( $statement );
    my $genesRef = $self->genesRef();
    foreach my $g (@$genesRef) {
	my $synonymHashRef = createSynonymHash( $synonymRef, $g->locusId()  );
	$g->setSynonym( $synonymHashRef );
    }
    $self->{preloadSynonym_}=1;
}

# MNP 10/20/03 - Fixed to remove elements from the input synonymRef
sub createSynonymHash( $ $ ) {
    my ( $synonymRef, $locusId ) = @_;

    # synonymRef has the following structure  o.locusId, s.name, s.type
    my %synonymHash;

    while ( scalar @{ $synonymRef } > 0 &&
	    $locusId == $synonymRef->[0][0] ) {
	my $row = shift @{ $synonymRef };
	$synonymHash{ $row->[2] } = $row->[1];
    }
    return \%synonymHash;
}

return 1;

