#!/usr/bin/perl -w
#
# Written by: Sean Ahern (ahern@llnl.gov)
#
# this is a reimplementation, as a filter, of the vile paragraph reformatter.
#
# USAGE:  reformat [-w width] [prefix]
# It reads stdin and outputs a reformatted paragraph on stdout.
#
# The vile macro I use to run this is:
#
# ; Format a paragraph
# 1 store-macro
#     set-variable %savcol $curcol
#     set-variable %savline $curline
#     ~if &equal $char &ascii ">"
#         set-variable %pre @"Prefix string: "
#     ~else
#         set-variable %pre ""
#     ~endif
#     ~force filter-til forward-paragraph &cat "reformat -w78 " %pre
#     ~force %savline goto-line
#     ~force %savcol goto-column
# ~endm
# bind-key execute-macro-1 ^P
#
# It does the standard stuff of filtering a paragraph, but it will also prompt
# you for a prefix string if your cursor is on a ">".  I probably should put in
# math to calculate the width from the wrapmargin or the fill-col, rather than
# hardcoding "78", but I'll leave that as an exercise for the reader.
#
# Here it is...  Enjoy!


require("getopt.pl");

$debug = 0;
print "Debugging messages turned on.\n" if ($debug == 1);

$endings = "'\\\"\\\`\\)\\]}";   # Extra slashes because this string is used in
                         # regular expressions.

# Get the width.  If none specified, default to 72.
$opt_w = 72;
&Getopt("w:");
$save_width = $opt_w;

# If we have an argument, use it, otherwise "".
if (scalar(@ARGV) >= 1)
{
    $name = shift(@ARGV);
} else
{
    $name = "";
}

