use Mojo::Base -strict;

use Test::More;
use Mojo::Transaction::WebSocket;
use Mojo::WebSocket
  qw(WS_BINARY WS_CLOSE WS_CONTINUATION WS_PING WS_PONG WS_TEXT),
  qw(build_frame parse_frame);

# Simple text frame roundtrip
my $bytes = build_frame 0, 1, 0, 0, 0, WS_TEXT, 'whatever';
is $bytes, "\x81\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
my $frame = parse_frame \(my $dummy = $bytes), 262144;
is $frame->[0], 1,          'fin flag is set';
is $frame->[1], 0,          'rsv1 flag is not set';
is $frame->[2], 0,          'rsv2 flag is not set';
is $frame->[3], 0,          'rsv3 flag is not set';
is $frame->[4], 1,          'text frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 0, 0, 0, 1, 'whatever'), $bytes, 'frames are equal';

# Simple ping frame roundtrip
$bytes = build_frame 0, 1, 0, 0, 0, WS_PING, 'whatever';
is $bytes, "\x89\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,          'fin flag is set';
is $frame->[1], 0,          'rsv1 flag is not set';
is $frame->[2], 0,          'rsv2 flag is not set';
is $frame->[3], 0,          'rsv3 flag is not set';
is $frame->[4], 9,          'ping frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 0, 0, 0, 9, 'whatever'), $bytes, 'frames are equal';

# Simple pong frame roundtrip
$bytes = build_frame 0, 1, 0, 0, 0, WS_PONG, 'whatever';
is $bytes, "\x8a\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,          'fin flag is set';
is $frame->[1], 0,          'rsv1 flag is not set';
is $frame->[2], 0,          'rsv2 flag is not set';
is $frame->[3], 0,          'rsv3 flag is not set';
is $frame->[4], 10,         'pong frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 0, 0, 0, 10, 'whatever'), $bytes, 'frames are equal';

# Simple text frame roundtrip with all flags set
$bytes = build_frame 0, 1, 1, 1, 1, 1, 'whatever';
is $bytes, "\xf1\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,          'fin flag is set';
is $frame->[1], 1,          'rsv1 flag is set';
is $frame->[2], 1,          'rsv2 flag is set';
is $frame->[3], 1,          'rsv3 flag is set';
is $frame->[4], 1,          'text frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 1, 1, 1, 1, 'whatever'), $bytes, 'frames are equal';

# Simple text frame roundtrip without FIN bit
$bytes = build_frame 0, 0, 0, 0, 0, 1, 'whatever';
is $bytes, "\x01\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 0,          'fin flag is not set';
is $frame->[1], 0,          'rsv1 flag is not set';
is $frame->[2], 0,          'rsv2 flag is not set';
is $frame->[3], 0,          'rsv3 flag is not set';
is $frame->[4], 1,          'text frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 0, 0, 0, 0, 1, 'whatever'), $bytes, 'frames are equal';

# Simple text frame roundtrip with RSV1 flags set
$bytes = build_frame(0, 1, 1, 0, 0, 1, 'whatever');
is $bytes, "\xc1\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,          'fin flag is set';
is $frame->[1], 1,          'rsv1 flag is set';
is $frame->[2], 0,          'rsv2 flag is not set';
is $frame->[3], 0,          'rsv3 flag is not set';
is $frame->[4], 1,          'text frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 1, 0, 0, 1, 'whatever'), $bytes, 'frames are equal';

# Simple continuation frame roundtrip with RSV2 flags set
$bytes = build_frame(0, 1, 0, 1, 0, WS_CONTINUATION, 'whatever');
is $bytes, "\xa0\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,          'fin flag is set';
is $frame->[1], 0,          'rsv1 flag is not set';
is $frame->[2], 1,          'rsv2 flag is set';
is $frame->[3], 0,          'rsv3 flag is not set';
is $frame->[4], 0,          'continuation frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 0, 1, 0, 0, 'whatever'), $bytes, 'frames are equal';

# Simple text frame roundtrip with RSV3 flags set
$bytes = build_frame(0, 1, 0, 0, 1, 1, 'whatever');
is $bytes, "\x91\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,          'fin flag is set';
is $frame->[1], 0,          'rsv1 flag is not set';
is $frame->[2], 0,          'rsv2 flag is not set';
is $frame->[3], 1,          'rsv3 flag is set';
is $frame->[4], 1,          'text frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 0, 0, 1, 1, 'whatever'), $bytes, 'frames are equal';

