#!/usr/bin/perl

BEGIN {
   die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n"
      unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH};
   unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib";
};

use strict;
use warnings FATAL => 'all';
use English qw(-no_match_vars);
use Test::More;

use Schema;
use SchemaIterator;
use Quoter;
use DSNParser;
use Sandbox;
use OptionParser;
use TableParser;
use TableNibbler;
use RowChecksum;
use NibbleIterator;
use OobNibbleIterator;
use Cxn;
use PerconaTest;

use constant PTDEBUG => $ENV{PTDEBUG} || 0;

use Data::Dumper;
$Data::Dumper::Indent    = 1;
$Data::Dumper::Sortkeys  = 1;
$Data::Dumper::Quotekeys = 0;

my $dp  = new DSNParser(opts=>$dsn_opts);
my $sb  = new Sandbox(basedir => '/tmp', DSNParser => $dp);
my $dbh = $sb->get_dbh_for('master');
my $output;

plan skip_all => 'Cannot connect to sandbox master';
if ( !$dbh ) {
   plan skip_all => 'Cannot connect to sandbox master';
}
else {
   plan tests => 17;
}

my $q   = new Quoter();
my $tp  = new TableParser(Quoter=>$q);
my $nb  = new TableNibbler(TableParser=>$tp, Quoter=>$q);
my $o   = new OptionParser(description => 'OobNibbleIterator');
my $rc  = new RowChecksum(OptionParser => $o, Quoter=>$q);

$o->get_specs("$trunk/bin/pt-table-checksum");

my $cxn = new Cxn(
   dbh          => $dbh,
   dsn          => { h=>'127.1', P=>'12345', n=>'h=127.1,P=12345' },
   DSNParser    => $dp,
   OptionParser => $o,
);

my %common_modules = (
   Quoter       => $q,
   TableParser  => $tp,
   TableNibbler => $nb,
   OptionParser => $o,
);
my $in = "/t/lib/samples/NibbleIterator/";

sub make_nibble_iter {
   my (%args) = @_;

   if (my $file = $args{sql_file}) {
      $sb->load_file('master', "$in/$file");
   }

   @ARGV = $args{argv} ? @{$args{argv}} : ();
   $o->get_opts();

   my $schema = new Schema();
   my $si     = new SchemaIterator(
      dbh             => $dbh,
      keep_ddl        => 1,
      keep_tbl_status => 1,
      Schema          => $schema,
      %common_modules,
   );
   1 while $si->next();

   my $ni = new OobNibbleIterator(
      Cxn         => $cxn,
      tbl         => $schema->get_table($args{db}, $args{tbl}),
      chunk_size  => $o->get('chunk-size'),
      chunk_index => $o->get('chunk-index'),
      callbacks   => $args{callbacks},
      select      => $args{select},
      one_nibble  => $args{one_nibble},
      %common_modules,
   );

   return $ni;
}

# ############################################################################
# a-z w/ chunk-size 5, z is final boundary and single value
# ############################################################################
my $ni = make_nibble_iter(
   sql_file => "a-z.sql",
   db       => 'test',
   tbl      => 't',
   argv     => [qw(--databases test --chunk-size 5)],
);

my @rows = ();
for (1..5) {
   push @rows, $ni->next();
}
is_deeply(
   \@rows,
   [['a'],['b'],['c'],['d'],['e']],
   'a-z nibble 1'
) or print Dumper(\@rows);

@rows = ();
for (1..5) {
   push @rows, $ni->next();
}
is_deeply(
   \@rows,
   [['f'],['g'],['h'],['i'],['j']],
   'a-z nibble 2'
) or print Dumper(\@rows);

@rows = ();
for (1..5) {
   push @rows, $ni->next();
}
is_deeply(
   \@rows,
   [['k'],['l'],['m'],['n'],['o']],
   'a-z nibble 3'
) or print Dumper(\@rows);

@rows = ();
for (1..5) {
   push @rows, $ni->next();
}
is_deeply(
   \@rows,
   [['p'],['q'],['r'],['s'],['t']],
   'a-z nibble 4'
) or print Dumper(\@rows);

@rows = ();
for (1..5) {
   push @rows, $ni->next();
}
is_deeply(
   \@rows,
   [['u'],['v'],['w'],['x'],['y']],
   'a-z nibble 5'
) or print Dumper(\@rows);

@rows = ();
for (1..5) {
   push @rows, $ni->next();
}
is_deeply(
   \@rows,
   [['z']],
   'a-z nibble 6'
) or print Dumper(\@rows);

is(
   $ni->nibble_number(),
   8,
   "8 nibbles"
);

ok(
   !$ni->more_boundaries(),
   "No more boundaries"
);

# ############################################################################
# Get lower and upper oob values.
# ############################################################################
$ni = make_nibble_iter(
   sql_file => "a-z.sql",
   db       => 'test',
   tbl      => 't',
   argv     => [qw(--databases test --chunk-size 8)],
);

$dbh->do("delete from test.t where c='a' or c='z'");

@rows = ();
for (1..8) {
   push @rows, $ni->next();
}
is_deeply(
   \@rows,
   [['b'],['c'],['d'],['e'],['f'],['g'],['h'],['i']],
   'a-z nibble 1 with oob'
) or print Dumper(\@rows);

@rows = ();
for (1..8) {
   push @rows, $ni->next();
}
is_deeply(
   \@rows,
   [['j'],['k'],['l'],['m'],['n'],['o'],['p'],['q']],
   'a-z nibble 2 with oob'
) or print Dumper(\@rows);

@rows = ();
for (1..8) {
   push @rows, $ni->next();
}
is_deeply(
   \@rows,
   [['r'],['s'],['t'],['u'],['v'],['w'],['x'],['y']],
   'a-z nibble 3 with oob'
) or print Dumper(\@rows);

# NibbleIterator is done (b-y), now insert a row on the low end (a),
# and one on the high end (z), past what NibbleIterator originally
# saw as the first and last boundaries.  OobNibbleIterator should kick
# in and see a and z.
$dbh->do("insert into test.t values ('a'), ('z')");

# OobNibbleIterator checks the low end first.
@rows = ();
push @rows, $ni->next();
is_deeply(
   \@rows,
   [['a']],
   'a-z nibble 4 lower oob'
) or print Dumper(\@rows);

# Then it checks the high end.
@rows = ();
push @rows, $ni->next();
is_deeply(
   \@rows,
   [['z']],
   'a-z nibble 4 upper oob'
) or print Dumper(\@rows);

ok(
   !$ni->more_boundaries(),
   "No more boundaries"
);

# #############################################################################
# Empty table
# https://bugs.launchpad.net/percona-toolkit/+bug/987393 
# #############################################################################
$sb->load_file('master', "t/pt-table-checksum/samples/empty-table-bug-987393.sql");

$ni = make_nibble_iter(
   db       => 'test',
   tbl      => 'test_empty',
   argv     => [qw(--databases test --chunk-size-limit 0)],
);

@rows = ();
for (1..5) {
   push @rows, $ni->next();
}
is_deeply(
   \@rows,
   [],
   "Empty table"
);

# #############################################################################
# Done.
# #############################################################################
{
   local *STDERR;
   open STDERR, '>', \$output;
   $ni->_d('Complete test coverage');
}
like(
   $output,
   qr/Complete test coverage/,
   '_d() works'
);
$sb->wipe_clean($dbh);
ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
exit;
