package Bio::EnsEMBL::GlyphSet::P_protdas;
use strict;
use base qw(Bio::EnsEMBL::GlyphSet);
use Sanger::Graphics::ColourMap;
use Sanger::Graphics::Bump;
use Bio::EnsEMBL::Glyph::Symbol::box; 
use POSIX; #floor

sub _init {
  my ($self) = @_;

  my $conf = $self->{'extras'};
  $self->{'pix_per_bp'}    = $self->{'config'}->transform->{'scalex'};
  my $prot_len      = $self->{'container'}->length;
  $conf->{'length'} = $prot_len;

# Check that we have features back and it's not a error message
  if (my @das_features = @{$conf->{features} || []}) {
    my $f = $das_features[0];
    if($f->das_type_id() eq '__ERROR__') {
      $self->errorTrack( 'Error retrieving '.$self->{'extras'}->{'label'}.' features ('.$f->das_id.')');
      return -1 ;   # indicates no features drawn because of DAS error
    }
  } else {
    $self->errorTrack( 'No positional '.$conf->{'label'}.' features in this region' );
    return 0;
  }

### If we display a genomic source (i.e features are chromosome based) then we need to 
### map them to the peptide
  if ($conf->{'source_type'} =~ /^ensembl_location/) {
    my $transcript = $self->{'container'}->adaptor->db->get_TranscriptAdaptor->fetch_by_translation_stable_id( $self->{'container'}->stable_id );
    my @features;
    foreach my $feat (@{$conf->{features}}) {
      my @coords =  grep { $_->isa('Bio::EnsEMBL::Mapper::Coordinate') } $transcript->genomic2pep($feat->das_segment->start, $feat->das_segment->end, $feat->strand);
      if (@coords) {
        my $c = $coords[0];
        my $end = ($c->end > $prot_len) ? $prot_len : $c->end; 
        $feat->das_end( $end );
        my $start = ($c->start < $end) ? $c->start : $end;
        $feat->das_start($start);
        push (@features, $feat);
      }
    }
    $conf->{features} = \@features;
  }

# Styles are returned as an array - we build a hash so it is easier to use ( probably should look into the fetching function ... ;)
  # hash styles by type
  my %styles;
  my $styles  = $conf->{'styles'};

  if( $styles && @$styles && $conf->{'use_style'} ) {
    my $styleheight = 0;
    foreach(@$styles) {
      $styles{$_->{'category'}}{$_->{'type'}} = $_ unless $_->{'zoom'};

      # Set row height ($configuration->{'h'}) from stylesheet
      # Currently, this uses the greatest height present in the stylesheet
      # but should really use the greatest height in the current featureset

      if (exists $_->{'attrs'} && exists $_->{'attrs'}{'height'}){
        my $tmpheight = $_->{'attrs'}{'height'};
        $tmpheight += abs $_->{'attrs'}{'yoffset'} if $_->{'attrs'}{'yoffset'} ;
        $styleheight = $tmpheight if $tmpheight > $styleheight;
      }
    }
    $conf->{'h'} = $styleheight if $styleheight;
    $conf->{'styles'} = \%styles;
  } else {
    $conf->{'use_style'} = 0;
  }

  if (my $chart = $conf->{'score'}){
    return $self->render_colourgradient( $conf ) if ($chart eq 'c');
    return $self->render_tilingarray( $conf )   if ($chart eq 's');
    return $self->render_histogram( $conf )     if ($chart eq 'h');
  }
  return $self->render_grouped($conf);
}

sub gmenu {
  my ($self, $f) = @_;
  my $desc = $f->das_feature_label() || $f->das_feature_id;
  my $zmenu = { 'caption' => $desc };
  if( my $m = $f->das_feature_id ){ $zmenu->{"03:ID: $m"}     = undef }
  if( my $m = $f->das_type       ){ $zmenu->{"05:TYPE: $m"}   = undef }
  if( my $m = $f->das_method     ){ $zmenu->{"10:METHOD: $m"} = undef }
  my $ids = 15;
  my $href;
  foreach my $dlink ($f->das_links) {
    my $txt = $dlink->{'txt'} || $dlink->{'href'};
    my $dlabel = sprintf("%02d:LINK: %s", $ids++, $txt);
    $zmenu->{$dlabel} = $dlink->{'href'};
    $href =  $dlink->{'href'} if (! $href);
  }
  if( my $m = $f->das_note       ) { 
    if (ref $m eq 'ARRAY') {
	foreach my $n (@$m) {
	  $zmenu->{"$ids:NOTE: $n"}   = undef;
	  $ids++;
	}
     } else {
	$zmenu->{"40:NOTE: $m"}   = undef;
     }
  }
  return $zmenu;
}

