package EnsEMBL::Web::Document::SpreadSheet;

use strict;
use Exporter;
use EnsEMBL::Web::Root;

our @ISA = qw(Exporter EnsEMBL::Web::Root);
our @EXPORT = qw(FORMAT_BOLD FORMAT_ITALIC FORMAT_BOLDITALIC FORMAT_NDP ALIGN_LEFT ALIGN_CENTER ALIGN_RIGHT ALTERNATING_BACKGROUND FORMAT_THOUSANDIFY);
our @EXPORT_OK = qw(FORMAT_BOLD FORMAT_ITALIC FORMAT_BOLDITALIC FORMAT_NDP ALIGN_LEFT ALIGN_CENTER ALIGN_RIGHT ALTERNATING_BACKGROUND FORMAT_THOUSANDIFY);
our %EXPORT_TAGS = (
  'FORMATS' => [qw(FORMAT_BOLD FORMAT_ITALIC FORMAT_BOLDITALIC FORMAT_NDP ALIGN_LEFT ALIGN_CENTER ALIGN_RIGHT ALTERNATING_BACKGROUND FORMAT_THOUSANDIFY)]
);

sub FORMAT_BOLD       { format => sub { "<strong>$_[0]</strong>" } }
sub FORMAT_ITALIC     { format => sub { "<em>$_[0]</em>" } }
sub FORMAT_BOLDITALIC { format => sub { "<strong><em>$_[0]</em></strong>" } }
sub FORMAT_NDP        { my $T = shift; sub { sprintf "%0.${T}f", $_[0] } }
sub FORMAT_THOUSANDIFY  { sprintf $_[0]->thousandify( $_[1] ); }
sub ALIGN_LEFT        { align => 'left' }
sub ALIGN_CENTER      { align => 'center' }
sub ALIGN_RIGHT       { align => 'right' }
sub ALTERNATING_BACKGROUND { my $self = shift ; rows => [qw(bg1 bg2)] };

sub new { # All now configured by the component!!
  my $class = shift;
  my ($c, $d, $o, $s) = @_;
  
  $c ||= [];
  $d ||= [];
  $o ||= {};
  $s ||= [];
  
  my $self = {
    '_columns'  => $c,
    '_spanning' => $s,
    '_data'     => $d,
    '_options'  => $o
  };
  
  bless $self, $class;
}

sub strip_HTML {
  my ($self, $string) = @_;
  $string =~ s/<[^>]+>//g;
  return $string;
}

sub render {
  my $self = shift;
  return unless @{$self->{'_columns'} || []};
  my $options = $self->{'_options'} || {};
  my $align = $options->{'align'} || 'autocenter';
  my $width = $options->{'width'} || '100%';
  my $margin = $options->{'margin'} || '0px';
  my $padding = $options->{'cellpadding'} || 0;
  my $spacing = $options->{'cellspacing'} || 0;

  $align .= ' top-border' if $options->{'header'} eq 'no';
  my $output = qq{\n<table class="ss $align" style="width:$width;margin:$margin" cellpadding="$padding" cellspacing="$spacing">};

  if (scalar(@{$self->{'_spanning'}})) {
    $output .= qq{\n  <tr class="ss_header">};
    foreach my $header (@{$self->{'_spanning'}}) {
      my $span = $header->{'colspan'} || 1;
      $output .= qq{<th colspan="$span"><em>$header->{'title'}</em></th>};
    }
    $output .= "</tr>\n";
  }

  foreach my $row (@{$self->_process}) {
    my $tag = 'td';
    if ($row->{'style'} eq 'header') {
      $output .= qq{\n  <tr class="ss_header">};
      $tag = 'th';
    } elsif ($row->{'style'} eq 'total') {
      $output .= "\n  <tr>";
      $tag = 'th';
    } else {
      my $valign = $row->{'valign'} || 'top';
      my $class = $row->{'class'} ? qq{ class="$row->{'class'}"} : '';
      $output .= qq{\n  <tr style="vertical-align:$valign"$class>};
    }
    foreach my $cell (@{$row->{'cols'}}) {
      my $extra = $cell->{'class'} ? qq{ class="$cell->{'class'}"} : '';
      $extra .= $cell->{'style'} ? qq{ style="$cell->{'style'}"} : '';
      $extra .= $cell->{'colspan'} ? qq{ colspan="$cell->{'colspan'}"} : '';
      my $val = defined ($cell->{'value'}) && $cell->{'value'} ne '' ? $cell->{'value'} : '<span style="display:none">-</span>';
      $output .= "\n    <$tag$extra>$val</$tag>";
    }

    $output .= "\n  </tr>";
  }
  $output .= "\n</table>";
  return $output;
}

