package CCircle ;

=head1 LICENSE

   Bweb - A Bacula web interface
   Bacula® - The Network Backup Solution

   Copyright (C) 2000-2006 Free Software Foundation Europe e.V.

   The main author of Bweb is Eric Bollengier.
   The main author of Bacula is Kern Sibbald, with contributions from
   many others, a complete list can be found in the file AUTHORS.

   This program is Free Software; you can redistribute it and/or
   modify it under the terms of version two of the GNU General Public
   License as published by the Free Software Foundation plus additions
   that are listed in the file LICENSE.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301, USA.

   Bacula® is a registered trademark of Kern Sibbald.
   The licensor of Bacula is the Free Software Foundation Europe
   (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
   Switzerland, email:ftf@fsfeurope.org.

=head1 VERSION

    $Id$

=cut

use strict ;
use GD ;

my $pi =  3.14159265;

our $gd ;
our @color_tab ; 

our $black ;
our $white ;

our $last_level = 1 ;
our @draw_label ; 
our $height ;
our $width ;

our $cur_color = 1 ;

my $debug = 0 ;
my $font_size = 6 ;

our @image_map ;

sub new
{
    my ($class, %arg) = @_ ;

    my $self = {
	start_degrees    => 0,
	degrees_complete => 0,
	parent_percent   => 100,
	level            => 1,
	center_x         => 600,
	center_y         => 300,
	diameter         => 75,
	percent_total    => 0,
	min_percent      => 2,
	min_total        => 0,
	width            => 1200,
	height           => 600,
	min_label        => 4,
	min_degrees      => 2,
	max_label_level  => 2,
	display_other    => 0,
	base_url         => '',
    } ;

    map { $self->{$_} = $arg{$_} } keys %arg ;

    unless(defined $gd) {
	$height = $self->{height} ;
	$width  = $self->{width} ;

	$gd    = new GD::Image($width,$height);
	$white = $gd->colorAllocate(255,255,255);

	push @color_tab, ($gd->colorAllocate(135,236,88),
			  $gd->colorAllocate(255,95,95),
			  $gd->colorAllocate(245,207,91),
			  $gd->colorAllocate( 255, 236, 139),
			  $gd->colorAllocate( 255, 174, 185),
			  $gd->colorAllocate( 179, 255,  58),
			  $gd->colorAllocate( 205, 133,   0),
			  $gd->colorAllocate(205, 133,   0 ),
			  $gd->colorAllocate(238, 238, 209),

			  ) ;
		      

	$black = $gd->colorAllocate(0,0,0);
	#$gd->>transparent($white);
	$gd->interlaced('true');

	$gd->arc($self->{center_x},$self->{center_y}, 
		 $self->{diameter},$self->{diameter},
		 $self->{start_degrees},360, $black) ;

    }

    $self->{rayon} = $self->{diameter} / 2 ;

    bless $self ;

    # pour afficher les labels tout propre
    if ($self->{level} > $last_level) {
	$last_level = $self->{level} ;
    }

    return $self ;
}

sub calc_xy
{
    my ($self, $level, $deg) = @_ ;

    my $x1 =   $self->{center_x}+$self->{rayon} * $level * cos($deg*$pi/180) ;
    my $y1 =   $self->{center_y}+$self->{rayon} * $level * sin($deg*$pi/180) ;

    return ($x1, $y1) ;

}

sub add_part
{
    my ($self, $percent, $label, $tips) = @_ ;

    $tips = $tips || $label ;

    if (($percent + $self->{percent_total}) > 100.05) {
	print STDERR "Attention $label ($percent\% + $self->{percent_total}\%) > 100\%\n" ;
	return undef; 
    }

    if ($percent <= 0) {
	print STDERR "Attention $label <= 0\%\n" ;
	return undef; 
    }

    # angle de depart de l'arc
    my $start_degrees = (($self->{degrees_complete})?
	                 $self->{degrees_complete}:$self->{start_degrees}) ;

    # angle de fin de l'arc
    my $end_degrees = $start_degrees +
	($percent * ( ( $self->{parent_percent} * 360 )/100 ) ) /100 ;

#    print STDERR "-------- $debug --------
#percent = $percent%
#label = $label
#level = $self->{level}
#start = $start_degrees
#end   = $end_degrees
#parent= $self->{parent_percent}
#" ;
    if (($end_degrees - $start_degrees) < $self->{min_degrees}) {
	$self->{min_total} += $percent ;
	return undef ;
    }

    if ($percent <= $self->{min_percent}) {
	$self->{min_total} += $percent ;
	return undef ;
    }

    # on totalise les % en cours
    $self->{percent_total} += $percent ;

    #print STDERR "percent_total = $self->{percent_total}\n" ;

    # position dans le cercle
    my $n = $self->{level} ; # on ajoute/retire 0.005 pour depasser un peu
    my $m = $n+1 ;

    # si c'est la premiere tranche de la nouvelle serie, il faut dessiner
    # la premiere limite 

    if ($self->{degrees_complete} == 0) {
	my ($x1, $y1) = $self->calc_xy($n-0.005, $self->{start_degrees}) ;
	my ($x2, $y2) = $self->calc_xy($m+0.005, $self->{start_degrees}) ;

	$gd->line($x1, $y1, $x2, $y2, $black) ;
    }

    # seconde ligne
    my ($x3, $y3) = $self->calc_xy($n-0.005, $end_degrees) ;

    my ($x4, $y4) = $self->calc_xy($m+0.005, $end_degrees) ;

    # ligne de bord exterieur
    $gd->line($x3, $y3, $x4, $y4, $black);

    # on dessine le bord
    $gd->arc($self->{center_x},$self->{center_y}, 
	     $self->{diameter}*($self->{level}+1),
	     $self->{diameter}*($self->{level}+1),

	     $start_degrees-0.5,
	     $end_degrees+0.5, $black) ;

    # on calcule le point qui est au milieu de la tranche
    # angle = (angle nouvelle tranche)/2

    # rayon = n*rayon - 0.5*rayon
    # n=1 -> 0.5
    # n=2 -> 1.5
    # n=3 -> 2.5

    my $mid_rad = ($end_degrees - $start_degrees) /2 + $start_degrees;

    my $moy_x = ($self->{center_x}+
		   ($self->{rayon}*$m - 0.5*$self->{rayon})
		       *cos($mid_rad*$pi/180)) ;

    my $moy_y = ($self->{center_y}+
		   ($self->{rayon}*$m - 0.5*$self->{rayon})
		       *sin($mid_rad*$pi/180)) ;

    $gd->fillToBorder($moy_x, 
		      $moy_y,
		      $black,
		      $cur_color) ;
    
    # on prend une couleur au hasard
    $cur_color = ($cur_color % $#color_tab) + 1 ;

    # si le % est assez grand, on affiche le label
    if ($percent > $self->{min_label}) {
	push @draw_label, [$label, 
			   $moy_x, $moy_y, 
			   $self->{level}] ;

	$self->push_image_map($label, $tips, $start_degrees, $end_degrees) ;
    }

    # pour pourvoir ajouter des sous donnees
    my $ret = new CCircle(start_degrees  => $start_degrees, 
			  parent_percent => $percent*$self->{parent_percent}/100,
			  level          => $m,
			  center_x       => $self->{center_x},
			  center_y       => $self->{center_y},
			  min_percent    => $self->{min_percent},
			  min_degrees    => $self->{min_degrees},
			  base_url       => $self->{base_url} . $label,
			  ) ;
		 
    $self->{degrees_complete} = $end_degrees ;
    
    #print STDERR "$debug : [$self->{level}] $label ($percent)\n" ;
    #open(FP, sprintf(">/tmp/img.%.3i.png", $debug)) ;
    #print FP $gd->png;
    #close(FP) ;
    
    $debug++ ;

    return $ret ;
}

# on dessine le restant < min_percent
sub finalize
{
    my ($self) = @_ ;

    $self->add_part($self->{min_total}, 
		    "other < $self->{min_percent}%",
		    $black) ; 

}

sub set_title
{
    my ($self, $title) = @_ ;

    $gd->string(GD::gdSmallFont, $self->{center_x} - $self->{rayon}*0.7, 
		$self->{center_y}, $title, $black) ;
}

my $_image_map = '';

sub get_imagemap
{
    my ($self, $title, $img) = @_ ;

    return "
<map name='testmap'>
    $_image_map
</map>
<img src='$img' border=0 usemap='#testmap' alt=''>
" ; 

}

sub push_image_map
{
    my ($self, $label, $tips, $start_degrees, $end_degrees) = @_ ;

    if ($label =~ /^other .*</) {
	if (!$self->{display_other}) {
	    return ;
	}
	$label = '';
    }

    # on prend des points tous les $delta sur l'arc interieur et exterieur

    my $delta = 3 ;

    if (($end_degrees - $start_degrees) < $delta) {
	return ;
    }

    my @pts ;

    for (my $i = $start_degrees ; 
	 $i <= $end_degrees ; 
	 $i = $i + $delta)
    {
	my ($x1, $y1) = $self->calc_xy($self->{level}, $i) ;
	my ($x2, $y2) = $self->calc_xy($self->{level} + 1, $i) ;

	push @pts, sprintf("%.2f,%.2f",$x1,$y1) ;
	unshift @pts, sprintf("%.2f,%.2f",$x2, $y2) ;
    }

    my ($x1, $y1) = $self->calc_xy($self->{level}, $end_degrees) ;
    my ($x2, $y2) = $self->calc_xy($self->{level} + 1, $end_degrees) ;

    push @pts, sprintf("%.2f,%.2f",$x1,$y1) ;
    unshift @pts, sprintf("%.2f,%.2f",$x2, $y2) ;

    my $ret = join(",", @pts) ;

    # on refait le traitement avec $i = $end_degrees
    $_image_map .= "<area shape='polygon' coords='$ret' ". 
	           "title='$tips' href='$self->{base_url}$label'>\n" ;

}

sub get_labels_imagemap
{
    my ($self) = @_ ;
    my @ret ;

    for my $l (@draw_label)
    {
	# translation 
	my ($label, $x, $y, $level) = @{ $l } ;
	
	next if ($level > $self->{max_label_level}) ;

	next if (!$self->{display_other} and $label =~ /^other .*</) ;

	my $dy = ($y - $self->{center_y})*($last_level - $level) + $y ;

	my $x2 ;
	my $xp ;

	if ($x < $self->{center_x}) {
	    $x2 =   $self->{center_x} 
	          - $self->{rayon} * ($last_level + 3.7) ;
	    $xp = $x2 - (length($label) *6 + 2) ; # moins la taille de la police 
	} else {
	    $x2 = $self->{center_x} + $self->{rayon} * ($last_level + 3.7) ;
	    $xp = $x2 + 10 ;
	}

	push @ret, $xp - 1 . ";" . $dy - 6 . ";" . $xp + length($label) * $font_size . ";" . $dy + 10 . "\n" ;

	$gd->rectangle($xp - 1, $dy - 6,
		       $xp + length($label) * $font_size, 
		       $dy + 10, $black) ;
    }
}

my $_label_hauteur ;
my $_label_max ;
my $_label_pos ;
my $_label_init = 0 ;

# on va stocker les positions dans un bitmap $_label_pos
# 
# si on match pas la position exacte, on essaye la case
# au dessus ou en dessous
# 
# on a un bitmap pour les labels de gauche et un pour la droite
# $_label_pos->[0] et $_label_pos->[1]
sub get_label_pos
{
    my ($self, $x, $y) = @_ ;

    unless ($_label_init) {
	$_label_hauteur   = $self->{rayon} * 2 * $last_level ;
	# nombre max de label = hauteur max / taille de la font
	$_label_max =  $_label_hauteur / 12 ;
	$_label_pos = [ [], [] ] ;
	$_label_init = 1 ;
    }

    # on calcule la position du label dans le bitmap
    use integer ;
    my $num = $y * $_label_max / $_label_hauteur ;
    no integer ;

    my $n = 0 ;			# nombre d'iteration
    my $l ;

    # on prend le bon bitmap
    if ($x < $self->{center_x}) {
	$l = $_label_pos->[0] ;
    } else {
	$l = $_label_pos->[1] ;
    }	    
    
    # on parcours le bitmap pour trouver la bonne position
    while (($num - $n) > 0) {
	if (not $l->[$num]) {
	    last ;
	} elsif (not $l->[$num + $n]) {
	    $num = $num + $n ;
	    last ;
	} elsif (not $l->[$num - $n]) {
	    $num = $num - $n ;
	    last ;
	} 
	$n++ ;
    }
    
    $l->[$num] = 1 ;		# on prend la position
    
    if ($num <= 0) {
	return 0 ;
    }
    
    # calcul de la position
    $y = $num * $_label_hauteur / $_label_max ;

    return $y ;
}

sub draw_labels
{
    my ($self) = @_ ;

    $gd->fillToBorder(1, 
		      1,
		      $black,
		      $white) ;

    for my $l (@draw_label)
    {
	# translation 
	my ($label, $x, $y, $level) = @{ $l } ;

	next if ($level > $self->{max_label_level}) ;

	next if (!$self->{display_other} and $label =~ /^other .*</) ;

	my $dx = ($x - $self->{center_x})*($last_level - $level) + $x ;
	my $dy = ($y - $self->{center_y})*($last_level - $level) + $y ;

	$dy = $self->get_label_pos($dx, $dy) ;

	next unless ($dy) ; # pas d'affichage si pas de place
	
	$gd->line( $x, $y,
		   $dx, $dy,
		   $black) ;

	my $x2 ;
	my $xp ;

	if ($x < $self->{center_x}) {
	    $x2 =   $self->{center_x} 
	          - $self->{rayon} * ($last_level + 3.7) ;
	    $xp = $x2 - (length($label) * $font_size + 2) ; # moins la taille de la police 
	} else {
	    $x2 = $self->{center_x} + $self->{rayon} * ($last_level + 3.7) ;
	    $xp = $x2 + 10 ;
	}

	$gd->line($dx, $dy,
		  $x2, $dy,
		  $black) ;

	$gd->string(GD::gdSmallFont, $xp, $dy - 5, $label, $black) ;
    }
}

1;
__END__

package main ;

my $top = new CCircle() ;

my $chld1 = $top->add_part(50, 'test') ;
my $chld2 = $top->add_part(20, 'test') ;
my $chld3 = $top->add_part(10, 'test') ;
my $chld4 = $top->add_part(20, 'test') ;


$chld1->add_part(20, 'test1') ;
$chld1->add_part(20, 'test1') ;

$chld2->add_part(20, 'test1') ;
$chld2->add_part(20, 'test1') ;

$chld3->add_part(20, 'test1') ;
my $chld5 = $chld3->add_part(20, 'test1') ;

$chld5->add_part(50, 'test3') ;

$top->finalize() ;
$chld1->finalize() ;
$chld2->finalize() ;
$chld3->finalize() ;
$chld4->finalize() ;
$chld5->finalize() ;

$top->draw_labels() ;
# make sure we are writing to a binary stream
binmode STDOUT;

# Convert the image to PNG and print it on standard output
print $CCircle::gd->png;
