#!perl
use strict;
use warnings;
use Getopt::Long qw(GetOptions);
use Term::Size ();

GetOptions(
  my $opt = {},
  'sort',
  'width|w=i',
  'delimiter=s',
  'scaletotal',
  'numeric-format|nf=s',
  'style=s',
);

my $delimiter = "\\s+";
$delimiter = qr/$opt->{delimiter}/ if defined($opt->{delimiter});
$opt->{style} = '~' if not defined $opt->{style};

my %styles = (
  '-' => {character => '-', end_character => '>'},
  '=' => {character => '=', end_character => '>'},
  '~' => {character => '~', end_character => '>'},
);

if (not exists $styles{$opt->{style}}) {
  if (length($opt->{style}) == 1) {
    $styles{$opt->{style}} = {character => $opt->{style}, end_character => $opt->{style}};
  }
  else {
    die "Invalid histogram style '$opt->{style}'. Valid styles: '"
        . join("', '", keys %styles), "' and any single character.\n";
  }
}

# read all input
my @rows;
my $i = 0;
while (<STDIN>) {
  ++$i;
  chomp;
  s/^\s+//;
  my @col = split /$delimiter/o, $_;
  next if @col == 0;
  if (@col == 1) {
    push @rows, [$i, $col[0]];
  } else {
    push @rows, [@col[0,1]];
  }
}

# extract min/max/width info from input data
my $desc_width = 0;
my $val_width  = 0;
my $hist_total = 0;
my $show_numeric = defined($opt->{"numeric-format"});
my $numeric_format = $show_numeric ? $opt->{"numeric-format"} : "%f";
my ($hist_max, $hist_min);
foreach my $row (@rows) {
  my ($desc, $value) = @$row;
  my $formatted = sprintf($numeric_format, $value);

  $desc_width = length($desc) if length($desc) > $desc_width;
  $val_width  = length($formatted) if length($formatted) > $val_width;
  $hist_min = $value if !defined $hist_min or $value < $hist_min;
  $hist_max = $value if !defined $hist_max or $value > $hist_max;
  $hist_total += $value;
  push @$row, $show_numeric ? $formatted : '';
}

# sort by value if desired
@rows = sort {$a->[1] <=> $b->[1]} @rows if $opt->{sort};

# figure out output width
my ($tcols, $trows);
if (-t *STDOUT) {
  ($tcols, $trows) = Term::Size::chars(*STDOUT{IO});
}
else {
  $tcols = 80;
}
my $width = $opt->{width} || $tcols-2;

if ($width < $desc_width + 3) {
  warn "Terminal or desired width is insufficient.\n";
  $width = $desc_width + 3;
}

my $vwidth = $show_numeric ? $val_width+2 : 0;
my $hwidth = $width - $desc_width - $vwidth - 3;

my $scale = $opt->{scaletotal} ? $hist_total : $hist_max;

# format the output
my $format = "%${desc_width}s: %${vwidth}s|%-${hwidth}s|\n";
my $styledef = $styles{$opt->{style}};
my $hchar = $styledef->{character};
my $end_hchar = $styledef->{end_character};
my $end_hchar_len = length($end_hchar);

foreach my $row (@rows) {
  my $value = $row->[1];
  my $hlen = int($value / $scale * $hwidth);
  if ($hlen >= $end_hchar_len) {
    printf($format, $row->[0], $row->[2], ($hchar x ($hlen-$end_hchar_len)) . $end_hchar);
  }
  else {
    printf($format, $row->[0], $row->[2], ($hchar x $hlen));
  }
}