sub render_Text {
  my $self    = shift;
  return unless @{$self->{'_columns'} || []};
  my $options = $self->{'_options'} || {};
  my $align = $options->{'align'} ? $options->{'align'} : 'autocenter';
  my $width = $options->{'width'} ? $options->{'width'} : '100%';

  my $output = '';
  foreach my $row (@{$self->_process}) {
    $output .= join "\t", map { $self->strip_HTML($_->{'value'}) } @{$row->{'cols'}};
    $output .= "\n";
  }
  return $output;
}

sub _process {
  my $self = shift;

  my $counter = 0;
  my $data    = $self->{'_data'}    || [];
  my $columns = $self->{'_columns'} || [];
  my $options = $self->{'_options'} || {};

  my $no_cols = @$columns;
  # Start the table...
  my $return = [];
  foreach (0..($no_cols-1)) {
    my $col = $columns->[$_];
    $col = $columns->[$_] = { 'key' => $col } unless ref $col eq 'HASH';
    $counter++;
  }

  # Draw the header row unless the "header" options is set to "no"
  unless ($options->{'header'} eq 'no') {
    $counter = 0;
    my $border;
    my $row = { 'style' => 'header', 'cols' => [] };
    my $average = int(100/scalar $columns);
    foreach (@$columns) {
      push (@{$row->{'cols'}}, {
        'style' => 'text-align:' . ($options->{'alignheader'} || $_->{'align'} || 'center') . ';width:' . ($_->{'width'} || $average.'%'), 
        'value' => defined $_->{'title'} ? $_->{'title'} : $_->{'key'}, 
        'class' => 'bottom-border' 
      });
    }
    push (@$return, $row);
  }

  # Display each row in the table
  my $row_count = 0;
  my @sorted_data;
  if ($options->{'sort'}) {
    # This doesn't actually work, and _sort_array doesn't exist
    @sorted_data = (sort {
      ref $options->{'sort'} eq 'CODE' ? &{$options->{'sort'}}($a, $b) : $self->_sort_array($self->_sort_pars($options->{'sort'}, $columns), $a, $b)
    } @$data);
  } else {
    @sorted_data = @$data;
  }
  my @previous_row = ();
  my @totals = ();
  my $row_colours = exists $options->{'rows'} ? $options->{'rows'} : [ 'bg1', 'bg2' ];
  foreach my $row (@sorted_data) {
    my $flag = 0;
    my $out_row = { 'style' => 'row', 'class' => $row_colours->[0], 'col' => [] };
    $counter = 0;
    foreach my $col (@$columns) {
      my $value = get_value($row, $counter, $col->{'key'});
      my $hidden_value = lc (exists $col->{'hidden_key'} ? get_value($row, $counter, $col->{'hidden_key'}) : $value);
      my $class = '';
      # $#sorted_data is the last index of @sorted_data
      if ($row_count == $#sorted_data && !$options->{'total'}) {
        $class .= ' bottom-border';
      }
      my $style = exists $col->{'align'} ? "text-align:$col->{'align'};" : ($col->{'type'} eq 'numeric' ? 'text-align:right;' : '');
      $style .= exists $col->{'width'} ? "width:$col->{'width'};" : '';
      if (exists $options->{'row_style'} && $options->{'row_style'}->[$row_count]) {
        $style .= $options->{'row_style'}->[$row_count]->[$counter];
      }
      if ($flag == $counter) {
        if ($hidden_value eq $previous_row[$counter]) {
          $flag = $counter+1;
          $value = '' if $options->{'triangular'};
        }
      }
      $previous_row[$counter] = $hidden_value;
      my $val = $value;
      my $f = $col->{'format'};
      if ($value ne '' && $f) {
        if (ref $f eq 'CODE') {
          $val = $f->($value, $row);
        } elsif ($self->can($f)) {
          $val = $self->$f($value, $row);
        }
      }
      push (@{$out_row->{'cols'}}, { 
        'value' => $val,
        'class' => $class,
        'style' => $style
      });
      $counter++;
    }
    next if $flag == $counter; # SKIP WHOLLY BLANK LINES
    push (@$row_colours, shift @$row_colours);

    $row_count++;
    if ($options->{'total'} > 0) { # SUMMARY TOTALS
      if ($flag < $options->{'total'}) {
        for (my $i = $options->{'total'}-1; $i > $flag; $i--) {
          next unless @totals;
          my $TOTAL_ROW = pop @totals;
          my $total_row = { 'style' => 'total', 'cols' => [] };
          my $counter = 0;
          foreach my $col (@$columns) {
            my $class = ($i == $flag+1) ? 'bottom-border' : '';
            my $value = '';
            my $style = '';
            if ($counter == @totals) {
              $value = 'TOTAL';
            } elsif ($counter > @totals && $col->{'type'} eq 'numeric') {
              $style = 'text-align:right';
              $value = $self->thousandify($TOTAL_ROW->[$counter]);
            }
            push (@{$total_row->{'cols'}}, { 'value' => $value, 'style' => $style, 'class' => $class });
            $counter++;
          }
          push (@$return, $total_row);
        }
      }
      my $counter = 0;
      foreach my $col (@$columns) {
        if ($col->{'type'} eq 'numeric') {
          my $value = get_value($row, $counter, $col->{'key'});
          for (my $i = 0; $i < $options->{'total'}; $i++) {
            $totals[$i][$counter] += $value;
          }
        }
        $counter++;
      }
    }
    push (@$return, $out_row);
  }
  if ($options->{'total'} > 0) { # SUMMARY TOTALS
    while (@totals) {
      my $TOTAL_ROW = pop @totals;
      my $total_row = { 'style' => 'total', 'cols' => [] };
      my $counter = 0;
      foreach my $col (@$columns) {
        my $class = @totals ? 'bottom-border' : '';
        my $value = '';
        my $style = '';
        if ($counter == @totals) {
          $value = 'TOTAL';
        } elsif ($counter > @totals && $col->{'type'} eq 'numeric') {
          $value = $self->thousandify($TOTAL_ROW->[$counter]);
          $style = 'text-align:right';
        }
        push (@{$total_row->{'cols'}}, { 'value' => $value, 'style' => $style, 'class' => $class });
        $counter++;
      }
      push (@$return, $total_row);
    }
  }
  foreach (@$return) {
    $_->{'cols'}[0]{'class'} .= ' left-border';
    $_->{'cols'}[-1]{'class'} .= ' right-border';
  }
  return $return;
}

