package displays;

use Data::Dumper;

use constant TRUE => 1;
use constant FALSE => 0;

use Gtk2;
use strict;

my @colordefs = ( Gtk2::Gdk::Color->parse('red'),
                  Gtk2::Gdk::Color->parse('green'),
                  Gtk2::Gdk::Color->parse('blue'),
                  Gtk2::Gdk::Color->parse('cyan'),
                  Gtk2::Gdk::Color->parse('magenta'),
                  Gtk2::Gdk::Color->parse('yellow'),
                  Gtk2::Gdk::Color->parse('orange'),
                  Gtk2::Gdk::Color->parse('brown'),
                  Gtk2::Gdk::Color->parse('purple'),
                  Gtk2::Gdk::Color->parse('black') );

my $xsize = 320;
my $ysize = 240;

# add display
sub adddisplay {
  my ($widget, $type, $item) = @_;

  # tool type
  my $name;
  if ($type eq 1) {
    $name = "Table";
  } elsif ($type eq 2) {
    $name = "Avg. Value";
  } elsif ($type eq 3) {
    $name = "Graph";
  } elsif ($type eq 4) {
    $name = "Histogram";
  } elsif ($type eq 5) {
    $name = "Adapt. Hist.";
  } else {
    $name = "Unknown";
  }

  # create box
  $item = box::createbox($name, 'pixmaps/display.png', 'DISPLAY');
  $item->{name} = $name;

  # parameters
  $item->{data} = { process =>
                      sub { my ($d) = @_; @{$d->{table}} = @{$d->{raw}} },
                    preprocess => sub { },
                    postprocess => sub { } };
  if ($type eq 1) {
    $item->{setting} =
      {separator => {value => ",", label => "Field separator", type => "entry", length => 1},
       firstline => {value => TRUE, label => "First line", type => "checkbutton"}};
    $item->{data}->{show} = \&displays::table;
    $item->{data}->{setting} = $item->{setting};
  } elsif ($type eq 2) {
    $item->{data}->{show} = \&displays::value;
    $item->{data}->{process} = \&displays::averaging;
  } elsif ($type eq 3) {
    $item->{data}->{show} = \&displays::graph;
    $item->{data}->{process} =
      sub { my ($d) = @_; @{$d->{table}} = @{$d->{raw}}; undef($d->{xtable}) };
  } elsif ($type eq 4) {
    $item->{data}->{show} = \&displays::graph;
    $item->{data}->{process} = \&displays::histogram;
    $item->{setting}->{nbbins} =
      {value => 10, label => "Number of bins", type => "entry", length => 3};
    $item->{data}->{setting} = $item->{setting};
  } elsif ($type eq 5) {
    $item->{data}->{show} = \&displays::graph;
    $item->{data}->{process} = \&displays::adapthist;
    $item->{setting}->{nbbins} =
      {value => 10, label => "Number of bins", type => "entry", length => 3};
    $item->{data}->{setting} = $item->{setting};
  }
}

sub table {
  my ($data) = @_;

  my $vbox = Gtk2::VBox->new (FALSE, 0);

  my $hbox = Gtk2::HBox->new (FALSE, 0);
  $vbox->pack_start ($hbox, FALSE, FALSE, 0);

  my $name = Gtk2::Label->new ("Save name");
  $hbox->pack_start ($name, FALSE, FALSE, 2);

  my $entry = Gtk2::Entry->new_with_max_length (2048);
  $data->{entry} = $entry;
  $entry->set_width_chars (32);
  $entry->set_text ($data->{savename});
  $entry->signal_connect ("focus_out_event",
                          sub{ my ($w, undef, $d) = @_;
                               $d->{savename} = $w->get_text;
                               return FALSE }, $data);
  $hbox->pack_start ($entry, TRUE, TRUE, 5);

  my $browse = Gtk2::Button->new_from_stock("gtk-find");
  $browse->signal_connect ("clicked",
    sub { my (undef, $d) = @_;
          return if (defined($d->{fs}));
          $d->{fs} = Gtk2::FileSelection->new ('Save name');
          $d->{fs}->set_select_multiple (0);
          $d->{savename} = $d->{fs}->get_filename if ('ok' eq $d->{fs}->run);
          $d->{entry}->set_text($d->{savename});
          $d->{fs}->destroy;
          delete($d->{fs}) }, $data);
  $hbox->pack_end ($browse, FALSE, TRUE, 2);

  my $apply = Gtk2::Button->new_from_stock("gtk-execute");
  $apply->signal_connect ("clicked",
    sub { my (undef, $d) = @_;
          if (!open (OUT, ">", $d->{savename})) {
            $::statusbar->push ($::sbi++, "can't open csv file for writing");
            return;
          }
          print OUT
            db::writetable($d->{setting}->{separator}->{value},
                           $d->{setting}->{firstline}->{value} ?
                             $d->{colnames}: undef,
                           @{$d->{table}});
          close (OUT);
        }, $data);
  $vbox->pack_start ($apply, FALSE, FALSE, 2);

  return ($vbox);
}