# Simple binary frame roundtrip
$bytes = build_frame(0, 1, 0, 0, 0, WS_BINARY, 'works');
is $bytes, "\x82\x05\x77\x6f\x72\x6b\x73", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,       'fin flag is set';
is $frame->[1], 0,       'rsv1 flag is not set';
is $frame->[2], 0,       'rsv2 flag is not set';
is $frame->[3], 0,       'rsv3 flag is not set';
is $frame->[4], 2,       'binary frame';
is $frame->[5], 'works', 'right payload';
is $bytes = build_frame(0, 1, 0, 0, 0, 2, 'works'), $bytes, 'frames are equal';

# Masked text frame roundtrip
$bytes = build_frame 1, 1, 0, 0, 0, 1, 'also works';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,            'fin flag is set';
is $frame->[1], 0,            'rsv1 flag is not set';
is $frame->[2], 0,            'rsv2 flag is not set';
is $frame->[3], 0,            'rsv3 flag is not set';
is $frame->[4], 1,            'text frame';
is $frame->[5], 'also works', 'right payload';
isnt(build_frame(0, 1, 0, 0, 0, 2, 'also works'),
  $bytes, 'frames are not equal');

# Masked binary frame roundtrip
$bytes = build_frame(1, 1, 0, 0, 0, 2, 'just works');
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,            'fin flag is set';
is $frame->[1], 0,            'rsv1 flag is not set';
is $frame->[2], 0,            'rsv2 flag is not set';
is $frame->[3], 0,            'rsv3 flag is not set';
is $frame->[4], 2,            'binary frame';
is $frame->[5], 'just works', 'right payload';
isnt(build_frame(0, 1, 0, 0, 0, 2, 'just works'),
  $bytes, 'frames are not equal');

# One-character text frame roundtrip
$bytes = build_frame(0, 1, 0, 0, 0, 1, 'a');
is $bytes, "\x81\x01\x61", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,   'fin flag is set';
is $frame->[1], 0,   'rsv1 flag is not set';
is $frame->[2], 0,   'rsv2 flag is not set';
is $frame->[3], 0,   'rsv3 flag is not set';
is $frame->[4], 1,   'text frame';
is $frame->[5], 'a', 'right payload';
is build_frame(0, 1, 0, 0, 0, 1, 'a'), $bytes, 'frames are equal';

# One-byte binary frame roundtrip
$bytes = build_frame(0, 1, 0, 0, 0, 2, 'a');
is $bytes, "\x82\x01\x61", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,   'fin flag is set';
is $frame->[1], 0,   'rsv1 flag is not set';
is $frame->[2], 0,   'rsv2 flag is not set';
is $frame->[3], 0,   'rsv3 flag is not set';
is $frame->[4], 2,   'binary frame';
is $frame->[5], 'a', 'right payload';
is $bytes = build_frame(0, 1, 0, 0, 0, 2, 'a'), $bytes, 'frames are equal';

# 16-bit text frame roundtrip
$bytes = build_frame(0, 1, 0, 0, 0, 1, 'hi' x 10000);
is $bytes, "\x81\x7e\x4e\x20" . ("\x68\x69" x 10000), 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1, 'fin flag is set';
is $frame->[1], 0, 'rsv1 flag is not set';
is $frame->[2], 0, 'rsv2 flag is not set';
is $frame->[3], 0, 'rsv3 flag is not set';
is $frame->[4], 1, 'text frame';
is $frame->[5], 'hi' x 10000, 'right payload';
is build_frame(0, 1, 0, 0, 0, 1, 'hi' x 10000), $bytes, 'frames are equal';

# 64-bit text frame roundtrip
$bytes = build_frame(0, 1, 0, 0, 0, 1, 'hi' x 200000);
is $bytes, "\x81\x7f\x00\x00\x00\x00\x00\x06\x1a\x80" . ("\x68\x69" x 200000),
  'right frame';
$frame = parse_frame \($dummy = $bytes), 500000;
is $frame->[0], 1, 'fin flag is set';
is $frame->[1], 0, 'rsv1 flag is not set';
is $frame->[2], 0, 'rsv2 flag is not set';
is $frame->[3], 0, 'rsv3 flag is not set';
is $frame->[4], 1, 'text frame';
is $frame->[5], 'hi' x 200000, 'right payload';
is build_frame(0, 1, 0, 0, 0, 1, 'hi' x 200000), $bytes, 'frames are equal';

# Empty text frame roundtrip
$bytes = build_frame(0, 1, 0, 0, 0, 1, '');
is $bytes, "\x81\x00", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,  'fin flag is set';
is $frame->[1], 0,  'rsv1 flag is not set';
is $frame->[2], 0,  'rsv2 flag is not set';
is $frame->[3], 0,  'rsv3 flag is not set';
is $frame->[4], 1,  'text frame';
is $frame->[5], '', 'no payload';
is build_frame(0, 1, 0, 0, 0, 1, ''), $bytes, 'frames are equal';

