#!/usr/bin/env perl

use v5.14;
use warnings;
use utf8;

use App::ansifold qw($VERSION);

=encoding utf-8

=head1 NAME

ansifold - fold command handling ANSI terminal sequences

=head1 VERSION

Version 1.04

=head1 SYNOPSIS

ansifold [ options ]

    -w#, --width=#                Folding width (default 70)
         --boundary=word          Fold on word boundary
         --padding                Padding to margin space
         --padchar=_              Padding character
         --ambiguous=narrow|wide  Unicode ambiguous character handling
    -p,  --paragraph              Print extra newline
         --separate=string        Set separator string (default newline)
    -n                            Short cut for --separate ''
         --linebreak=mode         Line-break adjustment rule (default all)
         --runin                  Run-in width (default 4)
         --runout                 Run-out width (default 4)
    -s,  --smart                  Short cut for --boundary=word --linebreak=all

=head1 DESCRIPTION

B<ansifold> is almost B<fold> compatible command utilizing
L<Text::ANSI::Fold> module, which enables to handle ANSI terminal
sequences and Unicode multibyte characters properly.

It folds lines in 70 column by default.  Use option B<-w> to change
the folding width.

    $ ansifold -w132

Unlike original fold(1) command, multiple numbers can be specified
like:

    $ LANG=C date | ansifold -w 3,1,3,1,2 | cat -n
         1  Wed
         2   
         3  Dec
         4   
         5  19

Negative number fields are discarded.

    $ LANG=C date | ansifold -w 3,-1,3,-1,2
    Wed
    Dec
    19

Option B<-n> (or B<--separate> '') eliminates newlines between
columns.

    $ LANG=C date | ansifold -w 3,-1,3,-1,2 -n
    WedDec19

Single field is used repeatedly for the same line, but multiple fields
are not.  Put comma at the end of single field to discard the rest:

    ansifold -w 80,

Number description is handled by L<Getopt::EX::Numbers> module, and
consists of C<start>, C<end>, C<step> and C<length> elements.  For
example,

    $ echo AABBBBCCCCCCDDDDDDDDEEEEEEEEEE | ansifold -w 2:10:2

is equivalent to:

    $ echo AABBBBCCCCCCDDDDDDDDEEEEEEEEEE | ansifold -w 2,4,6,8,10

and produces output like this:

    AA
    BBBB
    CCCCCC
    DDDDDDDD
    EEEEEEEEEE

=head1 LINE BREAKING

Option B<--boundary=word> prohibit to break line in the middle of
alphanumeric word.  This version also supports line break adjustment,
mainly to perform Japanese ``KINSOKU'' processing.  Use
B<--linebreak=all> to enable it.

When B<--linebreak> option is enabled, if the cut-off text start with
space or prohibited characters (e.g. closing parenthesis), they are
ran-in at the end of current line as much as possible.

If the trimmed text end with prohibited characters (e.g. opening
parenthesis), they are ran-out to the head of next line, provided it
fits to maximum width.

Option B<--linebreak> takes a value of I<all>, I<runin>, I<runout> or
I<none>.  Default value is I<none>.

Maximum width of run-in/run-out characters are defined by B<--runin>
and B<--runout> option.  Default values are 4.

Option B<--smart> (or simply B<-s>) is shortcut for
"B<--boundary=word> B<--linebreak=all>" and enables all smart text
formatting capability.

=head1 SEE ALSO

L<ansifold|https://github.com/kaz-utashiro/ansifold>

L<Text::ANSI::Fold|https://github.com/kaz-utashiro/Text-ANSI-Fold>

L<Getopt::EX::Numbers>

L<https://www.w3.org/TR/2012/NOTE-jlreq-20120403/>,
Requirements for Japanese Text Layout,
W3C Working Group Note 3 April 2012

=head1 LICENSE

Copyright (C) 2018- Kazumasa Utashiro

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 AUTHOR

Kazumasa Utashiro

=cut

use open IO => 'utf8', ':std';

use Pod::Usage;
use List::Util qw(min);
use Text::ANSI::Fold qw(:constants);
use Data::Dumper;

my %opt = (
    width => [],
    separate => "\n",
    boundary => "none",
    linebreak => LINEBREAK_NONE,
    runin => 4,
    runout => 4,
    );

my @optargs = (
    "width|w=s"      =>  $opt{width},
    "boundary=s"     => \$opt{boundary},
    "padding!"       => \$opt{padding},
    "padchar=s"      => \$opt{padchar},
    "ambiguous=s"    => \$opt{ambiguous},
    "paragraph|p"    => \$opt{paragraph},
    "separate=s"     => \$opt{separate},
    "linebreak|lb=s" => sub {
	$opt{linebreak} = sub {
	    local $_ = shift;
	    my $v = LINEBREAK_NONE;
	    $v |= LINEBREAK_ALL    if /all/i;
	    $v |= LINEBREAK_RUNIN  if /runin/i;
	    $v |= LINEBREAK_RUNOUT if /runout/i;
	    $v;
	}->($_[1]);
    },
    "runin=i"  => \$opt{runin},
    "runout=i" => \$opt{runout},
    "n" => sub { $opt{separate} = "" },
    "smart|s!" => sub {
	($opt{boundary}, $opt{linebreak}) = do {
	    if ($_[1]) {
		('word', LINEBREAK_ALL);
	    } else {
		('none', LINEBREAK_NONE);
	    }
	};
    },
    "version|v" => sub {
	print "Version: $VERSION\n";
	exit;
    },
    );

use Getopt::EX::Long;
Getopt::Long::Configure("bundling");
GetOptions(@optargs) || pod2usage();

my $DEFAULT_WIDTH = 70;
my @width_map;
my @width_index;

use Getopt::EX::Numbers;
my $numbers = Getopt::EX::Numbers->new;

@{$opt{width}} = do {
    map {
	push @width_map, $_ > 0;
	$_ < 0 ? -$_ : $_;
    }
    map {
	if    (/^$/)                 { 0 }		# empty
	elsif (/^-?\d+$/)            { ($_) }		# 10
	elsif (/^(-?[-\d:]+) (?:\{(\d+)\})? $/x) {	# a:b:c:d{5}
	    ($numbers->parse($1)->sequence) x ($2 // 1);
	}
	else {
	    die "$_: width format error.\n";
	}
    }
    map { split /,/, $_, -1 }
    @{$opt{width}};
};
if (@{$opt{width}} > 1 and grep { not $_ } @width_map) {
    @width_index = grep { $width_map[$_] } 0 .. $#width_map;
}

if (@{$opt{width}} == 0) {
    $opt{width} = $DEFAULT_WIDTH;
}
elsif (@{$opt{width}} == 1) {
    $opt{width} = $opt{width}->[0];
}
else {
    @{$opt{width}} = grep { $_ } @{$opt{width}}; # remove zero
}

my $fold = Text::ANSI::Fold->new(
    map  { $_ => $opt{$_} }
    grep { defined $opt{$_} }
    qw(width boundary padding padchar ambiguous linebreak runin runout)
    );

my $separator = do {
    $opt{separate} =~ s{ ( \\ (.) ) } {
	{ '\\' => '\\', n => "\n" }->{$2} // $1
    }gexr;
};

while (<>) {
    my $chomped = chomp;
    my @chops = $fold->text($_)->chops;
    if (@width_index > 0) {
	@chops = grep { defined } @chops[@width_index];
    }
    print join $separator, @chops;
    print "\n" if $chomped;
    print "\n" if $opt{paragraph};
}

exit;
