use utf8;
use Unicode::String qw(utf7 utf8);

# Given a string of text and a maximum chunk size, return an array
# of chunks of a size less than or equal to the maximum, broken on
# word boundaries.
#
# Each chunk will be individually UTF-7-encoded on an as-needed
# basis, indicated by the presence of a UTF-7-encoded Byte Order
# Mark (BOM), U+FFEF, which comes out as "+/v8" (followed by a - if
# the next character is part of the UTF-7 base64 alphabet, that is,
# an ASCII letter (upper or lowercase), digit, '+' or '/'.
#
# Algorithm:
#
# 1. UTF-7-encode the entire string.
#
# 2. Set max = max_chunk_size - 5  (to allow for the addition of the BOM).
#
# 3. Look for sequences of max non-space characters in a row.  If
#    none found, go to step 5.
# 
# 4. For each series S of max non-space characters:
#
#	a. Scan backwards from the end of S until you either:
#	
#	   1. find a '+' character 
#
#	   2. run off the start of S
#
#	   3. find a character not in the UTF-7 base64 alphabet
#
#	b. If step 4a terminated before finding a '+' character,
#	   go to step 4d.
#	
#	c. S ends in the middle of a shifted sequence U.  Truncate
#	   U and S by moving the end of S backwards through the string
#	   until the length of the portion of U remaining (not counting
#	   the '+') is an  even multiple of 8.  Often this step results
#	   in removing U from S entirely, in  which case the '+' is moved
#	   as well; otherwise, insert another '+' after the new end of S.
#
#	d. Insert a space after S (before any new '+' inserted in 4c).
#
#	e. Resume scanning for the next S, starting with the character
#	   after the space.
#
# 5. Split the string up, by removing spaces, into chunks of size 
#    <= max.
#
# 6. For each chunk C
#
#	a. scan C for occurrances of '+' characters not immediately followed
#	   by '-' characters.
#
#	b. if no such characters are found, then replace all occurrences
#	   of '+-' with '+' 
#
#	c. otherwise, prepend the UTF-7-encoded BOM, remembering to follow
#	   it with '-' if the first character of C is part of the UTF-7
#	   base64 alphabet.
#
#	d. add C to the result array
#
# 7. Return the result array
#
# Note that the pathological case of long runs of non-spaces is the 
# only reason the split code has to be aware of UTF-7 at all; otherwise
# a straightforward sequence of indpendent operations (UTF-7-encode,
# then split) would work fine.
use constant BOM  => '+/v8';

sub utf7split	
{
    my ($string, $max_chunk_size) = @_;
    $string = utf8($string)->utf7;

    my $max = $max_chunk_size - 5;

    while ($string =~ /^(.*?)([^ ]{$max})([^ ].*)$/)
    {
	my ($left, $s, $right) = ($1, $2, $3);
	if ($s =~ m#\+([a-zA-Z0-9/])*$#)
	{
	    my $u = $1;
	    my $floor = int(length($u)/8);
	    $right = '+' . substr($u, $floor) . $right;

	    if ($floor == 0)
	    {
		$s =~ s/\+$u$//;
	    }
	    else
	    {
		$s =~ s/$u$/ substr($u, 0, $floor) /e;
	    }
	}
	$string = "$left$s $right";
    }

    my @chunks = ();
    if (length($string) < $max)
    {
	@chunks = (fix_utf7($string));
    }
    else
    {
        while ($string =~ /^(.{0,$max}) (.*$)/)
        {
	    (my $chunk, $string) = ($1, $2);
	
 	    push(@chunks, fix_utf7($chunk));
        }

        die "Assert failed!  Remaining string too long!\n" if
	    length($string) > $max;

        push(@chunks, fix_utf7($string));
    }

    return @chunks;
}

sub fix_utf7
{
    my $chunk = shift;

    if ($chunk !~ /\+[^-]/)
    {
	$chunk =~ s/\+-/+/g;
    }
    else
    {
	if ($chunk =~ m#^[a-zA-Z0-9+/]#)
	{
	    $chunk = '-' . $chunk;
	}
	$chunk = BOM . $chunk;
    }

    return $chunk;
}

# Given a string that may be UTF-7-encoded, decode it if needed
sub de_utf7
{
    my $string = shift;
    if ($string =~ /^\Q${\(BOM)}\E/)
    {
	$string = utf7($string);
    }
    return $string;
}

for (utf7split(@ARGV))
{
    print(($_),"\n");
}
