#-*- mode: perl;-*-

package NumericNode;

# use Carp::Assert;

our @ISA = qw( Algorithm::SkipList::Node );

sub validate_key {
  my $self = shift;
#  assert( UNIVERSAL::isa($self, "Algorithm::SkipList::Node") ), if DEBUG;

  my $key = shift;
  return ($key =~ /^\-?\d+$/); # make sure key is simple natural number
}

sub key_cmp {
  my $self = shift;
#   assert( UNIVERSAL::isa($self, "Algorithm::SkipList::Node") ), if DEBUG;

  my $left  = $self->key;
  my $right = shift;

  unless (defined $left) { return -1; }

  # Numeric Comparison

  return ($left <=> $right);
}

package main;

use Test::More tests => 264;
use Algorithm::SkipList 0.73;

# We build two lists and merge them

my $f = new Algorithm::SkipList( node_class => 'NumericNode' );
ok( ref($f) eq "Algorithm::SkipList");

foreach my $i (qw( 1 3 5 7 9 )) {
  my $finger = $f->insert($i, $i);
  ok($f->find($i, $finger) == $i);   # test return of fingers from insertion
}
ok($f->size == 5);

$f->merge($f);
ok($f->size == 5);

my $g = new Algorithm::SkipList( node_class => 'NumericNode' );
ok( ref($g) eq "Algorithm::SkipList");

foreach my $i (qw( 2 4 6 8 10 )) {
  $g->insert($i, $i);
}
ok($g->size == 5);

$f->merge($g);
ok($f->size == 10);

# $f->_debug;
# $g->_debug;

foreach my $i (1..10) {
  ok($f->find($i) == $i);
}



# redefine $g

foreach my $i (qw( 2 4 6 8 10 )) {
  $g->insert($i, -$i);
}
ok($g->size == 5);

$g->merge($g);
ok($g->size == 5);

# We want to test that mergine does not overwrite original values

$g->merge($f);
ok($g->size == 10);


foreach my $i (1..10) {
  ok($g->find($i) == (($i%2)?$i:-$i) );
}


{
  my ($k,$v) = $g->least;
  ok($k == 1);
  ok($v == 1);

  ($k, $v) = $g->greatest;
  ok($k == 10);
  ok($v == -10);
}

$f->clear;
ok($f->size == 0);


$f->append($g);
ok($f->size == $g->size);

$f->clear;
ok($f->size == 0);

$f->insert(-1, -1);
$f->insert(-2, 2);

ok($f->size == 2);


$f->append($g);

ok($f->size == 2+$g->size);

foreach my $i (-2..10) {
  ok($f->find($i) == (($i%2)?$i:-$i) ), if ($i);
}

{
  my ($k1,$v1) = $g->greatest;
  my ($k2,$v2) = $f->greatest;
  ok($k1 == $k2);
  ok($v1 == $v2);
}

my $z = $f->copy;
ok($z->size == $f->size);

# if ($z->size != $f->size) {
#   $z->_debug;
#   $f->_debug;
#   $g->_debug;
#   die;
# }

foreach my $i (-2..10) {
  ok($f->find($i) == (($i%2)?$i:-$i) ), if ($i);
  ok($z->find($i) == (($i%2)?$i:-$i) ), if ($i);
}

$z->clear;
ok($z->size == 0);


$z->append( $f->copy );
ok($z->size == $f->size);

foreach my $i (-2..10) {
  ok($z->find($i) == (($i%2)?$i:-$i) ), if ($i);
}

{
  my @keys = $g->keys;
  ok(scalar @keys == $g->size);

  foreach my $i (1..10) {
    ok($i == $keys[$i-1]); }

  ok(scalar $g->first_key == shift @keys);
  while (@keys) { ok($g->next_key == shift @keys); }


  my @vals = $g->values;
  ok(scalar @vals == $g->size);

  foreach my $i (1..10) {
    ok($g->find($i) == $vals[$i-1]); }
}


{
  my $g = new Algorithm::SkipList( node_class => 'NumericNode' );

  foreach (20..29) {
    $g->insert( $_, 1+$g->size );
  }
  
  my $count = $g->size;
  foreach my $key (20..29) {
    ok( defined $g->find($key), "verify key in g" );
    my $h = $g->copy( $key );
    ok( defined $h,           "verify h is defined" );

    ok( $h->size == $count,   "verify size of h" );
    ok( ($h->least)[0] == $key );
    ok( ($h->least)[1] == $g->find($key) );

    if ($key <= 28) {
      my $h = $g->copy( $key, undef, 28 );
      ok( defined $h, "verify h is defined" );

      ok( $h->size == ($count-1), "verify size of h" );
      ok( ($h->least)[0] == $key );
      ok( ($h->least)[1] == $g->find($key) );
    }

    $count--;
  }


  my $h = $g->copy(19);
  ok(! defined $h);

  $h = $g->copy(30);
  ok(! defined $h);
}

{
  foreach my $i (20..29) {
    my $g = new Algorithm::SkipList( node_class => 'NumericNode' );

    foreach (20..29) {
      $g->insert( $_, 1+$g->size );
    }

    my $size = $g->size;

    my $h = $g->truncate($i);
    ok(defined $h);
    ok($size == ($h->size + $g->size));

    my $gn = $g->_greatest_node;
    my $hn = $h->_first_node;

    ok( $hn->key_cmp($i) == 0 ), if ($h->size);

    unless ($gn->isa("Algorithm::SkipList::Header")) {
      ok( $hn->key_cmp($gn->key) == 1 ), if ($gn->key);
    }

    unless ($hn->isa("Algorithm::SkipList::Header")) {
      ok( $gn->key_cmp($hn->key) == -1 ), if ($hn->key);
    }
  }
  
}