sub add_option {
  my $self = shift;
  my $key = shift;
  if (ref $self->{'_options'}->{$key} eq 'HASH') {
    $self->{'_options'}->{$key} = {%{$self->{'_options'}->{$key}}, %{$_[0]}};
  } elsif (ref $self->{'_options'}->{$key} eq 'ARRAY') {
    push (@{$self->{'_options'}->{$key}}, @_);
  } elsif (scalar @_ == 1) {
    $self->{'_options'}->{$key} = ref $_[0] eq 'ARRAY' ? [ $_[0] ] : $_[0];
  } else {
    $self->{'_options'}->{$key} = \@_;
  }
}

sub add_columns {
  my $self = shift;
  push (@{$self->{'_columns'}}, @_);
}

sub add_spanning_headers {
  my $self = shift;
  push (@{$self->{'_spanning'}}, @_);
}

sub add_row {
  my ($self, $data) = @_;
  push (@{$self->{'_data'}}, $data);
}

sub get_value {
  my ($row, $counter, $key) = @_;  
  my $rtn = '--';
  if (ref $row eq 'ARRAY') {
    $rtn = $row->[$counter];
  } elsif (ref $row eq 'HASH') {
    $rtn = $row->{$key}
  } elsif ($row->can($key)) {
    $rtn = $row->${\$key}();
  }
  return $rtn;
}
1;