# -*- perl -*-
# Formatter.pm $Id: Formatter.pm,v 1.11 1999/01/25 18:09:04 jens Exp $
# (C) Copyright Jens G Jensen <jens@arcade.mathematik.uni-freiburg.de>
# This file is part of epsmerge and is distributed under GNU GPL


package Formatter;

use strict;
use integer;
use Cell;
use Options;


# Pass options to new and it'll pick out those it wants
sub new {
    my $self = shift;
    $self = ref($self) || $self;
    return bless { }, $self;
}

# The Biggie
# Input:
#   A boundingbox (4 integers)
#   A CompositeCell reference (whose Cells should be formatted)
sub format {
    use integer;
    my $self = shift;
    my $opt = Options->new();
    $self->{bbox} = [ splice @_, 0, 4 ];
    my $ccell = shift;
    my @cellrefs = $ccell->cells();
    my $numcells = @cellrefs;
    my $x = $opt->getopts('x');
    my $y = $opt->getopts('y');
    $self->find_max_dim( @cellrefs ) if $opt->getopts('prs');
    my ($w, $h);
    if( $x ) {
	if( $y ) {
	    unless( $numcells <= $x * $y ) {
		printf STDERR "Can't fit $numcells subcells into just %dx%d boxes!\n", $x, $y;
		exit(5);
	    }
	}
	else {
	    for( $y = $numcells / $x; $numcells > $x * $y ; ++$y) { };
	}
    }
    else {
	if( $y ) {
	    for( $x = $numcells / $y; $numcells > $x * $y ; ++$x) { };
	}
	else {
	    if($numcells == 1) {
		$x = $y = 1;
	    }
	    else {
		($x, $y, $w, $h) = $self->_format( $ccell );
	    }
	}
    }
    unless($w && $h) {
	use integer;
	$w = ( $self->{'bbox'}[2] - $self->{'bbox'}[0] - ($x-1) * $opt->getopts('xcs') ) / $x;
	$h = ( $self->{'bbox'}[3] - $self->{'bbox'}[1] - ($y-1) * $opt->getopts('ycs') ) / $y;
    }
    if( $w <= 0 || $h <= 0 ) {
	print STDERR "Can't format with these margins and spacings; no room for cells!\n";
	exit(5);
    }
    my ($xc, $yc, $col, $row, $indx);
    $indx = 0;
    # hm -- some code (almost-)duplication...
    if( $opt->getopts('rmo') ) {
	# 123/456/789 format
	$yc = $self->{'bbox'}[3] - $h;
      FORM:
	for($row=0; $row<$y; ++$row) {
	    $xc = $self->{'bbox'}[0];
	    for($col=0; $col<$x; ++$col) {
		my @box = $self->_newbox( $cellrefs[ $indx ], $xc, $yc, $w, $h);
		$cellrefs[ $indx ++ ]->write( @box );
		$xc += $w + $opt->getopts('xcs');
		last FORM if $indx == $numcells;
	    }
	    $yc -= $h + $opt->getopts('ycs');
	}
    }
    else {
	# 147/258/369 format
	$xc = $self->{'bbox'}[0];
      FORM:
	for($col=0; $col<$x; ++$col) {
	    $yc = $self->{'bbox'}[3] - $h;
	    for($row=0; $row<$y; ++$row) {
		my @box = $self->_newbox( $cellrefs[ $indx ], $xc, $yc, $w, $h);
		$cellrefs[ $indx ++ ]->write( @box );
		$yc -= $h + $opt->getopts('ycs');
		last FORM if $indx == $numcells;
	    }
	    $xc += $w + $opt->getopts('xcs');
	}
    }
}