sub averaging {
  my ($data) = @_;
  my @table = @{$data->{raw}};

  # compute average
  $#{$data->{table}} = -1;
  foreach my $i (0..$#table) {
    foreach my $j (0..$#{$table[0]}) {
      ${$data->{table}}[$j] += $table[$i][$j]
    }
  }
  foreach my $j (0..$#{$table[0]}) { ${$data->{table}}[$j] /= ($#table+1) }
}

sub value {
  my ($data) = @_;

  # vertical box for alignment
  my $vbox = Gtk2::VBox->new (FALSE, 0);

  # compute average
  return ($vbox) if (!($data->{colnames}));

  # for each variable
  my $i = 0;
  foreach my $var (split(/:/, $data->{colnames})) {
    my $hbox = Gtk2::HBox->new (FALSE, 0);
    $vbox->pack_start ($hbox, FALSE, FALSE, 0);
    my $name = Gtk2::Label->new ($var);
    $hbox->pack_start ($name, FALSE, FALSE, 2);
    my $value = Gtk2::Label->new (${$data->{table}}[$i++]);
    $hbox->pack_end ($value, FALSE, FALSE, 2);
  }

  return ($vbox);
}

sub histogram {
  my ($data) = @_;
  my @table = @{$data->{raw}};
  my $nbbins = $data->{setting}->{nbbins}->{value};
  use vars qw/@min @max/;

  # compute min and max
  $#min = $#max = -1;
  foreach my $j (0..$#{$table[0]}) {
    $min[$j] = $max[$j] = $table[0][$j];
    foreach my $i (0..$#table) {
      $min[$j] = $table[$i][$j] if ($min[$j] > $table[$i][$j]);
      $max[$j] = $table[$i][$j] if ($max[$j] < $table[$i][$j]);
    }
  }

  # init histogram
  my @hist; $#hist = -1; $#hist = $nbbins-1;
  foreach my $i (0..$nbbins-1) {
    $#{$hist[$i]} = -1;
    $#{$hist[$i]} = $#{$table[0]};
  }

  # commpute histogram
  foreach my $i (0..$#table) {
    foreach my $j (0..$#{$table[0]}) {
      my $k = POSIX::floor (($table[$i][$j] - $min[$j]) * $nbbins / ($max[$j] - $min[$j]));
      $k = $nbbins-1 if ($k == $nbbins);
      $hist[$k][$j]++;
    }
  }

  # normalize histogram
  foreach my $i (0..$nbbins-1) {
    foreach my $j (0..$#{$table[0]}) {
      $hist[$i][$j] /= $#table + 1;
      $hist[$i][$j] *= 100;
    }
  }

  # create data for plotting
  $#{$data->{table}} = $#{$data->{xtable}} = -1;
  foreach my $k (0...2*$nbbins+1) {
    $#{$data->{table}[$k]} = $#{$data->{xtable}[$k]} = -1;
  }
  my $l = 0;
  foreach my $k (0...$nbbins-1) {

    # first point
    if ($k == 0) {
      foreach my $j (0..$#{$table[0]}) {
        $data->{table}[$l][$j] = 0;
        $data->{xtable}[$l][$j] = $min[$j];
      }
      $l++;
    }

    # step points
    foreach my $j (0..$#{$table[0]}) {
      $data->{table}[$l][$j] = $hist[$k][$j];
      $data->{table}[$l+1][$j] = $hist[$k][$j];
      $data->{xtable}[$l][$j] =
        $min[$j] + $k*($max[$j]-$min[$j])/$nbbins;
      $data->{xtable}[$l+1][$j] =
        $min[$j] + ($k+1)*($max[$j]-$min[$j])/$nbbins;
    }
    $l += 2;

    # last point
    if ($k == $nbbins-1) {
      foreach my $j (0..$#{$table[0]}) {
        $data->{table}[$l][$j] = 0;
        $data->{xtable}[$l][$j] = $max[$j];
      }
      $l++;
    }
  }
}

sub adapthist {
  my ($data) = @_;
  my @table = @{$data->{raw}};
  my $nbbins = $data->{setting}->{nbbins}->{value};
  use vars qw/@x @y/;
  $#x = $#y = -1;

  # check number of bins
  $nbbins = $#table if ($nbbins > $#table);

  # commpute histogram
  foreach my $j (0..$#{$table[0]}) {

    # copy curve
    my @curve;
    $#curve = -1;
    foreach my $i (0..$#table) { $curve[$i] = $table[$i][$j] }
    @curve =  sort { $a <=> $b } @curve;
    my $length = $curve[$#curve] - $curve[0];

    ## fill bins
    use vars qw/$bin $k/;
    $#{$x[$j]} = $#{$y[$j]} = -1;
    $k = $bin = 0;
    $x[$j][0] = $curve[0];
    foreach my $i (0..$#curve) {
      $bin++;
      if ($i >= ($k+1)*($#curve + 1)/$nbbins) {
        $x[$j][++$k] = ($curve[$i-1] + $curve[$i]) / 2;
        # normalize histogram + weight for unequal bin
        $y[$j][$k] = $length*$bin/(($#curve+1)*($x[$j][$k]-$x[$j][$k-1]));
        $bin = 0;
      }
    }

    # last point
    $bin++;
    $x[$j][++$k] = $curve[$#curve];
    $y[$j][$k] = $length*$bin/(($#curve+1)*($x[$j][$k]-$x[$j][$k-1]));
  }


  # create data for plotting
  $#{$data->{table}} = $#{$data->{xtable}} = -1;
  foreach my $k (0...2*$nbbins+1) {
    $#{$data->{table}[$k]} = $#{$data->{xtable}[$k]} = -1;
  }
  my $l = 0;
  foreach my $k (0...$nbbins-1) {

    # first point
    if ($k == 0) {
      foreach my $j (0..$#{$table[0]}) {
        $data->{table}[$l][$j] = 0;
        $data->{xtable}[$l][$j] = $x[$j][0];
      }
      $l++;
    }

    # step points
    foreach my $j (0..$#{$table[0]}) {
      $data->{table}[$l][$j] = $data->{table}[$l+1][$j] =
        100 * $y[$j][$k+1];
      $data->{xtable}[$l][$j] = $x[$j][$k];
      $data->{xtable}[$l+1][$j] = $x[$j][$k+1];
    }
    $l += 2;

    # last point
    if ($k == $nbbins-1) {
      foreach my $j (0..$#{$table[0]}) {
        $data->{table}[$l][$j] = 0;
        $data->{xtable}[$l][$j] = $x[$j][$k+1];
      }
      $l++;
    }
  }
}

sub graph {
  my ($data) = @_;

  # look for graph bounds
  use vars qw/$xmin $xmax $ymin $ymax/;

  # x bounds
  if ($#{$data->{xtable}} > -1) {
    $data->{xmin} = $data->{xmax} = ${$data->{xtable}}[0][0];
    foreach my $i (0..$#{$data->{xtable}}) {
      foreach my $j (0..$#{$data->{xtable}[0]}) {
        $data->{xmin} = ${$data->{xtable}}[$i][$j]
          if ($data->{xmin} > ${$data->{xtable}}[$i][$j]);
        $data->{xmax} = ${$data->{xtable}}[$i][$j]
          if ($data->{xmax} < ${$data->{xtable}}[$i][$j]);
      }
    }
  } else {
    $#{$data->{xtable}} = -1;
    foreach my $i (0..$#{$data->{table}}) {
      $#{$data->{xtable}[$i]} = -1;
      foreach my $j (0..$#{$data->{table}[0]}) {
        $data->{xtable}[$i][$j] = $i;
      }
    }
    $data->{xmin} = 0;
    $data->{xmax} = $#{$data->{xtable}};
  }

  # y bounds
  $data->{ymin} = $data->{ymax} = ${$data->{table}}[0][0];
  foreach my $i (0..$#{$data->{table}}) {
    foreach my $j (0..$#{$data->{table}[0]}) {
      $data->{ymin} = ${$data->{table}}[$i][$j]
        if ($data->{ymin} > ${$data->{table}}[$i][$j]);
      $data->{ymax} = ${$data->{table}}[$i][$j]
        if ($data->{ymax} < ${$data->{table}}[$i][$j]);
    }
  }

  # Check for flat curve
  if ($data->{xmax} <= $data->{xmin}) {
    $data->{xmin} += 1/2;
    $data->{xmin} -= 1/2;;
  }
  if ($data->{ymax} <= $data->{ymin}) {
    $data->{ymin} += 1/2;
    $data->{ymin} -= 1/2;;
  }

  # Create a table for placing the ruler and the drawing area
  my $table = new Gtk2::Table (2, 2, FALSE);

  # Create the drawing area.
  my $area = new Gtk2::DrawingArea;
  $area->size ($xsize, $ysize);
  $table->attach ($area,
                  1, 2,                         1, 2,
                  ['expand', 'fill'], ['expand', 'fill'],
                  0,                            0);
  $area->set_events (['pointer_motion_mask', 'pointer_motion_hint_mask']);
  $area->signal_connect (expose_event => sub {
    my ($area, $event, $data) = @_;

    # scaling factor
    my ($xsize, $ysize) = $area->window->get_size;
    my $xscale = $xsize / ($data->{xmax} - $data->{xmin});
    my $yscale = $ysize / ($data->{ymax} - $data->{ymin});

    # draw curves
    foreach my $j (0..$#{$data->{table}[0]}) {

      # graphic context
      my $gc = Gtk2::Gdk::GC->new ($area->window);
      my $color = $colordefs[$j % ($#colordefs+1)];
      $gc->set_rgb_fg_color ($color);

      my @points; $#points = -1;
      foreach my $i (0..$#{$data->{table}}) {
        my $x = ($data->{xtable}[$i][$j] - $data->{xmin}) * $xscale;
        my $y = $ysize - ($data->{table}[$i][$j] - $data->{ymin}) * $yscale;
        push (@points, $x, $y);
      }
      $area->window->draw_lines ($gc, @points);
    }
  }, $data);

  # The horizontal ruler goes on top. As the mouse moves across the
  # drawing area, a motion_notify_event event is propagated to the
  # ruler so that the ruler can update itself properly.
  my $hrule = new Gtk2::HRuler;
  $hrule->set_metric ('pixels');
  $hrule->set_range ($data->{xmin}, $data->{xmax},
                     ($data->{xmax}-$data->{xmin})/10,
                     $xsize);
  $area->signal_connect (motion_notify_event => sub { $hrule->event ($_[1]) });
  $table->attach ($hrule,
                  1, 2,                         0, 1,
                  ['expand', 'shrink', 'fill'], [],
                  0,                            0 );

  # The vertical ruler goes on the left. As the mouse moves across the
  # drawing area, a motion_notify_event event is propagated to the
  # ruler so that the ruler can update itself properly.
  my $vrule = new Gtk2::VRuler;
  $vrule->set_metric ('pixels');
  $vrule->set_range ($data->{ymax}, $data->{ymin},
                     -($data->{ymax}-$data->{ymin})/10,
                     $ysize);
  $area->signal_connect (motion_notify_event => sub { $vrule->event ($_[1]) });
  $table->attach ($vrule,
                  0, 1,                         1, 2,
                  [], ['fill', 'expand', 'shrink'],
                  0,                            0 );

  return ($table);
}

1;
