#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package TableObj;
use strict;

use Data::Dumper;
use PDF::API2;

use lib '/app/tools/rps/bin/statements';
use EmbedImage;

use constant kDefaultColPadding => 2;
use constant kDefaultRowPadding => 2;
use constant kDefaultPenSize    => 1;
use constant kDefaultTopLinePad => 1;
use constant kDefaultBottomLinePad => 1;
use constant kDefaultLineColor => 'black';



#
# my $table = TableObj->new
# (
#   $pdf;           # A reference to a PDF::API2 object, which will provide the interface for PDF functions.
#   $page;          # A reference to a PDF::API2::Page object, on which we'll draw the Table.
#                   # !!! Is this strictly necessary?  I would prefer to delay providing the actual page
#                   # !!! until the 'print' method.
#   $dataArrayRef;  # A reference to a 2-dimentional array of scalars, which form the table's content.
#   !!! Really, why bother with 3 args that are not in the hash, if some of the hash args are _required_?
#
#   bottom_margin   => $anInteger,          # The bottom margin (in PostScript points) of each page.
#   new_page_y      => $anInteger,          # The Y coordinate (in PostScript points) at which to continue
#                                           # drawing when a new page is created.
#   new_page_func => \&aFunctionRef,        # _optional_ - a reference to a function of the following form:
#                                           # my $page = func($pdf);  - pdf is a PDF::API2 object, $page is a 
#                                                                     - PDF::API2::Page object
#   font            => $aFontRef,           # The default font.
#   font_size       => $anInteger,          # The default font size.
#    
#   # The following args are _optional_
#   #
#   column_props =>                         # This arg allows you to specify properties for each column.
#   [                                       # It takes a reference to an array of hash refs, one hash ref per column.
#                                           # All these properties are optional.
#       {                                   
#           font        => $aFontRef,       # The font to use for this column.
#           font_size   => $anInteger,      # The font size to use for this column.
#           justify     => $aString,        # The column justification. Can be 'left' (default), 'right', or 'center'
#           min_w       => $anInteger,      # The minimum width (in PostScript points).
#           pad         => $anInteger,      # How much padding (in PostScript points) between this column and the column
#                                           # to the left.  This is ignored for the left-most column.
#       },
#       # ... Remember, need a hash ref (even an empty one) for each column if you are going
#       #     to use this argument.
#   ],
#   row_props =>                            # This arg allows you to specify properties for each row.
#                                           # It takes a reference to an array of hash refs, one hash ref per row.
#   [                                       # All these properties are optional.
#       { 
#           font        => $aFontRef,       # The font to use for this row.
#           font_size   => $anInteger,      # The font size to use for this row.
#           pad         => $anInteger,      # How much padding (in PostScript points) between this row and the row above it.
#                                           # This is ignored for the top-most row.
#                                           
#           lines =>                        # A reference to an array of hash refs that define a set of horizontal lines to
#                                           # draw in this row.  
#                                           # Lines will be drawn directly beneath any text in the row.
#                                           # You can have as many 'line definitions' as you need.
#           [
#               {                           # Each line definition has the following properties:
#                   start_col   => $i1,         # The index (0-based) of the column in which to start drawing the line.
#                                               #   The line will begin in the left-most corner of the cell (not including any pad).
#                   end_col     => $i2,         # The index (0-based) of the column in which to stop drawing the line.
#                                               #   The line wil end in the right-most corner of that column, and will span any padding
#                                               #   between columns (in other words, it will be a continuous line from $i1 to $i2)
#                   top_pad     => $anInteger,  # _optional_  The amount of padding (in PostScript points) between the 
#                                               #   top of the column OR the bottom of the text (if any), and the line.
#                   bottom_pad  => $anInteger,  # _optional_  The amount of padding between the bottom of the line and 
#                                               #   the bottom of the row.  
#                   pen_size    => $anInteger,  # _optional_  The height (in PostScript points) of the line. Default is 1.
#               },
#               # ... You can define as many lines in a row as you need
#           ],
#       },
#       # ... Remember, you will need a hash ref (even an empty one) for each row if you are
#       #     going to use this argument.
#   ],
# );
#
# # !!! Note that in general row_props override column_props.
# # !!! We might need to make that more consistent.
# # !!! We might want to provide a way to change that (i.e. make column settings override row settings if necessary).
#
# # TODO - Might want to provide a way to declare special options for the 'header' row (for example, to have it print
# #        on each page).
#
#
sub new
{
    my ($class, $pdf, $startPage, $dataArray, %args) = @_;

    my $self = bless {}, $class;

    return $self->_init($pdf, $startPage, $dataArray, %args);
}