# Return the required boundingbox for the current cell
sub _newbox {
    my ($self, $cell, $llx, $lly, $w, $h) = @_;
    my $opt = Options->new();
    use integer;
    if( $opt->getopts('prs') ) {
	if( $opt->getopts('par') ) {
	    print STDERR "Warning: -prs taking precedence over -par\n";
	    $opt->setopts( par => 0 );
	}
	my @dim = $cell->dim();
	my ($oldw, $oldh) = ($w, $h);
	$w = $w * $dim[0] / $self->{'maxw'};
	$h = $h * $dim[1] / $self->{'maxh'};
	$llx += ($oldw - $w) / 2;
	$lly += ($oldh - $h) / 2;
    }
    elsif( $opt->getopts('par') ) {
	my @dim = $cell->dim();
	no integer;
	my $ar = $dim[1] / $dim[0];
	if( $h/$w < $ar ) {
	    my $oldw = $w;
	    $w = $h / $ar;
	    $llx += ($oldw - $w) / 2;
	}
	else {
	    my $oldh = $h;
	    $h = $ar * $w;
	    $lly += ($oldh - $h) / 2;
	}
    }
    # force rounding to integer
    return ($llx+0, $lly+0, $llx+$w, $lly+$h);
}

# This finds the max width and height ever occuring among the cells
# and stores this data in the object; used only for -prs option
# Could transfer to CompositeCell.
sub find_max_dim {
    my $self = shift;
    my ($maxw, $maxh) = (0, 0);
    foreach (@_) {
	my ($w, $h) = $_->dim();
	$maxw = $w if $w > $maxw;
	$maxh = $h if $h > $maxh;
    }
    $self->{maxw} = $maxw;
    $self->{maxh} = $maxh;
}

# Calculate modified width and height
# Input: width, height
sub _modwh {
    my ($self, $opt) = @_;
    my ($w, $h) = ($self->{bbox}->[2] - $self->{bbox}->[0], $self->{bbox}->[3] - $self->{bbox}->[1]);
    $w += $opt->getopts('xcs');
    $h += $opt->getopts('ycs');
    if( $w <= 0 || $h <= 0) {
	print STDERR "Not enough room for formatting with these margins and spacings\n";
	exit(5);
    }
    return ($w, $h);
}

# Given cell references, calculate suggested format given
# that we somehow try to preserve their aspect ratio
sub _format {
    my ($self, $ccell) = @_;
    my $opt = Options->new();
    my ($modw, $modh) = $self->_modwh($opt);
    my $n = $ccell->cells();

    # plain average; experience must show if this is OK
    no integer;
    my $avg_aspect_ratio = 0;
    foreach ( $ccell->cells() ) {
	$avg_aspect_ratio += $_->asprat();
    }
    $avg_aspect_ratio /= $n;

    my $x;
    # hairy algorithm stuff but more precise approximation
    # (less precise would be sqrt( avg * modw / modh * $n ))
    my $b = ($avg_aspect_ratio * $opt->getopts('xcs') - $opt->getopts('ycs')) / 2;
    my $d = ($b * $b) + $avg_aspect_ratio * $modw * $modh / $n;
    $x = (sqrt( $d ) - $b) * $n / $modh;

    # trouble ahead: must round to integer
    $x = ceil( $x );
    $x = 1 if $x <= 0;
    my $y = ceil( $n / $x );
    if( $x > 1 && $x * $y > ($x-1) * ceil($n/($x-1)) ) {
	--$x;
	$y = ceil( $n / $x );
    }

    my ($neww, $newh) =
	(floor( $modw / $x ) - $opt->getopts('xcs'), floor( $modh / $y ) - $opt->getopts('ycs'));

    # finally, the testing: should be ok but better safe than sorry
    while( $x * $y < $n ) { ++$y; }
    while( ($neww + $opt->getopts('xcs')) * $x > $modw ) { --$neww; }
    while( ($newh + $opt->getopts('ycs')) * $y > $modh ) { --$newh; }

    return ($x, $y, $neww, $newh);
}

# Normally, I'd use POSIX for these but I am not sure that everybody
# has POSIX; note they are not methods
sub ceil {
    no integer;
    my $num = sprintf "%d", $_[0];
    return $num == $_[0] ? $num : $num+1;
}

sub floor {
    return sprintf "%d", $_[0];
}

1;