#----------------------------------------------------------------------
# Returns the order corresponding to this glyphset
sub managed_name{
  my $self = shift;
  return $self->{'extras'}->{'order'} || 0;
}
sub zmenu {
  my ($self, $f) = @_;
  my $desc = $f->das_feature_label() || $f->das_feature_id;
  my $zmenu = { 'caption' => $desc };
  if( my $m = $f->das_score){ $zmenu->{"20:SCORE: $m"}     = undef }
  my $ids = 50;
  my $href;
  foreach my $dlink ($f->das_links) {
    my $txt = $dlink->{'txt'} || $dlink->{'href'};
    my $dlabel = sprintf("%02d:LINK: %s", $ids++, $txt);
    $zmenu->{$dlabel} = $dlink->{'href'};
    $href =  $dlink->{'href'} if (! $href);
  }
if( my $m = $f->das_note       ) {
    if (ref $m eq 'ARRAY') {
        foreach my $n (@$m) {
          $zmenu->{"$ids:NOTE: $n"}   = undef;
          $ids++;
        }
     } else {
        $zmenu->{"40:NOTE: $m"}   = undef;
     }
  }
  return $zmenu;
}

# Function will display DAS features with variable height depending on SCORE attribute
sub render_histogram {
  my( $self, $configuration ) = @_;

  my @features = sort { $a->das_start() <=> $b->das_start() } @{$configuration->{'features'} || []};
  my ($min_score, $max_score) = (sort {$a <=> $b} (map { $_->score } @features))[0,-1];
  my $style;

  my $row_height = $configuration->{'h'} || 30;
  my $pix_per_score = ($max_score - $min_score) / $row_height;
  my $bp_per_pix = 1 / $self->{pix_per_bp};

  $configuration->{h} = $row_height;

  my ($gScore, $gWidth, $fCount, $gStart, $mScore) = (0, 0, 0, 0, $min_score);
  for (my $i = 0; $i< @features; $i++) {
    my $f = $features[$i];

    # keep within the window we're drawing
    my $START = $f->das_start() < 1 ? 1 : $f->das_start();
    my $END   = $f->das_end()   > $configuration->{'length'}  ? $configuration->{'length'} : $f->das_end();
    my $width = ($END - $START +1);

    my $score = $f->das_score;
    $score = $max_score if ($score > $max_score);
    $score = $min_score if ($score < $min_score);

# Here we "group" features if they are too small and located very close to each other ..

    $gWidth += $width;
    $gScore += $score;
    $mScore = $score if ($score > $mScore);
    $fCount ++;
    $gStart = $START if ($fCount == 1);
# If feature is smaller than 1px and next feature is closer than 1px then we merge features ..
# 1px value depend on the zoom ..
    if ($gWidth < $bp_per_pix) {
      my $nf = $features[$i+1];
      if ($nf) {
        my $distance = $nf->das_start() - $END;
        next if ($distance < $bp_per_pix);
      }
    }
    my $height;
    if (lc($configuration->{'fg_merge'}) eq 'a') { # get the average value
      $height = ($gScore / $fCount - $min_score) / $pix_per_score;
     } else { # get the max value
      $height = ($mScore - $min_score) / $pix_per_score;
      if ($height < 0) {
        warn("ERROR: !! $mScore * $min_score * $pix_per_score");
      }
    }
    my ($zmenu );
    my $Composite = $self->Composite({
      'y'         => 0,
      'x'         => $START-1,
      'absolutey' => 1,
    });

    if ($fCount > 1) {
      $zmenu = {
        'caption'         => $configuration->{'label'},
      };
      $zmenu->{"03:$fCount features merged"} = '';
      $zmenu->{"05:Average SCORE: ".($gScore/$fCount)} = '';
      $zmenu->{"08:Max SCORE: $mScore"} = '';
      $zmenu->{"10:START: $gStart"} = '';
      $zmenu->{"20:END: $END"} = '';
    } else {
      $zmenu = $self->zmenu( $f );
    }
    $Composite->{'zmenu'} = $zmenu;

    my $y_offset = $row_height - $height;
    my $style = $self->get_featurestyle($f, $configuration);
    my $fdata = $self->get_featuredata($f, $configuration, $y_offset);

    my $symbol = Bio::EnsEMBL::Glyph::Symbol::box->new($fdata, $style->{'attrs'});
    $symbol->{'style'}->{'height'} = $height;

    $Composite->push($symbol->draw);
    $self->push( $Composite );

    $gWidth = $gScore = $fCount = 0;
    $mScore = $min_score;
  } # END loop over features

  return 1;
}   # END render_histogram