# Empty close frame roundtrip
$bytes = build_frame(0, 1, 0, 0, 0, WS_CLOSE, '');
is $bytes, "\x88\x00", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,  'fin flag is set';
is $frame->[1], 0,  'rsv1 flag is not set';
is $frame->[2], 0,  'rsv2 flag is not set';
is $frame->[3], 0,  'rsv3 flag is not set';
is $frame->[4], 8,  'close frame';
is $frame->[5], '', 'no payload';
is build_frame(0, 1, 0, 0, 0, 8, ''), $bytes, 'frames are equal';

# Masked empty binary frame roundtrip
$bytes = build_frame(1, 1, 0, 0, 0, 2, '');
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1,  'fin flag is set';
is $frame->[1], 0,  'rsv1 flag is not set';
is $frame->[2], 0,  'rsv2 flag is not set';
is $frame->[3], 0,  'rsv3 flag is not set';
is $frame->[4], 2,  'binary frame';
is $frame->[5], '', 'no payload';
isnt(build_frame(0, 1, 0, 0, 0, 2, ''), $bytes, 'frames are not equal');

# Size limit
$bytes = build_frame(0, 1, 0, 0, 0, WS_BINARY, 'works');
is $bytes, "\x82\x05\x77\x6f\x72\x6b\x73", 'right frame';
$frame = parse_frame \($dummy = $bytes), 4;
ok $frame, 'true';
ok !ref $frame, 'not a reference';

# Incomplete frame
is parse_frame(\($dummy = "\x82\x05\x77\x6f\x72\x6b"), 262144), undef,
  'incomplete frame';

# Fragmented message
my $fragmented = Mojo::Transaction::WebSocket->new;
my $text;
$fragmented->on(text => sub { $text = pop });
$fragmented->parse_message([0, 0, 0, 0, WS_TEXT, 'wo']);
ok !$text, 'text event has not been emitted yet';
$fragmented->parse_message([0, 0, 0, 0, WS_CONTINUATION, 'r']);
ok !$text, 'text event has not been emitted yet';
$fragmented->parse_message([1, 0, 0, 0, WS_CONTINUATION, 'ks!']);
is $text, 'works!', 'right payload';

# Compressed binary message
my $compressed = Mojo::Transaction::WebSocket->new({compressed => 1});
$frame = $compressed->build_message({binary => 'just works'});
is $frame->[0], 1, 'fin flag is set';
is $frame->[1], 1, 'rsv1 flag is set';
is $frame->[2], 0, 'rsv2 flag is not set';
is $frame->[3], 0, 'rsv3 flag is not set';
is $frame->[4], WS_BINARY, 'binary frame';
ok $frame->[5], 'has payload';
my $payload = $compressed->build_message({binary => 'just works'})->[5];
isnt $frame->[5], $payload, 'different payload';
ok length $frame->[5] > length $payload, 'payload is smaller';
my $uncompressed = Mojo::Transaction::WebSocket->new;
my $frame2 = $uncompressed->build_message({binary => 'just works'});
is $frame2->[0], 1, 'fin flag is set';
is $frame2->[1], 0, 'rsv1 flag is not set';
is $frame2->[2], 0, 'rsv2 flag is not set';
is $frame2->[3], 0, 'rsv3 flag is not set';
is $frame2->[4], WS_BINARY, 'binary frame';
ok $frame2->[5], 'has payload';
isnt $frame->[5], $frame2->[5], 'different payload';
is $frame2->[5], $uncompressed->build_message({binary => 'just works'})->[5],
  'same payload';

# Compressed fragmented message
my $fragmented_compressed
  = Mojo::Transaction::WebSocket->new({compressed => 1});
$text = undef;
$fragmented_compressed->on(message => sub { $text = pop });
my $compressed_payload
  = $fragmented_compressed->build_message({text => 'just works'})->[5];
ok !$text, 'message event has not been emitted yet';
$fragmented_compressed->parse_message([
  0, 1, 0, 0, WS_TEXT, substr($compressed_payload, 0, 3)
]);
ok !$text, 'message event has not been emitted yet';
$fragmented_compressed->parse_message([
  0, 0, 0, 0, WS_CONTINUATION, substr($compressed_payload, 3, 3)
]);
ok !$text, 'message event has not been emitted yet';
$fragmented_compressed->parse_message([
  1, 0, 0, 0, WS_CONTINUATION, substr($compressed_payload, 6)
]);
is $text, 'just works', 'decoded correctly';

done_testing();
