package SVG::Graph::Glyph::bezier;
BEGIN {
  $SVG::Graph::Glyph::bezier::AUTHORITY = 'cpan:CJFIELDS';
}

our $VERSION = '0.04'; # VERSION

use base SVG::Graph::Glyph;
use strict;
use Math::Spline;


sub draw {
    my ( $self, @args ) = @_;

    my $id = 'n' . sprintf( "%07d", int( rand(9999999) ) );
    my $group = $self->svg->group( id => "bezier$id" );

    my $xscale = $self->xsize / $self->group->xrange;
    my $yscale = $self->ysize / $self->group->yrange;

    my @p = map { [ $_->x, $_->y ] }
        sort { $a->x <=> $b->x } $self->group->data;
    my $c = spline_generate(@p);

    my @c;
    foreach my $i ( 1 .. $#p ) {
        my ( $x1, $y1 ) = @{ $p[ $i - 1 ] };
        my ( $x2, $y2 ) = @{ $p[$i] };

        my $e = 0;
        my ( $cx, $cy, $cx_sum, $cy_sum );
        for ( my $d = 2; $d <= 100; $d += 2 ) {
            next if $d == 0;
            my $delta = ( $x2 - $x1 ) / $d;
            my $y1d   = spline_evaluate( $x1 + $delta, $c, @p );
            my $y2d   = spline_evaluate( $x2 - $delta, $c, @p );

            my ( $int, $t1, $t2 ) = Intersection(
                Segment( [ $x1,          $y1 ],  [ $x1 + $delta, $y1d ] ),
                Segment( [ $x2 - $delta, $y2d ], [ $x2,          $y2 ] )
            );
            next unless ref($int);
            next unless $x1 <= $int->[0] and $x2 >= $int->[0];

            $e++;
            $cx_sum += $int->[0];
            $cy_sum += $int->[1];
        }

        $x1 = ( ( $x1 - $self->group->xmin ) * $xscale ) + $self->xoffset;
        $y1 = ( $self->ysize - ( $y1 - $self->group->ymin ) * $yscale )
            + $self->yoffset;
        $x2 = ( ( $x2 - $self->group->xmin ) * $xscale ) + $self->xoffset;
        $y2 = ( $self->ysize - ( $y2 - $self->group->ymin ) * $yscale )
            + $self->yoffset;

        #warn $cx_sum,"\t",$e;
        #next unless $e;
        if ($e) {
            $cx = $cx_sum / $e;
            $cy = $cy_sum / $e;

            $cx = ( ( $cx - $self->group->xmin ) * $xscale ) + $self->xoffset;
            $cy = ( $self->ysize - ( $cy - $self->group->ymin ) * $yscale )
                + $self->yoffset;

            $group->path(
                d     => "M$x1,$y1 Q$cx,$cy $x2,$y2",
                style => { $self->_style },
            );
        }
        else {
            $group->line(
                x1    => $x1,
                y1    => $y1,
                x2    => $x2,
                y2    => $y2,
                style => { $self->_style }
            );
        }
    }

}


