package db;

use strict;

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

use Gtk2;
use Data::Dumper;

# read raw data and convert it to table
sub readtable {
  my ($raw, $separator, $firstline) = @_;

  # init data
  use vars qw/@table $nbcols @colnames/;
  $#table = $#colnames = -1;
  my $nbrows = 0;

  # for each row
  my $special = 1;
  foreach my $row (split(/\n/, $raw)) {
    my @array = split($separator, $row);
    for my $n (0..$#array)
      { $array[$n] = "NaN" if (!length ($array[$n])) }

    # special processing for fisrt line
    if ($special) {
      $special = 0;
      if ($firstline) {
        push (@colnames, @array);
        next;
      } else {
        for my $n (0..$#array)
          { push (@colnames, "var$n") }
      }
    }

    # store data into table
    ##push (@table, @array);
    my $j = 0;
    $#{$table[$nbrows]} = -1;
    foreach my $val (@array) { $table[$nbrows][$j++] = $val }
    $nbrows++;
  }
  $nbcols = $#colnames + 1;

  # status
  $::statusbar->push ($::sbi++, "Read $nbrows x $nbcols");
  return ($nbrows, $nbcols, join(":", @colnames), @table);
}

# write a CSV table into a raw string
sub writetable {
  my ($separator, $firstline, @table) = @_;

  # init data
  my @raw;
  $#raw = -1;

  # fisrt line
  if ($firstline) {
    $firstline =~ s/:/$separator/g;
    push (@raw, $firstline);
  }

  # for each row
  foreach my $row (@table) { push (@raw, join ($separator, @{$row})) }

  # create raw string
  return (join ("\n", @raw));
}

# open db
sub opendb {
  # status
  $::statusbar->push ($::sbi++, "");

  # file selector
  my $fs = Gtk2::FileSelection->new ('Open Database');
  # tell it to forbit multiple selections...
  $fs->set_select_multiple (0);
  my $dbname = $fs->get_filename if ('ok' eq $fs->run);
  $fs->destroy;
  return if (!defined($dbname));

  # check if the file exists
  if (!open (IN, "<", $dbname)) {
    $::statusbar->push ($::sbi++, "can't find file '$dbname'");
    return;
  }

  # read all the file
  my @f; $#f = -1;
  foreach my $line (<IN>) { push (@f, $line) }
  my $raw = join("", @f);
  close (IN);

  # create box
  my ($name) = ($dbname =~ /[\/\\]/) ? ($dbname=~/.*[\/\\]([^\/\\]+)/) : $dbname;
  my $item = box::createbox($name, 'pixmaps/database.png', 'DB');
  $item->{name} = $name;

  # add box into list
  push (@::dblist, $item);

  # active menu and button
  $menu::bar->get_widget('/File/Close Database')->set_sensitive(TRUE);
  $::process_db_button->set_sensitive(TRUE);
  $::dbselected = $#::dblist;

  # parameters
  $item->{setting} =
    {separator => {value => ",", label => "Field separator", type => "entry", length => 1},
     firstline => {value => TRUE, label => "First line", type => "checkbutton"}};
  $item->{data} =
    { raw => $raw, show => \&db::list, preprocess => sub {},
      postprocess => sub {
        my ($item) = @_;
        $#{$item->{data}->{table}} = -1;
        ($item->{data}->{nbrows}, $item->{data}->{nbcols},
         $item->{data}->{colnames}, @{$item->{data}->{table}}) =
           readtable($item->{data}->{raw},
                     $item->{setting}->{separator}->{value},
                     $item->{setting}->{firstline}->{value});
        # propagate colnames
        my $first = $item; my $colnames = $item->{data}->{colnames};
        do {
          $colnames = "var" if (defined($first->{nbvars})); ##TEST##
          $first->{data}->{colnames} = $colnames;
          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));
      }
    };
  # convert data into table
  $item->{data}->{postprocess} ($item);
}

# close selected db
sub closedb {
  return if ($::dbselected == -1);
  my $item = $::dblist[$::dbselected];

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

  # destroy box
  delete ($::dblist[$::dbselected]);
  $item->destroy();

  # 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($_)) }
  $::dbselected = -1;

  # need  to unlink results
}

sub process {
  my ($type, $item) = (@_);
  my $colnames = "";

  # statua message
  my $msg = "Process:";
  $::statusbar->pop ($::sbi = 0);
  $::statusbar->push ($::sbi++, $msg);
  while (defined($item)) {

    # status message
    $msg = $msg." ".$item->{type};
    $::statusbar->push ($::sbi++, $msg);

    # process arrow
    if ($item->{type} eq 'ARROW') {
      last if (!($item->{setting}));

      my @table; $#table = -1;
      foreach my $i (0..$#{$item->{from}->{data}->{table}}) {
        $#{$table[$i]} = -1; my $j = 0; my $l = 0;
        $colnames = $item->{from}->{data}->{colnames};
        foreach my $name (split(":", $colnames)) {
          my $val = ${$item->{from}->{data}->{table}}[$i][$l++];
          $table[$i][$j++] = $val if ($item->{setting}->{$name}->{value})
        }
      }
      @{$item->{to}->{data}->{raw}} = @table;
      $item->{to}->{data}->{nbrows} = $item->{from}->{data}->{nbrows};
    }

    # process tool
    elsif ($item->{type} eq 'TOOL') {

      # save data
      $colnames = $item->{data}->{colnames};
      if (!open (OUT, ">", $::tmpcsv)) {
        $::statusbar->push ($::sbi++, "can't open temporary csv file for writing");
        return;
      }
      print OUT writetable(",", undef, @{$item->{data}->{raw}});
      close (OUT);

      # create octave script
      my @octscript; $#octscript = -1;
      push (@octscript, "# -*- Octave -*-");
      push (@octscript, "cd '".$::dbprocessdir."';");
      push (@octscript, "workbench = '".$::tmpwb."';");
      push (@octscript, "name = '".$::tmpcsv."';");
      push (@octscript, "load(workbench);") if ($type eq 'TOOL');
      push (@octscript, "output = data = csvread(name);");
      foreach my $var (keys %{$item->{setting}}) {
        push (@octscript, "$var = ".$item->{setting}->{$var}->{value}.";")
      }
      push (@octscript, "$item->{data}{script};");
      push (@octscript, "csvwrite(name, output);");
      push (@octscript, "save(workbench);") if ($type eq 'DB');

      # run octave script
      if (!open (OUT, ">", $::tmpoct)) {
        $::statusbar->push ($::sbi++, "can't open temporary oct file for writing");
        return;
      }
      print OUT join("\n", @octscript), "\n";
      close (OUT);
      system ($::octave, "--silent", $::tmpoct);

      # load data
      if (!open (IN, "<", $::tmpcsv)) {
        $::statusbar->push ($::sbi++, "can't open temporary csv file for reading");
        return;
      }

      # read all the file
      my @f; $#f = -1;
      foreach my $line (<IN>) { push (@f, $line) }
      close (IN);

      # convert raw into table
      ($item->{data}->{nbrows}, undef, undef, @{$item->{data}->{table}}) =
         readtable(join("", @f), ",", FALSE);
    }

    # process tool
    elsif ($item->{type} eq 'DISPLAY') {

      $item->{data}->{process}($item->{data});

    }

    # statua message
    $::statusbar->push ($::sbi++, $msg);

    # go to next item
    $item = ($item->{type} eq 'ARROW') ? $item->{to} :
      ((defined($item->{to})) ? $item->{to}->{arrow} : undef);
  }

  # special stuf for db process
  if ($type eq 'DB') {
    for (0..$#::toolslist) {
      if ((defined($::toolslist[$_])) &&
           (!defined($::toolslist[$_]->{from}))) {
        $::toolslist[$_]->{data}->{colnames} = $colnames;

        # propagate colnames
        my $first = $::toolslist[$_];
        do {
          $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));
      }
    }
  }

  return TRUE;
}

sub list {
  my ($data) = @_;
  return if (!($data->{colnames}));

  # array of colonm names
  my @array;
  $#array = -1;
  foreach my $col (split(/:/, $data->{colnames}))
    { push (@array, "$col", 'text') }

  # Simple list
  my $slist = Gtk2::SimpleList->new (@array);
  $slist->set_data_array ($data->{table}) if ($data->{table});
  foreach my $j (0..$data->{nbcols}-1)
    { $slist->set_column_editable ($j, TRUE) }
  $slist->set_rules_hint (TRUE);
  #$slist->set_reorderable (TRUE);
  map { $_->set_resizable (TRUE) } $slist->get_columns;

  # scroll window
  my $scrollwin = Gtk2::ScrolledWindow->new;
  $scrollwin->set_shadow_type ('etched-in');
  $scrollwin->set_policy ('automatic', 'automatic');
  $scrollwin->add ($slist);

  return ($scrollwin);
}

1;