# Function will display DAS features with variable height depending on SCORE attribute
# Similar to histogram but allows for negative values and will highlight pick values, i.e
# when 2 or more features are merged due to resolution the highest score will be used to determine the feature height
# Probably should merge with histogram as they are very similar

sub render_tilingarray{
  my( $self, $configuration ) = @_;
  my @features = sort { $a->das_score <=> $b->das_score  } @{$configuration->{'features'}};
  my ($min_score, $max_score) = ($features[0]->das_score || 0, $features[-1]->das_score || 0);
  my $style;

  my @positive_features = grep { $_->das_score >= 0 } @features;
  my @negative_features = grep { $_->das_score < 0 } reverse @features;

  my $row_height = $configuration->{'h'} || 30;
  my $pix_per_score = (abs($max_score) >  abs($min_score) ? abs($max_score) : abs($min_score)) / $row_height;
  my $bp_per_pix = 1 / $self->{pix_per_bp};
  $configuration->{h} = $row_height;

  my ($gScore, $gWidth, $fCount, $gStart, $mScore) = (0, 0, 0, 0, $min_score);

# Draw the axis

  $self->push( $self->Line({
    'x'         => 0,
    'y'         => $row_height + 1,
    'width'     => $configuration->{'length'},
    'height'    => 0,
    'absolutey' => 1,
    'colour'    => 'red',
    'dotted'    => 1,
  }));

  $self->push( $self->Line({
    'x'         => 0,
    'y'         => 0,
    'width'     => 0,
    'height'    => $row_height * 2 + 1,
    'absolutey' => 1,
    'absolutex' => 1,
    'colour'    => 'red',
    'dotted'    => 1,
  }));

  foreach my $f (@negative_features, @positive_features) {
    my $START = $f->das_start() < 1 ? 1 : $f->das_start();
    my $END   = $f->das_end()   > $configuration->{'length'}  ? $configuration->{'length'} : $f->das_end();
    my $width = ($END - $START +1);
    my $score = $f->das_score || 0;

    my $Composite = $self->Composite({
      'y'         => 0,
      'x'         => $START-1,
      'absolutey' => 1,
    });

    my $height = abs($score) / $pix_per_score;
    my $y_offset =     ($score > 0) ?  $row_height - $height : $row_height+2;
    $y_offset-- if (! $score);

    my $zmenu = $self->zmenu( $f );
    $Composite->{'zmenu'} = $zmenu;

    # make clickable box to anchor zmenu
    $Composite->push( $self->Space({
      'x'         => $START - 1,
      'y'         => ($score ? (($score > 0) ? 0 : ($row_height + 2)) : ($row_height + 1)),
      'width'     => $width,
      'height'    => $score ? $row_height : 1,
      'absolutey' => 1
    }) );

    my $style = $self->get_featurestyle($f, $configuration);
    my $fdata = $self->get_featuredata($f, $configuration, $y_offset);

    my $symbol = Bio::EnsEMBL::Glyph::Symbol::box->new($fdata, $style->{'attrs'});
    $symbol->{'style'}->{'height'} = $height;

    $Composite->push($symbol->draw);
    $self->push( $Composite );
  } # END loop over features

 return 1;
}   # END render_tilingarray
# Function will display DAS features in different colour with depending on SCORE attribute
sub render_colourgradient {
  my ($self, $configuration) = @_; 

  my $bp_per_pix = 1 / $self->{pix_per_bp};
  my @features = sort { $a->das_score <=> $b->das_score } @{$configuration->{'features'} || []};
  my ($min_value, $max_value) = $configuration->{'fg_data'} eq 'n' ? ($configuration->{'fg_min'}, $configuration->{fg_max}): ($features[0]->das_score || 0, $features[-1]->das_score || 0);
  my ($min_score, $max_score) = $configuration->{'fg_data'} eq 'n' ? (0, 100): ($min_value, $max_value);

  $configuration->{'fg_grades'} ||= 20;

  my $score_range = $max_value - $min_value;
  my $score_per_grade =  ($max_score - $min_score)/ $configuration->{'fg_grades'};
  my $cm = new Sanger::Graphics::ColourMap;
  my @cg = $cm->build_linear_gradient($configuration->{'fg_grades'}, ['yellow', 'green', 'blue']);
  my $style;
# To make sure that the features with lowest and highest scores get displayed
  push @features, $features[0];
  push @features, $features[-2];

  my $y_offset =     0;

  my $row_height = $configuration->{'h'} || 20;
  $configuration->{h} = $row_height;

 foreach my $f (@features) {
    my $START = $f->das_start() < 1 ? 1 : $f->das_start();
    my $END   = $f->das_end()   > $configuration->{'length'} ? $configuration->{'length'} : $f->das_end();
    my $width = ($END - $START +1);
    my $score = $configuration->{'fg_data'} eq 'n' ? ((($f->das_score || 0) - $min_value) * 100 / $score_range) : ($f->das_score || 0);

    if ($score < $min_value) {
      $score = $min_value;
    } elsif ($score > $max_value) {
      $score = $max_value;
    }
    my $Composite = $self->Composite({
      'y'         => 0,
      'x'         => $START-1,
      'absolutey' => 1,
    });

    my $grade = ($score >= $max_score) ? $configuration->{'fg_grades'} - 1 : int(($score - $min_score) / $score_per_grade);
    $grade = 0 if ($grade < 0);
    my $col = $cg[$grade];

    my $zmenu = $self->zmenu($f);
    $Composite->{'zmenu'} = $zmenu;

    # make clickable box to anchor zmenu
    $Composite->push( $self->Space({
      'x'         => $START - 1,
      'y'         => 0,
      'width'     => $width,
      'height'    => $row_height,
      'absolutey' => 1
    }) );

    my $style = $self->get_featurestyle($f, $configuration);
    my $fdata = $self->get_featuredata($f, $configuration, $y_offset);

    my $symbol = Bio::EnsEMBL::Glyph::Symbol::box->new($fdata, $style->{'attrs'});
    $style->{'attrs'}{'colour'} = $col;

    $Composite->push($symbol->draw);
    $self->push( $Composite );
  } # END loop over features

  return 1;
}   # END render_colourgradient
# Function will display DAS features grouped by feature id ( which is wrong ! DAS spec demands unique feature id! )
# Need to talk to das source maintainers first to convience them to update das sources to comply with DAS spec