sub _init
{
    my ($self, $pdf, $startPage, $dataArray, %args) = @_;

    $self->{pdf} = $pdf;
    $self->{page} = $startPage;
    $self->{data} = $dataArray;
    $self->{text} = $self->{page}->text();
    $self->{gfx} = $self->{page}->gfx();

    $self->{font} = $args{font};
    $self->{fontSize} = $args{font_size};

    $self->{numRows} = scalar(@$dataArray);
    $self->{numColumns} = scalar(@{$dataArray->[0]});

    $self->{newPageFunc} = $args{new_page_func};
    
    my $linkArray = $args{row_links};

    # The bottom margin.
    #
    $self->{bottomMargin} = $args{bottom_margin};


    # Where to start the table when we start a new page.
    #
    $self->{newPageY} = $args{new_page_y};

    # Look for optional args
    #
#    $self->{rowPadding} = defined $args{row_padding} ? $args{row_padding} : kDefaultRowPadding;
#    $self->{columnPadding} = defined $args{column_padding} ? $args{column_padding} : kDefaultColPadding;

    # Build our own 2-d array, which will contain the text and font info for every cell.
    # As we go, we'll keep updating the cellWidth and rowHeight data.
    #
    my @collWidth;
    my @rowHeight;
    my @rowLines;

    my @cells;
    for (my $j = 0; $j < $self->{numRows}; $j++)
    {
        my @row;
        my %lineData;

        if (0 == $j)
        {
            $self->{rowPadding}[$j] = 0;
        }
        else
        {
            $self->{rowPadding}[$j] = defined $args{row_padding} ? $args{row_padding} : kDefaultRowPadding;

            # Any specific row properties we should take into account?
            #
            if (defined $args{row_props})
            {
                if (defined $args{row_props}[$j]{pad})
                {
                    $self->{rowPadding}[$j] = $args{row_props}[$j]{pad};
                }
            }
        }

        # See if the 'repeat' row is being declared.
        #
        if (defined $args{row_props} && $args{row_props}[$j]{repeat})
        {
            $self->{repeatRow} = $j;
        }
        # Now see if the 'repeatRows' flag is set.  This allows multiple rows to be repeated. 
        #
        elsif (defined $args{row_props} && $args{row_props}[$j]{repeatRows})
        {
            $self->{repeatRows} .= "-".$j;
        }        

        for (my $i = 0; $i < $self->{numColumns}; $i++)
        {
            my $width = 0;
            my $maxWidth = 0;
            my %cell;
            $cell{text} = $dataArray->[$j][$i];
            $cell{link} = $linkArray->[$j][$i];

            $cell{font} = $self->{font};
            $cell{fontSize} = $self->{fontSize};
            $cell{justify} = 'left';
            $cell{image} = '';
            $cell{doNotTruncate} = '';
            $cell{pad} = kDefaultColPadding;

            my $columnPadding = defined $args{column_padding} ? $args{column_padding} : kDefaultColPadding;

            # If there are column_props defined, use those to override default values.
            #
            if (defined $args{column_props})
            {
                my $default = $args{column_props}->[$i];
                
                $cell{font}     = $default->{font}      if (defined $default->{font});
                $cell{fontSize} = $default->{font_size} if (defined $default->{font_size});
                $cell{justify}  = $default->{justify}   if (defined $default->{justify});
                $cell{image}    = $default->{image}     if (defined $default->{image});
                $cell{doNotTruncate} = $default->{do_not_truncate} if (defined $default->{do_not_truncate});
                $columnPadding  = $default->{pad}       if (defined $default->{pad});

                $width = $default->{min_w} if (defined $default->{min_w});
                $maxWidth = $default->{max_w} if (defined $default->{max_w});
            }


            # We don't add padding to the left-most column.  That would be goofy.
            #
            if (0 == $i)
            {
                $self->{columnPadding}[$i] = 0;
            }
            else
            {
                if (! defined $self->{columnPadding}[$i] || $columnPadding > $self->{columnPadding}[$i])
                {
                    $self->{columnPadding}[$i] = $columnPadding;
                }
            }


            # If this is the header line, look for header overrides.
            # !!! I may change this a bit...
            #
            if (0 == $j && defined $args{header_props})
            {
                $cell{font} = $args{header_props}{font} if (defined $args{header_props}{font});
                $cell{fontSize} = $args{header_props}{font_size} if (defined $args{header_props}{font_size});
            }

            # Look for row-level property overrides
            #
            if (defined $args{row_props})
            {
                $cell{font} = $args{row_props}->[$j]->{font} if (defined $args{row_props}->[$j]->{font});
                $cell{fontSize} = $args{row_props}->[$j]->{font_size} if (defined $args{row_props}->[$j]->{font_size});
            }

            # Find out how wide this text will be, and whether it is greater 
            # than the 'min_w' that may have been specified for the column.
            # !!! min_w?   Shouldn't this be max_w?
            #
            # Don't do this for images!
            if (!$cell{image})
            {
                my $cellTextWidth = $self->{text}->advancewidth($cell{text}, font => $cell{font}, fontsize => $cell{fontSize});

                if ($cellTextWidth > $width)
                {
                    if (! $maxWidth)
                    {
                        $width = $cellTextWidth;
                    }
                    elsif ($cellTextWidth > $maxWidth && !$cell{doNotTruncate})
                    {
                        # !!! We're going to _truncate_ the string to fit.
                        # !!! For now, we'll attempt a somewhat peculiar 'center-truncation' technique.
                        #
                        my $elipsis = '...';
                        my $elipsisWidth = $self->{text}->advancewidth($elipsis, font => $cell{font}, fontsize => $cell{fontSize});
    
                        my @chars = split(//, $cell{text});
    
                        my $preString;
                        my $postString;
                        my $lastPreString;
                        my $lastPostString;
                        while (scalar @chars > 0)
                        {
                            $preString .= shift(@chars);
                            if (scalar @chars)
                            {
                                $postString = pop(@chars) . $postString;
                            }
    
                            my $preWidth = $self->{text}->advancewidth($preString, font => $cell{font}, fontsize => $cell{fontSize});
                            my $postWidth = $self->{text}->advancewidth($postString, font => $cell{font}, fontsize => $cell{fontSize});
                            if ($preWidth + $postWidth + $elipsisWidth > $maxWidth)
                            {
                                last;
                            }
                            $lastPreString = $preString;
                            $lastPostString = $postString;
                        }
    
                        # Replace text with truncated version.
                        #
                        $cell{text} = $lastPreString . $elipsis . $lastPostString;
                    }
                }
            }

            if ($width > $collWidth[$i])
            {
                $collWidth[$i] = $width;
            }

            my $cellHeight = $cell{fontSize};




            # Do we need to increase the row height, too?
            #
            if ($cellHeight > $rowHeight[$j])
            {
                $rowHeight[$j] = $cell{fontSize};
            }

            push @row, \%cell;
        }

        # If we have been asked to draw lines, take those into account too when calculating rowHeight
        #
        if (defined $args{row_props} && defined $args{row_props}[$j]{lines})
        {
            my @lines;
            my $maxLineHeight = 0;
            foreach my $lineDef (@{$args{row_props}[$j]{lines}})
            {
                # !!! These defaults should be able to be globally overridden...
                #
                my $lineHeight = (defined $lineDef->{pen_size} ? $lineDef->{pen_size} : kDefaultPenSize);
                $lineHeight += (defined $lineDef->{top_pad} ? $lineDef->{top_pad} : kDefaultTopLinePad);
                $lineHeight += (defined $lineDef->{bottom_pad} ? $lineDef->{bottom_pad} : kDefaultBottomLinePad);

                if ($lineHeight > $maxLineHeight)
                {
                    $maxLineHeight = $lineHeight;
                }

                push @lines, $lineDef;
            }

            $lineData{maxHeight} = $maxLineHeight;
            $lineData{lines} = \@lines;
        }

        push @cells, \@row;
        push @rowLines, \%lineData;
    }


    $self->{cells} = \@cells;
    $self->{columnWidth} = \@collWidth;
    $self->{rowHeight} = \@rowHeight;
    $self->{rowLines} = \@rowLines;


    return $self;
}


# This method abstracts adjusting the 'Y' coordinate.
# It will instantiate a new page if necessary
#
sub adjustY
{
    my ($self, $y, $delta) = @_;

#    return $y unless defined $delta;

    if ($y - $delta >= $self->{bottomMargin})
    {
        $y -= $delta;
        return $y;
    }

    my $funcRef = $self->{newPageFunc};

    $self->{page} = &$funcRef($self->{pdf});
    $self->{text} = $self->{page}->text();
    $self->{gfx} = $self->{page}->gfx();
    $y = $self->{newPageY};


    # Print out the 'repeat' row if there is one
    #
    if (defined $self->{repeatRow})
    {
        $y = $self->printRow($self->{_baseX}, $y, $self->{repeatRow});
        $y -= $delta;
    }
    elsif (defined $self->{repeatRows})
    {
        my @repeatRows = split(/-/, $self->{repeatRows});
        shift(@repeatRows);
        foreach my $repeatRow (@repeatRows)
        {
            $y = $self->printRow($self->{_baseX}, $y, $repeatRow);            
        }
        $y -= $delta;
    }
    

    return $y;
}

sub wrapIfNecessary
{
    my ($self, $y, $delta) = @_;

    if ($y - $delta >= $self->{bottomMargin})
    {
        # Do nothing.
        #
        return $y;
    }

    my $funcRef = $self->{newPageFunc};

    $self->{page} = &$funcRef($self->{pdf});
    $self->{text} = $self->{page}->text();
    $self->{gfx} = $self->{page}->gfx();
    $y = $self->{newPageY};


    # Print out the 'repeat' row if there is one
    #
    if (defined $self->{repeatRow})
    {
        $y = $self->printRow($self->{_baseX}, $y, $self->{repeatRow});
        $y -= $delta;
    }
    elsif (defined $self->{repeatRows})
    {
        my @repeatRows = split(/-/, $self->{repeatRows});
        shift(@repeatRows);
        foreach my $repeatRow (@repeatRows)
        {
            $y = $self->printRow($self->{_baseX}, $y, $repeatRow);   
        }
        $y -= $delta;
    }

    return $y;
}


sub printRow
{
    my ($self, $x, $y, $j) = @_;

    my $localX = $x;

    my $clientNameClean = Common::Client::Current()->WebAlias();
    $clientNameClean = Common::Client::Current()->ClientNameClean() unless $clientNameClean;

    # Adjust 'y' down by the height of this row.
    #
    $y = $self->adjustY($y, $self->{rowHeight}->[$j] + $self->{rowPadding}->[$j]);


    # In case we need to make a link later...
    #
    my $top = $y + $self->{rowHeight}->[$j] + $self->{rowPadding}->[$j];

    for (my $i = 0; $i < $self->{numColumns}; $i++)
    {
        my $cell = $self->{cells}->[$j][$i];
        my $colWidth = $self->{columnWidth}->[$i];

        # Apply the cell padding to the left
        #
        $localX += $self->{columnPadding}->[$i];

        # For creating the link dimensions below.
        #
        my $left = $localX;
        
        # Set the font data appropriately
        #
        $self->{text}->font($cell->{font}, $cell->{fontSize});

        if ($cell->{link})
        {
            $self->{text}->fillcolor("blue");    
        }
        else
        {
            $self->{text}->fillcolor("black");
        }
            
        # Output the text
        #
        # Hold on, it could be an image!
        if ($cell->{image} eq 'gif')
        {
        	    EmbedImage::addIconGIF(
        	    page    =>  $self->{page},
        	    pdf     =>  $self->{pdf},
        	    source  =>  $cell->{text},
        	    link    =>  $cell->{link},
        	    x       =>  $x,
        	    y       =>  $y
    	    );
    	    
    	    # We create the link for images in the embed function, so let's clear it out now. 
    	    #
    	    $cell->{link} = '';
        }  
        elsif ('left' eq $cell->{justify})
        {
            $self->{text}->translate($localX, $y);
            $self->{text}->text($cell->{text});
        }
        elsif ('right' eq $cell->{justify})
        {
            $self->{text}->translate($localX + $colWidth, $y);
            $self->{text}->text_right($cell->{text});
        }
        elsif ('center' eq $cell->{justify})
        {
            $self->{text}->translate($localX + ($colWidth / 2), $y);
            $self->{text}->text_center($cell->{text});
        }

        $localX += $colWidth;

        if ($cell->{link})
        {

            # And now we'll create the actual link    
            my $annot = $self->{page}->annotation();
            my $bottom = $y;
            my $right = $localX;
            
            my $url = $cell->{link};  
            my $anchorTest = substr($url, 0, 1);
            
            # !!! 
            # If the first character of the link is '#',
            # we're going to assume this is meant to be an 
            # internal link for this document.
            #
            
            if ($anchorTest ne '#' )
            {
                # external link
                $url = "https://" . $clientNameClean . ".royaltyshare.com" . $url;
                $annot->url($url, -rect => [$left, $bottom, $right, $top]);   
            }
            else
            {
                # internal link
                $annot->link($url, -rect => [$left, $bottom, $right, $top]);                   
            }     
        }
   
    }


    # Any lines to draw?
    #
    if (defined $self->{rowLines}[$j] && defined $self->{rowLines}[$j]{maxHeight})
    {
        my $lineData = $self->{rowLines}[$j];
        my $lineArray = $lineData->{lines};


        # Seems kinda dumb to draw a 'part' of a line on a page.
        # So, if the line height is going to cause the page to wrap, let's wrap the page first, 
        # and _then_ start drawing the line.
        #
        $y = $self->wrapIfNecessary($y, $lineData->{maxHeight});

        foreach my $line (@$lineArray)
        {
            next unless (defined $line->{start_col} && defined $line->{end_col});
            my $lineXStart = $x;
            my $lineY = $y;

            $lineY -= (defined $line->{top_pad} ? $line->{top_pad} : kDefaultTopLinePad);

            # Find the starting X offset
            #
            for (my $xx = 0; $xx < $line->{start_col}; $xx++)
            {
                $lineXStart += $self->columnWidth($xx);
            }
            my $lineXEnd = $lineXStart + $self->columnWidth($line->{start_col}, $line->{end_col});

            my $lineHeight = (defined $line->{pen_size} ? $line->{pen_size} : kDefaultPenSize);
            my $color = (defined $line->{color} ? $line->{color} : kDefaultLineColor);
            $self->{gfx}->strokecolor($color);

            # Draw the line
            #
            for (my $yy = 0; $yy < $lineHeight; $yy++)
            {
                $self->{gfx}->move($lineXStart, $lineY);
                $self->{gfx}->line($lineXEnd, $lineY);
                $self->{gfx}->stroke();

                $lineY--;
            }
        }

        $y = $self->adjustY($y, $lineData->{maxHeight});
    }

    return $y;
}

sub print
{
    my ($self, $x, $y) = @_;


    $self->{_baseX} = $x;

    # Now the fun part: print this thing out.
    #
    for (my $j = 0; $j < $self->{numRows}; $j++)
    {
        $y = $self->printRow($x, $y, $j);
    }

    return ($self->{page}, $y);
}

sub columnWidth
{
    my ($self, $startCol, $endCol) = @_;

    my $width = 0;

    $endCol = $startCol unless defined $endCol;

    for (my $i = $startCol; $i <= $endCol; $i++)
    {
        $width += $self->{columnWidth}->[$i];
        $width += $self->{columnPadding}->[$i];
    }

    return $width;
}

sub rowHeight
{
    my ($self, $startRow, $endRow) = @_;

    my $height= 0;

    $endRow = $startRow unless defined $endRow;

    for (my $i = $startRow; $i < $endRow; $i++)
    {
        $height += $self->{rowHeight}->[$i];
        $height += $self->{rowPadding}->[$i];
    }

    return $height;
}


1;