sub spline_generate {
    my (@points) = @_;

    my ( $i, $delta, $temp, @factors, @coeffs );
    $coeffs[0] = $factors[0] = 0;

    # Decomposition phase of the tridiagonal system of equations
    for ( $i = 1; $i < @points - 1; $i++ ) {

        #ad
        next unless ( $points[ $i + 1 ][0] - $points[ $i - 1 ][0] );

        $delta = ( $points[$i][0] - $points[ $i - 1 ][0] )
            / ( ( $points[ $i + 1 ][0] - $points[ $i - 1 ][0] ) );

        $temp = $delta * ( $coeffs[ $i - 1 ] || 0 ) + 2;

        $coeffs[$i] = ( $delta - 1 ) / @points;

        #ad
        next unless ( $points[ $i + 1 ][0] - $points[$i][0] );
        next unless ( $points[$i][0] - $points[ $i - 1 ][0] );

        $factors[$i]
            = ( $points[ $i + 1 ][1] - $points[$i][1] )
            / ( ( $points[ $i + 1 ][0] - $points[$i][0] ) )
            - ( $points[$i][1] - $points[ $i - 1 ][1] )
            / ( ( $points[$i][0] - $points[ $i - 1 ][0] ) );

        $factors[$i]
            = ( 6
                * $factors[$i]
                / ( ( $points[ $i + 1 ][0] - $points[ $i - 1 ][0] ) )
                - $delta
                * $factors[ $i - 1 ] ) / $temp;
    }

    # Backsubstitution phase of the tridiagonal system
    #
    $coeffs[$#points] = 0;
    for ( $i = @points - 2; $i >= 0; $i-- ) {
        $coeffs[$i] = ( $coeffs[$i] || 0 ) * ( $coeffs[ $i + 1 ] || 0 )
            + ( $factors[$i] || 0 );
    }
    return \@coeffs;
}


sub spline_evaluate {
    my ( $x, $coeffs, @points ) = @_;
    my ( $i, $delta, $mult );

    # Which section of the spline are we in?
    #
    for ( $i = @points - 2; $i >= 1; $i-- ) {
        last if $x >= $points[$i][0];
    }

    $delta = $points[ $i + 1 ][0] - $points[$i][0];

    #ad
    return 0 unless $delta;

    $mult
        = ( $coeffs->[$i] / 2 )
        + ( $x - $points[$i][0] )
        * ( $coeffs->[ $i + 1 ] - $coeffs->[$i] )
        / ( 6 * $delta );
    $mult *= $x - $points[$i][0];
    $mult += ( $points[ $i + 1 ][1] - $points[$i][1] ) / ($delta);
    $mult -= ( $coeffs->[ $i + 1 ] + 2 * $coeffs->[$i] ) * $delta / 6;
    return $points[$i][1] + $mult * ( $x - $points[$i][0] );
}


sub Segment {
    [ $_[0], $_[1] ];
}


sub VectorSum {
    my ( $x, $y ) = ( 0, 0 );
    for (@_) {
        $x += $_->[0];
        $y += $_->[1];
    }
    [ $x, $y ];
}


sub ScalarProd {
    my $s = shift;
    my $v = shift;
    [ $v->[0] * $s, $v->[1] * $s ];
}


sub Minus {
    my $v = shift;
    [ -$v->[0], -$v->[1] ];
}


sub Intersection {
    my $l1 = shift;
    my $l2 = shift;
    my ( $bp1, $op1 ) = @$l1;
    my ( $bp2, $op2 ) = @$l2;
    my $v1 = VectorSum( $op1, Minus($bp1) );
    my $v2 = VectorSum( $op2, Minus($bp2) );
    my ( $x1, $y1 ) = @$v1;
    my ( $x2, $y2 ) = @$v2;

    my $DEN = $x1 * $y2 - $x2 * $y1;

    # Lines are parallel.
    return undef if $DEN == 0;

    my ( $bx1, $by1 ) = @$bp1;
    my ( $bx2, $by2 ) = @$bp2;

    my $t1 = ( ( $bx2 - $bx1 ) * $y2 - ( $by2 - $by1 ) * $x2 ) / $DEN;
    my $t2 = ( ( $bx2 - $bx1 ) * $y1 - ( $by2 - $by1 ) * $x1 ) / $DEN;

    my $RESULT = VectorSum( $bp1, ScalarProd( $t1, $v1 ) );
    ( $RESULT, $t1, $t2 );
}

1;

__END__
=pod

=encoding utf-8

=head1 NAME

SVG::Graph::Glyph::bezier

=head2 draw

 Title   : draw
 Usage   :
 Function:
 Example :
 Returns :
 Args    :

=head2 spline_generate

 Title   : spline_generate
 Usage   :
 Function:
 Example :
 Returns :
 Args    :

=head2 spline_evaluate

 Title   : spline_evaluate
 Usage   :
 Function:
 Example :
 Returns :
 Args    :

=head2 Segment

 Title   : Segment
 Usage   :
 Function:
 Example :
 Returns :
 Args    :

=head2 VectorSum

 Title   : VectorSum
 Usage   :
 Function:
 Example :
 Returns :
 Args    :

=head2 ScalarProd

 Title   : ScalarProd
 Usage   :
 Function:
 Example :
 Returns :
 Args    :

=head2 Minus

 Title   : Minus
 Usage   :
 Function:
 Example :
 Returns :
 Args    :

=head2 Intersection

 Title   : Intersection
 Usage   :
 Function:
 Example :
 Returns :
 Args    :

=head1 AUTHOR

Chris Fields <cjfields@bioperl.org>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2012 by Chris Fields.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)

=cut