sub render_grouped {
  my ($self, $configuration) = @_; 
  my $Config        = $self->{'config'};
  my @bitmap        = undef;
  my $prot_len      = $configuration->{'length'};
  my $pix_per_bp    = $self->{'pix_per_bp'};
  my $bitmap_length = floor( $prot_len * $pix_per_bp);

  my $y             = 0;
  my $h             = $configuration->{'h'} || 4;
  $configuration->{'h'} = $h;

  my $fhash;

  $self->_init_bump;

  foreach my $f (@{$self->{extras}->{features}}) {
# Create a new composite and put the feature there
    my $Composite = $self->Composite({
      'x'     => $f->start,
      'y'     => $y,
      'zmenu' => $self->gmenu($f),
    });

    my $style = $self->get_featurestyle($f, $configuration);
    my $fdata = $self->get_featuredata($f, $configuration, 0);
    my $symbol = $self->get_symbol($style, $fdata);
#    my $symbol = Bio::EnsEMBL::Glyph::Symbol::box->new($fdata, $style->{'attrs'});
   $Composite->push($symbol->draw);

# Now check that the new feature does not overlap any preceeding features
# If it does than 'bump' the row, i.e move the composite down to the next row

    my $bump_start = floor($Composite->x() * $pix_per_bp);
    next if ($bump_start > $bitmap_length);
    my $bump_end = $bump_start + floor($Composite->width()*$pix_per_bp);
    my $row = $self->bump_row( $bump_start, $bump_end );
    );
    $Composite->y($Composite->y() + $row * ($h + 2) );
    $self->push($Composite);

# Now to the bit that always was in Ensembl but does not comply with DAS spec, namely grouping features by feature id.
# need to address the issue to make sure we comply with DAS spec
# meantime we put a line between features that have same id and reside on the same row in the bitmap

    my $key = join('*', $row,$f->das_feature_id); 
    if (my $ox = $fhash->{$key}) {
      my $rect = $self->Rect({
         'x'         => $ox,
         'y'         => 2,
         'width'     => $f->start - $ox,
         'height'    => 0,
         'colour'    => $style->{'attrs'}{'colour'},
         'absolutey' => 1,
      });
      $Composite->push($rect);
    }

    $fhash->{$key} = $f->end;
  }
}
1;