=head1 NAME

iPE::Util::Output - A class to aid in outputting parameter files for iParameterEstimation.

=head1 DESCRIPTION

Aids in keeping a standard output format for all parameter modules in iParameterEstimation through standard word and number formatting routines.  Basic services are indentation, via indent, increaseIndent, and decreaseIndent.  One can format floats with floatf and ints with intf.  For tables with english words for, say, state names, one can use addWord and wordf to keep track of the maximum word length and format the word so as to keep a well flushed table.

=cut

package iPE::Util::Output;
use Symbol;
use strict;

=head1 FUNCTIONS

=over 8

=item new(file_handle)

Create and initialize a new Output object.  Optionally pass a file handle to initialize the file handle associated with the output object.

=cut
sub new {
    my $class = shift;
    my $this = bless {}, $class;

    my $fh = shift;
    $this->{fh_} = $fh;
    $this->{word_lengths_}  = ();
    $this->{extra_space_}   = 2;
    $this->{indent_level_}  = 0;
    $this->{indent_size_}   = 4;
    $this->{float_digits_}  = 10;
    $this->{whole_digits_}  = 5;
    $this->{tab_size_}     = 6;

    $this->_update_formats();

    return $this;
}

sub _update_formats {
    my ($this) = @_;
    $this->{float_format_} = 
        "%".($this->{float_digits_}+$this->{whole_digits_}).
        ".".$this->{float_digits_}."f";
    $this->{int_format_} = "%".$this->{whole_digits_}."d";
}

sub indent_level { return shift->{indent_level_} }
sub indent_size  { return shift->{indent_size_}  }
sub float_digits { return shift->{float_digits_} }
sub tab_size     { return shift->{tab_size_}     }

=item fh ()

Get the current filehandle from the output object.

=cut
sub fh { return shift->{fh_}; }

=item setFH (file_handle) 

Sets the filehandle to the passed file handle.

=cut
sub setFH { $_[0]->{fh_} = $_[1] }

=item indent ()

Get the current indentation string.

=cut
sub indent { 
    my ($this) = @_;
    if($this->indent_level) { 
        return sprintf "%*s", $_[0]->indent_level*$_[0]->indent_size, ' ' 
    }
    else {
        return "";
    }
}

=item tab ()

Get a tab string.

=cut
sub tab { return sprintf "%*s", $_[0]->tab_size, ' ' }

=item setFileHandle (fileHandle)

Sets the current file handle to output to.

=cut
sub setFileHandle { $_[0]->{fh_} = $_[1]; }

=item print (scalar, ...)

Print to the current file handle.  Works just as Perl print does, except that it uses the contained file handle.

=cut
sub print {
    my $this = shift;

    print {$this->fh} @_;
}

=item printPCdata (scalar)

Print parsed character data.  Use this to print out inputted data from XML files.

=cut
sub printPCData {
    my ($this, $data) = @_;
    chomp($data);
    #eliminate the first end of line character added for XML output
    $data =~ s/\n//;
    my @lines = split ('\n', $data);
    print {$this->fh} $this->indent.$_."\n" for (@lines);
}

=item addWord (word_type, word)

Add a new word to a given word_type.  This keeps track of the maximum word length of a given word_type.  For example, if you are printing out state names, you would add a number of state names, passing "State" to the word_type parameter.  When the formatted words are retrieved via wordf, the tab indentation following the word would be consistent with the maximum word length of the "State" type, so that the words following appear in left-flushed columns.

=cut
sub addWord {
    my ($this, $word_type, $word) = @_;
    
    if(!exists $this->{word_lengths_}{$word_type} ||
            $this->{word_lengths_}{$word_type} < length($word)) {
        $this->{word_lengths_}{$word_type} = length($word);
    }
}

=item wordf (word_type, word)

Get the string for a word formatted of a certain type.  If the words have not been added via addWord, this will just add a tab to the end of the word.  No warning is thrown.

=cut
sub wordf {
    my ($this, $word_type, $word) = @_;
    my $result;

    if(exists $this->{word_lengths_}{$word_type}) {
        $result = sprintf "%s%*s", 
            $word, $this->{word_lengths_}{$word_type}+1 - length($word), ' ';
    }
    else {
        $result = $word."\t";
    }

    return $result;
}

=item intf (value)

Get the string for a formatted integer.

=cut
sub intf {
    my ($this, $val) = @_;

    return sprintf $this->{int_format_}, $val;
}

=item floatf (value)

Get the string for a formatted floating point digit, with the set (or default, 10) number of digits after the decimal point.

=cut
sub floatf {
    my ($this, $val) = @_;

    return sprintf $this->{float_format_}, $val;
}

=item increaseIndent ()

Increases the current indent level for the output object.  When indent is called, the length will increase the amount set by setIndentSize, or by the default, 4, if not called.

=cut
sub increaseIndent { shift->{indent_level_}++; }

=item decreaseIndent ()

Decreases the current indent level for the output object.  When indent is called, the length will decrease the amount set by setIndentSize, or by the default, 4, if not called.

=cut
sub decreaseIndent { $_[0]->{indent_level_}--; $_[0] < 0 and $_[0] = 0;}

=item setIndentSize (size)

Sets how many additional spaces are in a single indentation level.

=cut
sub setIndentSize { $_[0]->{indent_size_} = $_[1] }

=item setFloatSize (size)

Sets how many precision decimal places to output with a floating point number.

=cut
sub setFloatSize { 
    my ($this, $size) = @_;
    $this->{float_digits_} = $size;
    $this->_update_formats();

}

=item reset ()

Resets all the indentation flags and word length flags.

=cut
sub reset {
    my ($this) = @_;

    for my $word_type (keys %{$this->{word_lengths_}}) {
        $this->{word_lengths_}{$word_type} = undef;
    }
    $this->{indent_level_} = 0;
}


=head1 SEE ALSO

L<iPE>

=head1 AUTHOR

Bob Zimmermann (rpz@cs.wustl.edu).

=cut

1;
