package box;

use strict;

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

use Gtk2;
use Gnome2::Canvas;
use Gtk2::SimpleList;
use Data::Dumper;
use GD;

# gap
our $gap = 7;

# last box positions
our $pos_x = 0;
our $pos_y = 0;

# last event position for box move
our $box_x = -1;
our $box_y = -1;

# last event postion for line creation
our $line_x = -1;
our $line_y = -1;

# selected box
our $selected = 0;

# createbox
sub createbox {
  my ($name, $pixmapname, $type, $params) = @_;

  # load pixmap image from a png file
  my $pixmap = GD::Image->newFromPng($::dbprocessdir."/".$pixmapname);
  my ($pixmap_w, $pixmap_h) = $pixmap->getBounds();

  # evaluate string size
  # gdMediumBoldFont 7x13
  my ($label_w, $label_h) = (7 * length($name), 13);

  # create a new image
  my $image_w = 5 + (($pixmap_w > $label_w) ? $pixmap_w : $label_w);
  my $image_h = 5 + $pixmap_h + $label_h;
  my $image = new GD::Image($image_w, $image_h);

  # allocate some colors
  my $bgcolor = $image->colorAllocate(255,255,255);
  my $bdcolor = $image->colorAllocate(140, 140, 140);
  my $txcolor = $image->colorAllocate(240,60,90);

  # make the background transparent and interlaced
  $pixmap->transparent($bgcolor);

  # copy pixmap into image
  $image->copy($pixmap, ($image_w-$pixmap_w)/2, 3,
               0, 0 , $pixmap_w-1, $pixmap_h-1);

  # Put a black frame around the picture
  $image->rectangle(0, 0, $image_w-1, $image_h-1, $bdcolor);
  $image->string(gdMediumBoldFont, 3, 3+$pixmap_w, $name, $txcolor);

  # write png file
  if (!open (OUT, ">", $::tmppng)) {
    $::statusbar->push ($::sbi++, "can't open temporary png file for writing");
    return;
  }
  binmode OUT;
  print OUT $image->png;
  close (OUT);

  # load png file into a gtk2 widget
  my $tmppixmap = Gtk2::Image->new_from_file ($::tmppng);
  my ($tmppixmap_w, $tmppixmap_h) = ($tmppixmap->get_pixbuf()->get_width(),
                                     $tmppixmap->get_pixbuf()->get_height());

  # add it to canvas
  my $item = Gnome2::Canvas::Item->new ($::canvas->root,
                                        'Gnome2::Canvas::Pixbuf',
                                        anchor => 'GTK_ANCHOR_NW',
                                        pixbuf => $tmppixmap->get_pixbuf(),
                                        x => $pos_x + $gap, y =>$pos_y + $gap,
                                        width => $tmppixmap_w,
                                        height => $tmppixmap_h);
  $item->raise_to_top;

  # set the freeze flag when we enter or leave the frame
  $item->{type} = $type;
  $item->{to} = $item->{from} = ();
  $item->signal_connect(event => \&processboxevent);

  # next box position
  $pos_x += $gap + $image_w;
  #$pos_y += $gap + $image_h;

  # by defaut desactive menu and button deticated to db
  $menu::bar->get_widget('/File/Close Database')->set_sensitive(FALSE);
  $::process_db_button->set_sensitive(FALSE);
  $::dbselected = -1;

  return $item;
}

# process box event
sub processboxevent {
  my ($item, $event) = @_;

  # preprocess
  $item->{data}->{preprocess} ($item)
    if (defined($item->{data}->{preprocess}));

  # button press event
  if ($event->type eq 'button-press') { \&buttonpress(@_) }
  elsif ($event->type eq 'motion-notify') { \&motionnotify(@_) }
  elsif ($event->type eq 'button-release') { \&buttonrelease(@_) }

}

