use strict;
use warnings;

use Benchmark qw(cmpthese);
use Sereal;
use JSON::XS ();
use JSON ();
use Google::ProtocolBuffers;
use Google::ProtocolBuffers::Dynamic;

Google::ProtocolBuffers->parsefile('t/proto/person.proto');

my $d;
{
    $d = Google::ProtocolBuffers::Dynamic->new('t/proto');
    $d->load_file("person.proto");
    $d->load_file("person3.proto");
    $d->map_message("test.Person", "DynamicPerson", { decode_blessed => 0 });
    $d->map_message("test.PersonArray", "DynamicPersonArray", { decode_blessed => 0 });
    $d->map_message("test.Person3", "DynamicPerson3", { decode_blessed => 0 });
    $d->map_message("test.Person3Array", "DynamicPerson3Array", { decode_blessed => 0 });
    $d->resolve_references();
}

my $sereal_encoder = Sereal::Encoder->new;
my $sereal_decoder = Sereal::Decoder->new;

sub chars {
    my ($min, $max) = @_;
    my $num = rand($max - $min) + $min;

    return join '', map chr(rand(26) + 97), 1 .. $num;
}

sub random_person {
    my ($id) = @_;

    return {
        id      => $id,
        name    => chars(3, 16) . ' ' . chars(6, 12),
        email   => chars(7, 12) . '@test.com',
    };
}

my $person = random_person(1);
my $pb_person = Test::Person->encode($person);
my $pbd_person = DynamicPerson->encode($person);
my $sereal_person = $sereal_encoder->encode($person);
my $json_person = JSON::encode_json($person);

sub encode_protobuf_pp_one { Test::Person->encode($person); }
sub encode_protobuf_one { DynamicPerson->encode($person); }
sub encode_protobuf3_one { DynamicPerson3->encode($person); }
sub encode_sereal_one { $sereal_encoder->encode($person); }
sub encode_json_one { JSON::encode_json($person); }

sub decode_protobuf_pp_one { Test::Person->decode($pbd_person); }
sub decode_protobuf_upb_one { DynamicPerson->decode_upb($pbd_person); }
sub decode_protobuf3_upb_one { DynamicPerson3->decode_upb($pbd_person); }
sub decode_protobuf3_bbpb_one { DynamicPerson3->decode_bbpb($pbd_person); }
sub decode_sereal_one { $sereal_decoder->decode($sereal_person); }
sub decode_json_one { JSON::from_json($json_person); }

print "\nEncoder\n";
cmpthese(-1, {
    protobuf_pp => \&encode_protobuf_pp_one,
    protobuf    => \&encode_protobuf_one,
    protobuf3   => \&encode_protobuf3_one,
    sereal      => \&encode_sereal_one,
    json        => \&encode_json_one,
});

print "\nDecoder\n";
cmpthese(-3, {
    protobuf_pp     => \&decode_protobuf_pp_one,
    protobuf_upb    => \&decode_protobuf_upb_one,
    protobuf3_upb   => \&decode_protobuf3_upb_one,
    protobuf3_bbpb  => \&decode_protobuf3_bbpb_one,
    sereal          => \&decode_sereal_one,
    json            => \&decode_json_one,
});

my $persons = {
    persons => [map random_person($_), 1 .. 100 ],
};
my $pb_persons = Test::PersonArray->encode($persons);
my $pbd_persons = DynamicPersonArray->encode($persons);
my $sereal_persons = $sereal_encoder->encode($persons);
my $json_persons = JSON::encode_json($persons);

sub encode_protobuf_pp_arr { Test::PersonArray->encode($persons); }
sub encode_protobuf_arr { DynamicPersonArray->encode($persons); }
sub encode_protobuf3_arr { DynamicPerson3Array->encode($persons); }
sub encode_sereal_arr { $sereal_encoder->encode($persons); }
sub encode_json_arr { JSON::encode_json($persons); }

sub decode_protobuf_pp_arr { Test::PersonArray->decode($pbd_persons); }
sub decode_protobuf_upb_arr { DynamicPersonArray->decode_upb($pbd_persons); }
sub decode_protobuf3_upb_arr { DynamicPerson3Array->decode_upb($pbd_persons); }
sub decode_protobuf3_bbpb_arr { DynamicPerson3Array->decode_bbpb($pbd_persons); }
sub decode_sereal_arr { $sereal_decoder->decode($sereal_persons); }
sub decode_json_arr { JSON::from_json($json_persons); }

print "\nEncoder (arrays)\n";
cmpthese(-1, {
    protobuf_pp => \&encode_protobuf_pp_arr,
    protobuf    => \&encode_protobuf_arr,
    protobuf3   => \&encode_protobuf3_arr,
    sereal      => \&encode_sereal_arr,
    json        => \&encode_json_arr,
});

print "\nDecoder (arrays)\n";
cmpthese(-1, {
    protobuf_pp     => \&decode_protobuf_pp_arr,
    protobuf_upb    => \&decode_protobuf_upb_arr,
    protobuf3_upb   => \&decode_protobuf3_upb_arr,
    protobuf3_bbpb  => \&decode_protobuf3_bbpb_arr,
    sereal          => \&decode_sereal_arr,
    json            => \&decode_json_arr,
});