# Get the indents from a line.  If there is a special character sequence (#,
# >, *, or /*) then return that sequence and the second indent.  Otherwise
# just return the first indent and nothing for the characters and second
# indent.
sub get_indent
{
    local($line) = @_;
    local(@ret);

    $ret[0] = "";
    $ret[1] = "";
    $ret[2] = "";

    # if there is a special character at the front of the line...
    if ($line =~ m=^\s*([#>\*]+|/\*)=)
    {
        $line =~ m=^(\s*)([#\*]+|/\*|(\s*>)+)(\s*)=;
        $ret[0] = $1;
        $ret[1] = $2;
        $ret[2] = $4;
    } else
    {
        $line =~ m=^(\s*)=;
        $ret[0] = $1;
    }

    return(@ret);
}

$have_name = 0 if ($name =~ /^\s*$/);
$have_name = 1 if ($name !~ /^\s*$/);

# At the start, we haven't printed anything out.
$length = 0;
$printed_word = 0;
$first_word = 1;
$indent_characters = "";
$C_comment = 0;
$output_line = "";

# Read in all of the input
@lines = <STDIN>;

# Check to see if we already have something of the form "name>" on the first
# line.
if ($lines[0] =~ /^\s*(\w+)>/)
{
    $name = $1;
    $have_name = 1;
    for($i=0;$i<scalar(@lines);$i++)
    {
        $lines[$i] =~ s/$name>/>/;
    }
}

# Process the first line for spaces and indents.
($first_indent[0],$indent_characters,$first_indent[1]) = &get_indent($lines[0]);
print "First indent 0: \"$first_indent[0]\"\n" if ($debug == 1);
print "First indent 1: \"$first_indent[1]\"\n" if ($debug == 1);

if ($indent_characters !~ /^\s*$/)
{
    print "We have an indent of \"$indent_characters\".\n" if ($debug == 1);
    $have_indent = 1;
} else
{
    $have_indent = 0;
}

# If we do have a name set up the indent to be a ">".
if ($have_name == 1)
{
    $indent_characters = ">";
    $indent_characters = "$name$indent_characters";
    $have_indent = 1;
}

# Set up the width for use on the first line.  We'll reset it for later
# lines.
$width = $save_width;

# Adjust the width to accomodate the indent and the spaces.
$save_width -= length($indent_characters);

# If we have more than one line...
if (scalar(@lines) > 1)
{
    # Process the second line for spaces.
    ($second_indent[0],$foobar,$second_indent[1]) = &get_indent($lines[1]);
    $second_indent[0] .= " " if (length($foobar) == 1);
    $second_indent[0] .= "  " if (length($foobar) == 2);
    $foobar = "";
} else
{
    $second_indent[0] = $first_indent[0];
    $second_indent[1] = $first_indent[1];
}

# If we have a newline for the second indent, use the first indent.
$second_indent[0] = $first_indent[0] if ($second_indent[0] =~ /\n/);
$second_indent[1] = $first_indent[1] if ($second_indent[1] =~ /\n/);

print "Second indent 0: \"$second_indent[0]\"\n" if ($debug == 1);
print "Second indent 1: \"$second_indent[1]\"\n" if ($debug == 1);

# If we have a newline for the first indent, just use the second indent.
$first_indent[0] = $second_indent[0] if ($first_indent[0] =~ /\n/);
$first_indent[1] = $second_indent[1] if ($first_indent[1] =~ /\n/);

if (length($indent_characters) != 0)
{
    # Adjust the first indent if we have less of an indent on the second
    # line.
    if (length($first_indent[0]) > length($second_indent[0]))
    {
        $first_indent[1] = substr($first_indent[0],length($second_indent[0]));
        $first_indent[0] = $second_indent[0];
    }

    # Adjust the second indent.
    if (length($second_indent[0]) >=
        (length($first_indent[0]) +
         length($indent_characters) -
         length($name)
        )
       )
    {
        $test = substr($second_indent[0],
                                   length($first_indent[0])+
                                   length($indent_characters)-
                                   length($name));
        if (length($test) != 0)
        {
            $second_indent[1] = $test;
        }
    }

    $second_indent[0] = $first_indent[0];
}

# Print the indent for the first line.  It's different from the rest, most 
# likely.  
if ($have_indent == 1)
{
    $output_line .= "$first_indent[0]$indent_characters$first_indent[1]";
    $length = length($first_indent[0])+length($indent_characters)+
              length($first_indent[1]);
} else
{
    $output_line .= "$first_indent[0]";
    $length = length($first_indent[0]);
}

# Here we go
for($count = 0; defined($lines[$count]); $count++)
{
    $_ = $lines[$count];

    # Get rid of the $indent_characters if they exist at the front of the 
    # string.  
    if ($have_indent == 1)
    {
        if ($have_name == 1)
        {
            $temp_indent = $indent_characters;
            $temp_indent =~ s/^$name//;

            s/^\s*($temp_indent ?)*\s*//;
        } else
        {
            if (($C_comment == 0) || (! m|^\s*\*/|))
            {
                $temp_indent = $indent_characters;
                $temp_indent =~ s/\*/\\\*/;
                $temp_indent =~ s/^\s*//;
                s/^\s*($temp_indent ?)*\s*//;
            }
        }
    }

    # Get rid of any excess white space in all cases.
    s/^\s*//;
    s/\s*$//;

    # Split the line into words by whitespace.
    for $word (split(/\s+/))
    {
        # If printing out the current word would push us over the $width, do
        # a line break.
        if ($length+length($word) > $width)
        {
            # Print out the line so far, after processing.
            $output_line =~ s/\s*$//;
            print "$output_line\n";

            # Reset the line.
            $output_line = "";
            $length = 0;
            $first_word = 1;
            $width = $save_width;

            # If we don't have an indent, preserve the second indent level.
            $output_line .= "$second_indent[0]$indent_characters$second_indent[1]";
            $length += length($second_indent[0])+
                       length($indent_characters)+
                       length($second_indent[1]);
        }
        
        # It's safe to print out the next word and adjust the $length.
        if ($first_word != 1)
        {
            $output_line .= " ";
            $length++;
        }
        $output_line .= "$word ";
        $printed_word = 1;
        $length += length($word)+1;

        # If the word ends in a period, question mark, or exclamation point,
        # we might be ending a sentence.  Check for common abbreviations to
        # see.  If we *are* ending a sentence, we need to add another space
        # to the output.  We also might be right after a colon.
        if ($word =~ /[\.\?!:][$endings]*$/)
        {
            # Check for standard abbs.
            if ( ($word !~ /\bMr\.[$endings]*$/) &&
                 ($word !~ /\bMrs\.[$endings]*$/) &&
                 ($word !~ /\bMs\.[$endings]*$/) &&
                 ($word !~ /\bCo\.[$endings]*$/) &&
                 ($word !~ /\bLtd\.[$endings]*$/) &&
                 ($word !~ /\bltd\.[$endings]*$/) &&
                 ($word !~ /\bp\.m\.[$endings]*$/) &&
                 ($word !~ /\bP\.M\.[$endings]*$/) &&
                 ($word !~ /\ba\.m\.[$endings]*$/) &&
                 ($word !~ /\bA\.M\.[$endings]*$/) &&
                 ($word !~ /\bi\.e\.[$endings]*$/)
               )
            {
                $output_line .= " ";
                $length++;
            }
        }
    }

    # If we have a "/*" indent, change it to be " *".
    if ($indent_characters eq "/\*")
    {
        $C_comment = 1;
        $indent_characters = " *";

        # If we don't have a second indent 1, make one.
        if (length($second_indent[1]) == 0)
        {
            $second_indent[1] = " ";
        }
    }

    # If we got to the end of the first input line without printing anything,
    # do a line break.
    if ($printed_word == 0)
    {
        # Print out the line so far, after processing.
        $output_line =~ s/\s*$//;
        print "$output_line\n";

        # Reset the line.
        $output_line = "";
        $length = 0;
        $width = $save_width;

        # If we don't have an indent, preserve the second indent level.
        $output_line .= "$second_indent[0]$indent_characters$second_indent[1]";
        $length += length($second_indent[0])+
                   length($indent_characters)+
                   length($second_indent[1]);
    }
}

# Print out the last line, after processing.
$output_line =~ s/\s*$//;
print "$output_line\n";