sub buttonpress {
  my ($item, $event) = @_;
  my $state = $event->state;

  # button functions
  if ($event->button == 1) {
    ($box_x, $box_y) = $event->coords;
    $item->raise_to_top;

  } elsif ($event->button == 2) {
    ($line_x, $line_y) = $event->coords;

  } elsif ($event->button == 3) {
    $menu::context->{item}->{show}->set_sensitive($item->{data}->{colnames});
    $menu::context->{item}->{properties}->set_sensitive($item->{setting});
    $menu::context->popup(undef, undef, undef, undef,
                          $event->button, $event->time);
  }

  # active db menu and button
  if ($item->{type} eq 'DB') {
    $menu::bar->get_widget('/File/Close Database')->set_sensitive(TRUE);
    $::process_db_button->set_sensitive(TRUE);
    for (0..$#::dblist)
      { $::dbselected = $_ if ($::dblist[$_] == $item) }
  } else {
    $menu::bar->get_widget('/File/Close Database')->set_sensitive(FALSE);
    $::process_db_button->set_sensitive(FALSE);
    $::dbselected = -1;
  }
  $selected = $item;

  # active tools button
  $::process_tools_button->set_sensitive(FALSE);
  for (0..$#::toolslist) {
    $::process_tools_button->set_sensitive(TRUE)
      if ((defined($::toolslist[$_])) &&
          (!defined($::toolslist[$_]->{from})));
  }

  return TRUE;
}

sub drawarrow {
  my $arrow =
    Gnome2::Canvas::Item->new ($::canvas->root,
                               'Gnome2::Canvas::Line',
                               fill_color => 'red',
                               'cap-style' => 'projecting',
                               'last_arrowhead', TRUE,
                               'arrow_shape_a', 8.0,
                               'arrow_shape_b', 12.0,
                               'arrow_shape_c', 4.0,
                               'width-units' => 3,
                               points => @_);
  $arrow->lower_to_bottom;

  # setup type and event handler
  $arrow->{type} = "ARROW";
  $arrow->signal_connect(event => \&processarrowevent);

  return $arrow
}


sub movebox {
  my ($item, $event) = @_;

  my ($tmp_x, $tmp_y) = $event->coords;

  # avoid losing control on widget when going outside scrolling zone
  my (undef, undef, $canvas_w, $canvas_h) = $::canvas->get_scroll_region();
  my ($item_l, $item_t, $item_r, $item_b) = $item->get_bounds();
  $tmp_x = ($item_l+$tmp_x-$box_x < 0) ? $box_x-$item_l+1 :
    ($item_r+$tmp_x-$box_x > $canvas_w) ? $canvas_w+$box_x-$item_r-1 :
      $tmp_x;
  $tmp_y = ($item_t+$tmp_y-$box_y < 0) ? $box_y-$item_t+1 :
    ($item_b+$tmp_y-$box_y > $canvas_h) ? $canvas_h+$box_y-$item_b-1 :
      $tmp_y;

  # evaluate move
  my ($dep_x, $dep_y) = ($tmp_x-$box_x, $tmp_y-$box_y);
  $item->move($dep_x, $dep_y);
  $box_x = $tmp_x; $box_y = $tmp_y;

  if (defined($item->{to})) {
    $item->{to}->{arrow}->destroy();
    $item->{to}->{points}[0] += $dep_x;
    $item->{to}->{points}[1] += $dep_y;

    # save data from arrow structure
    my $arrow = $item->{to}->{arrow};
    my $data = $arrow->{data};
    my $from = $arrow->{from};
    my $name = $arrow->{name};
    my $setting = $arrow->{setting} if ($arrow->{setting});
    my $to = $arrow->{to};

    # draw arrow
    $arrow = $item->{to}->{arrow} = drawarrow ($item->{to}->{points});

    # restore arrow structure
    $arrow->{data} = $data;
    $arrow->{from} = $from;
    $arrow->{name} = $name;
    $arrow->{setting} = $setting if ($setting);
    $arrow->{to} = $to;
  }

  if (defined($item->{from})) {
    $item->{from}->{arrow}->destroy();
    $item->{from}->{points}[2] += $dep_x;
    $item->{from}->{points}[3] += $dep_y;

    # save data from arrow structure
    my $arrow = $item->{from}->{arrow};
    my $data = $arrow->{data};
    my $from = $arrow->{from};
    my $name = $arrow->{name};
    my $setting = $arrow->{setting} if ($arrow->{setting});
    my $to = $arrow->{to};

    # draw arrow
    $arrow = $item->{from}->{arrow} = drawarrow ($item->{from}->{points});

    # restore arrow structure
    $arrow->{data} = $data;
    $arrow->{from} = $from;
    $arrow->{name} = $name;
    $arrow->{setting} = $setting if ($setting);
    $arrow->{to} = $to;
  }
}

sub motionnotify {
  my ($item, $event) = @_;

  # button functions
  if (($box_x >= 0) && ($box_y >= 0)) {
    movebox (@_);

  } elsif (($line_x >= 0) && ($line_y >= 0)) {
    $item->{line}->destroy()
      if (defined($item->{line}));
    my ($tmp_x, $tmp_y) = $event->coords;

    # avoid losing control on widget when going outside scrolling zone
    my (undef, undef, $canvas_w, $canvas_h) =
      $::canvas->get_scroll_region();
    $tmp_x = ($tmp_x < 0) ? 0 : ($tmp_x > $canvas_w) ? $canvas_w : $tmp_x;
    $tmp_y = ($tmp_y < 0) ? 0 : ($tmp_y > $canvas_h) ? $canvas_h : $tmp_y;

    $item->{line} = drawarrow ([$line_x, $line_y, $tmp_x, $tmp_y]);
    $item->{line}->raise_to_top;
  }

  return TRUE;
}

sub createarrow {
  my ($item, $event) = @_;

  if (defined($item->{line})) {
    my ($x1, $y1, $x2, $y2) = $item->get_bounds();
    $line_x = $x2; $line_y = ($y1 + $y2) / 2;
    $item->{line}->destroy();
  }

  my $dest = $::canvas->get_item_at($event->coords);
  if (defined($dest) && ($dest != $item) &&
      !($dest->{type} eq 'ARROW')) {
    # get destination absices
    my ($x1, $y1, $x2, $y2) = $dest->get_bounds();
    my $tmp_x = $x1; my $tmp_y = ($y1 + $y2) / 2;
    my $arrow = drawarrow ([$line_x, $line_y, $tmp_x, $tmp_y]);
    my $newitem =
      {arrow => $arrow, points => [$line_x, $line_y, $tmp_x, $tmp_y]};

    # destroy arrow to
    if (defined($item->{to})) {
      undef($item->{to}->{arrow}->{to}->{from});
      $item->{to}->{arrow}->destroy();
    }
    # destroy arrow from
    if (defined($dest->{from})) {
      undef($dest->{from}->{arrow}->{from}->{to});
      $dest->{from}->{arrow}->destroy();
    }

    # add arrow reference into boxes
    $item->{to} = $dest->{from} = $newitem;

    # add boxe references into arrow
    $arrow->{from} = $item; $arrow->{to} = $dest;

    # parameters
    $arrow->{data} =
      { show => \&db::list,
        preprocess => sub {
          my ($arrow) = @_;
          undef($arrow->{setting});
          undef($arrow->{order});
          foreach my $name (split(":", $arrow->{from}->{data}->{colnames})) {
            $arrow->{setting}->{$name} =
              {value => TRUE, label => "Colomn $name", type => "checkbutton"};
            push(@{$arrow->{order}}, $name);
          }
        },
        postprocess => sub {
          my ($arrow) = @_;
          my @array; $#array = -1;
          foreach my $name (split(":", $arrow->{from}->{data}->{colnames}))
            { push (@array, $name) if ($arrow->{setting}->{$name}->{value}) }
          my $colnames = join (":", @array);
          if (!($arrow->{to}->{data}->{colnames} =~ $colnames)) {
            $arrow->{to}->{data}->{colnames} = $colnames;
            $arrow->{to}->{data}->{nbcols} = $#array + 1;
            $arrow->{to}->{data}->{nbrows} = $arrow->{from}->{data}->{nbrows};
            #### something more complex has to be done for chained connections
            undef($arrow->{to}->{to}->{arrow}->{setting})
              if (defined($arrow->{to}->{to}));
          }
        }
      };
  }
  $line_x = $line_y = -1;
  undef($item->{line});

  # find first box chained
  my $first = $item;
  while (defined($first->{from})) {
    $first = $first->{from}->{arrow}->{from};
  }

  # propagate colnames
  if (defined($first->{data}->{colnames})) {
    my $colnames = $first->{data}->{colnames};
    do {
      # get last value of colnames
      $colnames = $first->{data}->{colnames}
        if (defined($first->{data}->{colnames}));
      # set colname value
      $colnames = "var" if (defined($first->{nbvars})); ##TEST##
      $first->{data}->{colnames} = $colnames;
      # propagate settings
      if (!defined($first->{to})) { $first = undef; } else {
        my $arrow = $first->{to}->{arrow};
        $arrow->{data}->{preprocess} ($arrow);
        # go to next
        $first = $arrow->{to};
      }
    } while (defined($first));
  }
}

sub buttonrelease{
  my ($item, $event) = @_;

  # button functions
  if ($event->button == 1) {
    $box_x = $box_y = -1;

  } elsif ($event->button == 2) {
    createarrow (@_);
  }

  return TRUE;
}

sub close {
  my ($item, $item) = @_;
  if ($selected->{type} eq 'ARROW') {
    closearrow (@_)
  } else {
    closebox (@_)
  }
}

sub closebox {
  my ($item, $item) = @_;

  # erase process for box

  # destroy arrow to
  if (defined($selected->{to})) {
    undef($selected->{to}->{arrow}->{to}->{from});
    $selected->{to}->{arrow}->destroy();
  }
  # destroy arrow from
  if (defined($selected->{from})) {
    undef($selected->{from}->{arrow}->{from}->{to});
    $selected->{from}->{arrow}->destroy();
  }

  # special for db box
  if ($item->{type} eq 'DB') {
    for (0..$#::dblist) { $::dbselected = $_ if ($::dblist[$_] == $item) }
    delete ($::dblist[$::dbselected]);

    # restore unselected state
    $menu::bar->get_widget('/File/Close Database')->set_sensitive(FALSE);
    $::process_selected_button->set_sensitive(FALSE);
    $::process_all_button->set_sensitive(FALSE);
    foreach (@::dblist) {
      $::process_all_button->set_sensitive(TRUE) if (defined($_));
    }
  }

  # special for tools box
  if ($item->{type} eq 'TOOL') {
    for (0..$#::toolslist) { $::dbselected = $_ if ($::dblist[$_] == $item) }
    delete ($::dblist[$::dbselected]);

  }

  # destroy box
  $selected->destroy();

  # restore unselected state
  $selected = 0;

  # need  to unlink results
}

# process arrow event
sub processarrowevent {
  my ($item, $event) = @_;

  # button press event
  if (($event->type eq 'button-press') && ($event->button == 3)) {
    $menu::context->{item}->{show}->set_sensitive(FALSE);
    $item->{data}->{preprocess} ($item) if (!defined($item->{setting}));
    $menu::context->{item}->{properties}->set_sensitive($item->{setting});
    $menu::context->popup(undef, undef, undef, undef,
                          $event->button, $event->time);
  }
  $selected = $item;

}

# close arrow
sub closearrow {
  my ($item, $item) = @_;

  # erase process for arrow

  # unset arrow to reference
  undef($selected->{to}->{from});

  # unset arrow from reference
  undef($selected->{from}->{to});

  # destroy array
  $selected->destroy();
}

sub property {

  # window db property
  my $setting = Gtk2::Window->new;
  $setting->signal_connect ("delete_event",
                            sub{ my (undef, undef, $w) = @_; $w->destroy(); },
                            $setting);
  $setting->set_title (join(" ", $selected->{name}, "parameter(s)"));
  $setting->set_border_width (2);
  $setting->set_default_size (320, 50);

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

  # a label as title
  my $hbox1 = Gtk2::HBox->new (FALSE, 0);
  $vbox->pack_start ($hbox1, FALSE, FALSE, 5);
  my $label1 = Gtk2::Label->new ("Parameter(s)");
  $hbox1->pack_start ($label1, TRUE, FALSE, 0);

  # temporary hash for storing parameters
  my $tmp = {};

  my @keys = defined($selected->{order}) ?
    @{$selected->{order}} : keys %{$selected->{setting}};
  foreach my $var (@keys) {
    my $param = $selected->{setting}->{$var};
    $tmp->{$var} = $param->{value};

    # box for packing
    my $hbox = Gtk2::HBox->new (FALSE, 0);
    $vbox->pack_start ($hbox, FALSE, FALSE, 0);
    my $label = Gtk2::Label->new ($param->{label});
    $hbox->pack_start ($label, FALSE, FALSE, 2);
    my $field;
    if ($param->{type} eq 'entry') {
      $field = Gtk2::Entry->new_with_max_length ($param->{length});
      $field->set_width_chars ($param->{length});
      $field->set_text ($tmp->{$var});
      $field->signal_connect ("focus_out_event",
        sub{ my ($w, undef, $d) = @_;
             $d->{tmp}->{$d->{var}} = $w->get_text;
             return FALSE },
        {tmp => $tmp, var => $var});
    } elsif ($param->{type} eq 'checkbutton') {
      $field = Gtk2::CheckButton->new ();
      $field->set_active ($tmp->{$var});
      $field->signal_connect ("clicked",
        sub{ my ($w, $d) = @_;
             $d->{tmp}->{$d->{var}} =
               ($w->get_active eq 1) ? TRUE : FALSE;
             return FALSE },
        {tmp => $tmp, var => $var});
    }
    $hbox->pack_end ($field, FALSE, FALSE, 2);
  }

  # action button
  my $frame = Gtk2::Frame->new ();
  $frame->set_shadow_type ("etched-out");
  $vbox->pack_start ($frame, FALSE, FALSE, 5);
  my $hbox2 = Gtk2::HBox->new (FALSE, 0);
  $frame->add ($hbox2);
  my $apply = Gtk2::Button->new_from_stock("gtk-apply");
  $apply->signal_connect ("clicked",
    sub {
      my (undef, $d) = @_;
      foreach my $var (keys %{$d->{tmp}})
        { $d->{item}->{setting}->{$var}->{value} = $d->{tmp}->{$var} }
      $d->{window}->destroy();
      $d->{item}->{data}->{postprocess} ($d->{item});
    },
    {item => $selected, tmp => $tmp, window => $setting});
  $hbox2->pack_end ($apply, TRUE, FALSE, 3);
  my $cancel = Gtk2::Button->new_from_stock("gtk-cancel");
  $cancel->signal_connect ("clicked",
    sub{ my (undef, $w) = @_; $w->destroy(); },
    $setting);
  $hbox2->pack_end ($cancel, TRUE, FALSE, 3);

  # show window
  $setting->show_all ();
}

sub show {

  # dialog
  my $dialog = Gtk2::Dialog->new (join(" ", $selected->{name}, " display"),
                                  $::root, [qw/destroy-with-parent/],
                                  'gtk-close', 'none');
  $dialog->signal_connect (response => sub { $_[0]->destroy });
  $selected->{data}->{dialog} = $dialog;

  # display
  $dialog->vbox->pack_start ($selected->{data}->{show}($selected->{data}),
                             TRUE, TRUE, 0);

  # show window
  $dialog->show_all ();

}

1;
