#!/usr/bin/env perl
use strict;
use warnings;

use Benchmark qw(timethese);
use Getopt::Long qw(GetOptions :config no_ignore_case);
use List::Util qw(max);

use Compress::Bzip2  ();
use Compress::LZ4    ();
use Compress::LZF    ();
use Compress::Snappy ();
use Compress::Zlib   ();

my %opts = (
    iterations => -1,
    size       => 10,  # kB
);
GetOptions(\%opts, 'iterations|i=i', 'size|s=f',);

my $data = join '', ('A'..'Z', 'a'..'z', 0..9, qw(_ .)) x (16 * $opts{size});

my %compress = (
    'Compress::Bzip2::compress'  => sub { Compress::Bzip2::compress($data) },
    'Compress::LZ4::compress'    => sub { Compress::LZ4::compress($data) },
    'Compress::LZ4::compress_hc' => sub { Compress::LZ4::compress_hc($data) },
    'Compress::LZF::compress'    => sub { Compress::LZF::compress($data) },
    'Compress::Snappy::compress' => sub { Compress::Snappy::compress($data) },
    'Compress::Zlib::compress'   => sub { Compress::Zlib::compress($data) },
);

my $bzip2  = Compress::Bzip2::compress($data);
my $lz4    = Compress::LZ4::compress($data);
my $lz4_hc = Compress::LZ4::compress_hc($data);
my $lzf    = Compress::LZF::compress($data);
my $snappy = Compress::Snappy::compress($data);
my $zlib   = Compress::Zlib::compress($data);

my %compression_ratios = (
    'Compress::Bzip2::compress'  => length($bzip2) / length($data),
    'Compress::LZ4::compress'    => length($lz4) / length($data),
    'Compress::LZ4::compress_hc' => length($lz4_hc) / length($data),
    'Compress::LZF::compress'    => length($lzf) / length($data),
    'Compress::Snappy::compress' => length($snappy) / length($data),
    'Compress::Zlib::compress'   => length($zlib) / length($data),
);

my %decompress = (
    'Compress::Bzip2::decompress' =>
        sub { Compress::Bzip2::decompress($bzip2) },
    'Compress::LZ4::decompress' => sub { Compress::LZ4::decompress($lz4) },
    'Compress::LZF::decompress' => sub { Compress::LZF::decompress($lzf) },
    'Compress::Snappy::decompress' =>
        sub { Compress::Snappy::decompress($snappy) },
    'Compress::Zlib::uncompress' => sub { Compress::Zlib::uncompress($zlib) },
);

run(\%compress, 'Compressible', 'compression', \%compression_ratios);
print "\n";
run(\%decompress, 'Compressible', 'decompression');
print "\n";

# Make a string of random bytes.
$data = join '', map { chr int rand(256) } (1 .. $opts{size});
# Compress it just to make sure.
$data = Compress::Bzip2::compress($data);

$bzip2  = Compress::Bzip2::compress($data);
$lz4    = Compress::LZ4::compress($data);
$lz4_hc = Compress::LZ4::compress_hc($data);
$lzf    = Compress::LZF::compress($data);
$snappy = Compress::Snappy::compress($data);
$zlib   = Compress::Zlib::compress($data);

%compression_ratios = (
    'Compress::Bzip2::compress'  => length($bzip2) / length($data),
    'Compress::LZ4::compress'    => length($lz4) / length($data),
    'Compress::LZ4::compress_hc' => length($lz4_hc) / length($data),
    'Compress::LZF::compress'    => length($lzf) / length($data),
    'Compress::Snappy::compress' => length($snappy) / length($data),
    'Compress::Zlib::compress'   => length($zlib) / length($data),
);

run(\%compress, 'Uncompressible', 'compression', \%compression_ratios);
print "\n";
run(\%decompress, 'Uncompressible', 'decompression');

exit;


sub run {
    my ($tests, $type, $op, $c_ratios) = @_;

    my $header = sprintf '%s data (%s KiB) - %s', $type, $opts{size}, $op;
    printf "%s\n%s\n", $header, '-' x length($header);

    my $times = timethese -1, $tests, 'none';

    my @info;
    my ($max_name_len, $max_rate_len, $max_bw_len) = (0, 0, 0);
    my ($c_format, $c_ratio_sub) = ('', sub { });
    if ($c_ratios) {
        $c_format    = '  %.3f%%';
        $c_ratio_sub = sub { 100 * $c_ratios->{$_[0]} };
    }

    while (my ($name, $info) = each %$times) {
        my ($duration, $cycles) = @{$info}[ 1, 5 ];
        my $rate = sprintf '%.0f', $cycles / $duration;
        my $bw = $rate * $opts{size} / 1024;
        $bw = sprintf int $bw ? '%.0f' : '%.2f', $bw;

        push @info, [$name, $rate, $bw];

        $max_name_len = max $max_name_len, length($name);
        $max_rate_len = max $max_rate_len, length($rate);
        $max_bw_len   = max $max_bw_len,   length($bw);
    }

    for my $rec (sort { $b->[1] <=> $a->[1] } @info) {
        my ($name, $rate, $bw) = @$rec;

        my $name_padding = $max_name_len - length($name);

        printf "%s %s %${max_rate_len}s/s  %${max_bw_len}s MiB/s$c_format\n",
            $name, ' 'x$name_padding, $rate, $bw, $c_ratio_sub->($name);
    }
}
