#!/usr/bin/perl -w
# vim: ts=2 sw=2 filetype=perl expandtab

# Exercises Filter::Block without the rest of POE.  Suddenly things
# are looking a lot easier.

use strict;
use lib qw(./mylib ../mylib);
use lib qw(t/10_units/05_filters);

use TestFilter;
use Test::More tests => 34 + $COUNT_FILTER_INTERFACE;

sub POE::Kernel::ASSERT_DEFAULT () { 1 }

BEGIN {
  package POE::Kernel;
  use constant TRACE_DEFAULT => exists($INC{'Devel/Cover.pm'});
}

use_ok("POE::Filter::Block");
test_filter_interface("POE::Filter::Block");

# Test block filter in fixed-length mode.
{
  my $filter = new POE::Filter::Block( BlockSize => 4 );
  isa_ok( $filter, 'POE::Filter::Block' );
  my $raw    = $filter->put( [ "12345678" ] );

  my $cooked = $filter->get( $raw );
  is_deeply($cooked, [ "1234", "5678" ], "get() parses blocks");

  my $reraw = $filter->put( $cooked );
  is_deeply($reraw, [ "12345678" ], "put() serializes blocks");
}

# Test block filter with get_one() functions.
{
  my $filter = new POE::Filter::Block( BlockSize => 4 );
  isa_ok( $filter, 'POE::Filter::Block' );
  my $raw = $filter->put( [ "12345678" ] );

  $filter->get_one_start( $raw );

  my $cooked = $filter->get_one();
  is_deeply($cooked, [ "1234" ], "get_one() parsed one block");

  my $reraw = $filter->put( $cooked );
  is_deeply($reraw, [ "1234" ], "put() serialized one block");
}

# Test block filter in variable-length mode, without a custom codec.
{
  my $filter = new POE::Filter::Block( );
  isa_ok( $filter, 'POE::Filter::Block' );
  my $raw = $filter->put([ "a", "bc", "def", "ghij" ]);

  my $cooked = $filter->get( $raw );
  is_deeply(
    $cooked, [ "a", "bc", "def", "ghij" ],
    "get() parsed variable blocks"
  );

  $cooked = $filter->get( [ "1" ] );
  ok(!@$cooked, "get() doesn't return for partial input 1");

  $cooked = $filter->get( [ "0" ] );
  ok(!@$cooked, "get() doesn't return for partial input 0");

  $cooked = $filter->get( [ "\0" ] );
  ok(!@$cooked, "get() doesn't return for partial input end-of-header");

  $cooked = $filter->get( [ "klmno" ] );
  ok(!@$cooked, "get() doesn't return for partial input payload");

  $cooked = $filter->get( [ "pqrst" ] );
  is_deeply($cooked, [ "klmnopqrst" ], "get() returns payload");

  my $raw_two = $filter->put( [ qw(a bc def ghij) ] );
  is_deeply(
    $raw_two, [ "1\0a", "2\0bc", "3\0def", "4\0ghij" ],
    "variable length put() serializes multiple blocks"
  );
}

# Test block filter in variable-length mode, with a custom codec.
{
  sub encoder {
    my $stuff = shift;
    substr($$stuff, 0, 0) = pack("N", length($$stuff));
    undef;
  }

  sub decoder {
    my $stuff = shift;
    return unless length $$stuff >= 4;
    my $packed = substr($$stuff, 0, 4);
    substr($$stuff, 0, 4) = "";
    return unpack("N", $packed);
  }

  my $filter = new POE::Filter::Block(
    LengthCodec => [ \&encoder, \&decoder ],
  );
  isa_ok( $filter, 'POE::Filter::Block' );

  my $raw = $filter->put([ "a", "bc", "def", "ghij" ]);

  my $cooked = $filter->get( $raw );
  is_deeply(
    $cooked, [ "a", "bc", "def", "ghij" ],
    "customi serializer parsed its own serialized data"
  );

  $cooked = $filter->get( [ "\x00" ] );
  ok(!@$cooked, "custom serializer did not parse partial header 1/4");

  $cooked = $filter->get( [ "\x00" ] );
  ok(!@$cooked, "custom serializer did not parse partial header 2/4");

  $cooked = $filter->get( [ "\x00" ] );
  ok(!@$cooked, "custom serializer did not parse partial header 3/4");

  $cooked = $filter->get( [ "\x0a" ] );
  ok(!@$cooked, "custom serializer did not parse partial header 4/4");

  $cooked = $filter->get( [ "klmno" ] );
  ok(!@$cooked, "custom serializer did not parse partial payload");

  $cooked = $filter->get( [ "pqrst" ] );
  is_deeply(
    $cooked, [ "klmnopqrst" ],
    "custom serializer parsed full payload"
  );

  my $raw_two = $filter->put( [ qw(a bc def ghij) ] );
  is_deeply(
    $raw_two, [
      "\x00\x00\x00\x01a",
      "\x00\x00\x00\x02bc",
      "\x00\x00\x00\x03def",
      "\x00\x00\x00\x04ghij",
    ],
    "custom serializer serialized multiple payloads"
  );
}

# Test param constraints
{
    my $filter = eval {
            new POE::Filter::Block(
                        MaxLength => 10,
                        MaxBuffer => 5 );
        };
    ok( $@, "MaxLength must not exceed MaxBuffer" );
    ok( !$filter, "No object on error" );

    $filter = eval { new POE::Filter::Block( MaxLength => -1 ) };
    ok( $@, "MaxLength must be positive" );

    $filter = eval { new POE::Filter::Block( MaxLength => 'something' ) };
    ok( $@, "MaxLength must be a number" );

    $filter = eval { new POE::Filter::Block( MaxBuffer => 0 ) };
    ok( $@, "MaxBuffer must be positive" );

    $filter = eval { new POE::Filter::Block( MaxBuffer => 'something' ) };
    ok( $@, "MaxBuffer must be a number" );
}

# Test MaxLength
{
    my $filter = new POE::Filter::Block( MaxLength => 10 );
    isa_ok( $filter, 'POE::Filter::Block' );

    my $data = "134\0a bunch of data here"; # partial block
    my $blocks = eval { $filter->get( [ $data ] ) };
    like( $@, qr/block exceeds/, "Block is to large" );
}

# Test MaxBuffer
{
    my $filter = new POE::Filter::Block( MaxBuffer => 10,
                                         MaxLength => 5 );
    isa_ok( $filter, 'POE::Filter::Block' );

    my $data = "134\0a bunch of data here"; # partial block
    my $blocks = eval { $filter->get( [ $data ] ) };
    like( $@, qr/buffer exceeds/, "buffer grew to large" );
}

exit;
