use Test::More qw(no_plan);

BEGIN {
  use_ok( 'Set::IntSpan::Fast' );
}

sub brute_force_card_in_range {
  my $lo   = shift;
  my $hi   = shift;
  my $card = 0;
  for my $el ( @_ ) {
    $card++ if $el >= $lo && $el <= $hi;
  }
  return $card;
}

{
  srand( 1 );
  my @members = ();
  my @sets = map { Set::IntSpan::Fast->new() } ( 1 .. 7 );

  my $next = int( rand( 100 ) );
  for ( 1 .. 40 ) {
    push @members, $next;

    # Distribute among sets
    $sets[ $next % @sets ]->add( $next );
    $next += int( sqrt( rand( 5 ) ) ) + 1;
  }

  my $set = Set::IntSpan::Fast->new();
  $set->add( @members );
  my @got = ();
  for my $i ( $members[0] - 1 .. $members[-1] + 1 ) {
    push @got, $i if $set->contains( $i );
  }

  is_deeply( \@got, \@members, 'contains' );

  is( $set->cardinality(), scalar( @members ), 'cardinality' );

  # Test the cardinality of various ranges
  my $min    = $members[0];
  my $max    = $members[-1];
  my $stride = int( ( $max - $min ) / 5 );
  for ( my $lo = $min - $stride * 2; $lo <= $max + $stride * 2; $lo++ )
  {
    $hi = $lo + $stride;
    my $want = brute_force_card_in_range( $lo, $hi, @members );
    my $got = $set->cardinality( $lo, $hi );
    is( $got, $want, "cardinality for $lo to $hi OK" );
  }

  my $copy = $set->copy();
  isa_ok $copy, 'Set::IntSpan::Fast';
  @orig = $set->as_array();
  @copy = $copy->as_array();

  is_deeply( \@copy, \@orig, 'copy' );

  # Union
  my $first  = shift @sets;
  my $merged = $first->union( @sets );
  isa_ok $merged, 'Set::IntSpan::Fast';
  @got = $merged->as_array();
  is_deeply( \@got, \@members, 'union' );

  # Intersection
  for ( @sets ) {
    $_->merge( $first );
  }

  my $inter = $sets[0]->intersection( @sets[ 1 .. $#sets ] );

  my @common = $inter->as_array();
  my @first  = $first->as_array();
  is_deeply( \@common, \@first, 'intersection' );
}

{
  my @s1  = ( 1, 2, 3, 4, 5, 6 );
  my @s2  = ( 2, 4, 6, 7, 8, 9 );
  my @xor = ( 1, 3, 5, 7, 8, 9 );

  my $set1 = Set::IntSpan::Fast->new();
  $set1->add( @s1 );
  my $set2 = Set::IntSpan::Fast->new();
  $set2->add( @s2 );
  my @got = $set1->xor( $set2 )->as_array();
  is_deeply( \@got, \@xor, 'xor' );
}

{
  my @s1 = ( 1, 2, 3, 4, 5, 6 );
  my @s2 = ( 2, 4, 6, 7, 8, 9 );
  my @diff = ( 1, 3, 5 );

  my $set1 = Set::IntSpan::Fast->new();
  $set1->add( @s1 );
  my $set2 = Set::IntSpan::Fast->new();
  $set2->add( @s2 );
  my @got = $set1->diff( $set2 )->as_array();
  is_deeply( \@got, \@diff, 'diff' );
}

{
  my @sets = ();
  for ( 0 .. 3 ) {
    my $s = Set::IntSpan::Fast->new();
    $s->add( 1, 3, 5, 7, 9 );
    $s->add_range( 100, 1_000_000 );
    push @sets, $s;
  }

  ok( $sets[0]->equals( @sets[ 1 .. $#sets ] ), 'equal' );
  for ( 0 .. 3 ) {
    $sets[$_]->add( 6 );
    my $eq = $sets[0]->equals( @sets[ 1 .. $#sets ] );
    $eq = !$eq unless $_ == 3;
    ok( $eq, "equal $_" );
  }

  # Equal sets are supersets and subsets of each other
  ok( $sets[0]->superset( $sets[1] ), 'superset equal' );
  ok( $sets[0]->subset( $sets[1] ),   'subset equal' );

  $sets[0]->add( 11 );
  ok( $sets[0]->superset( $sets[1] ), 'superset bigger' );
  ok( !$sets[0]->subset( $sets[1] ),  'subset bigger' );
}

{
  my @sets = map { Set::IntSpan::Fast->new(); } ( 1 .. 3 );
  ok( $sets[0]->equals( $sets[1] ),        'empty sets equal' );
  ok( $sets[0]->equals( @sets[ 1 .. 2 ] ), 'three empty sets equal' );
  $sets[0]->add( 0 );
  ok( !$sets[0]->equals( $sets[1] ),        'sets not equal' );
  ok( !$sets[0]->equals( @sets[ 1 .. 2 ] ), 'three sets not equal 1' );
  ok( !$sets[2]->equals( @sets[ 0 .. 1 ] ), 'three sets not equal 2' );
  ok( !$sets[1]->equals( @sets[ 0, 2 ] ), 'three sets not equal 3' );
  $sets[1]->add( 0 );
  ok( !$sets[0]->equals( @sets[ 1 .. 2 ] ), 'three sets not equal 4' );
  ok( !$sets[2]->equals( @sets[ 0 .. 1 ] ), 'three sets not equal 5' );
  ok( !$sets[1]->equals( @sets[ 0, 2 ] ), 'three sets not equal 6' );
  $sets[2]->add( 0 );
  ok( $sets[0]->equals( @sets[ 1 .. 2 ] ), 'three sets equal 1' );
  ok( $sets[2]->equals( @sets[ 0 .. 1 ] ), 'three sets equal 2' );
  ok( $sets[1]->equals( @sets[ 0, 2 ] ), 'three sets equal 3' );
}

{
  my $set = Set::IntSpan::Fast->new();
  is( $set->as_string(), '', 'empty as_string' );
  $set->add( 1 );
  is( $set->as_string(), '1', 'single element' );
  $set->add_range( 3, 1_000_000 );
  is( $set->as_string(), '1,3-1000000', 'range' );
}

{
  my $set = Set::IntSpan::Fast->new();
  ok( !$set->contains_any( 0 ), 'empty contains_any' );
  $set->add( 3 );
  ok( !$set->contains_any( 0, 2, 4, 6 ), 'false contains_any' );
  $set->add_range( -3, 3 );
  ok( $set->contains_any( -4, 0 ), 'range contains_any' );
}
