use strict;
use warnings;

use lib 'lib';
use lib 't/lib';

use Test::More 'no_plan';

use_ok( 'Geo::Google::PolylineEncoder' );

# RT #49327
# Result of encode_signed_number wrong for small negative numbers
{
    my $test_number = -0.000001;
    my $r = Geo::Google::PolylineEncoder->encode_signed_number($test_number);
    is( $r, chr(63), 'encode_signed_number( -0.000001 ) - RT 49327' );
}

# test the basic encoding functions
{
    my $enc = Geo::Google::PolylineEncoder->new;
    # example from http://code.google.com/apis/maps/documentation/polylinealgorithm.html
    is( $enc->encode_number( 17 ), 'P', 'encode_number: 17' );
    is( $enc->encode_number( 174 ), 'mD', 'encode_number: 174' );
    is( $enc->encode_signed_number( -179.9832104 ), '`~oia@', 'encode_signed_number: -179.9832104' );

    # this example was being encoded differently by both:
    # 'krchI' - http://code.google.com/apis/maps/documentation/polylineutility.html
    # 'irchI' - http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/encodeForm.html
    # double-checked the polyline algorithm docs, seems they changed from using
    # floor() to using round(), so going with the first.
    is( $enc->encode_signed_number( 53.926935 ), 'krchI', 'encode_signed_number: 53.926935' );

    # trying to show that this number was encoded wrong, but it's right... ?
    # yet it appears wrong in Test 2 below?
    #      got: krchIwzo}@EqKa...
    # expected: krchIwzo}@CqKa...
    # why?
    is( $enc->encode_signed_number( 53.92696 ), 'orchI', 'encode_signed_number: 53.92696' );
}

# Test 1 - basic polyline with 3 points
# example from http://code.google.com/apis/maps/documentation/polylinealgorithm.html
{
    my $points = [
		  { lat => 38.5, lon => -120.2 }, # lvl 17
		  { lat => 40.7, lon => -120.95 }, # lvl 16
		  { lat => 43.252, lon => -126.453 }, # lvl 17
		 ];
    my $encoder = Geo::Google::PolylineEncoder->new(zoom_factor => 2, num_levels => 18);
    my $eline   = $encoder->encode( $points );
    is( $eline->{num_levels}, 18, 'ex1 num_levels' );
    is( $eline->{zoom_factor}, 2, 'ex1 zoom_factor' );
    is( $eline->{points}, '_p~iF~ps|U_ulLnnqC_mqNvxq`@', 'ex1 points' );
    is( $eline->{levels}, 'POP', 'ex1 levels' );

    my $d_points = $encoder->decode_points( $eline->{points} );
    my $d_levels = $encoder->decode_levels( $eline->{levels} );
    is( scalar @$d_points, scalar @$d_levels, 'decode: num levels == num points' );
    is_deeply( $d_points, $points, 'decode_points' );
    is_deeply( $d_levels, [ 17, 16, 17 ], 'decode_levels' );
}

# Test 1a - polyline with only 2 points
# (resulting encodings were breaking Google Maps)
{
    my @points = [
		  { lat => 38.5, lon => -120.2 },
		  { lat => 40.7, lon => -120.95 },
		 ];
    my $encoder = Geo::Google::PolylineEncoder->new(zoom_factor => 2, num_levels => 18);
    my $eline   = $encoder->encode( @points );
    is( $eline->{num_levels}, 18, 'ex1a num_levels' );
    is( $eline->{zoom_factor}, 2, 'ex1a zoom_factor' );
    is( $eline->{points}, '_p~iF~ps|U_ulLnnqC', 'ex1a points' );
    is( $eline->{levels}, 'PP', 'ex1a levels' );
}

# Test 1b - arrayref as input
{
    my @points = [ # lat, lon
		  [ 38.5, -120.2 ],
		  [ 40.7, -120.95 ],
		 ];
    my $encoder = Geo::Google::PolylineEncoder->new(zoom_factor => 2, num_levels => 18);
    my $eline   = $encoder->encode( @points );
    is( $eline->{points}, '_p~iF~ps|U_ulLnnqC', 'ex1b points' );
    is( $eline->{levels}, 'PP', 'ex1b levels' );
}

# Test 1c - arrayref as input
{
    my @points = [ # lat, lon
		  [ -120.2, 38.5 ],
		  [ -120.95, 40.7 ],
		 ];
    my $encoder = Geo::Google::PolylineEncoder->new(zoom_factor => 2, num_levels => 18, lons_first => 1 );
    my $eline   = $encoder->encode( @points );
    is( $eline->{points}, '_p~iF~ps|U_ulLnnqC', 'ex1c points' );
    is( $eline->{levels}, 'PP', 'ex1c levels' );
}

# Test 2 - polyline with 12 points that kept on encoding incorrectly because I
# set escape_encoded_line => 1 by default.  This naturally screws things up...
# Also check that visible_threshold has desired effect
{
    my @points = (
		  { lat => 53.926935, lon => 10.244442 },
		  { lat => 53.926960, lon => 10.246454 },
		  { lat => 53.927131, lon => 10.248521 },
		  { lat => 53.927462, lon => 10.250555 },
		  { lat => 53.928056, lon => 10.253243 },
		  { lat => 53.928511, lon => 10.255110 }, # skipped @ default visible_threshold
		  { lat => 53.929217, lon => 10.257998 },
		  { lat => 53.930089, lon => 10.261353 },
		  { lat => 53.930831, lon => 10.263948 },
		  { lat => 53.931672, lon => 10.266299 }, # skipped @ default visible_threshold
		  { lat => 53.932730, lon => 10.269256 },
		  { lat => 53.933209, lon => 10.271115 },
		 );

    my $encoder = Geo::Google::PolylineEncoder->new(zoom_factor => 2, num_levels => 18);
    my $eline   = $encoder->encode( \@points );
    is( $eline->{num_levels}, 18, 'ex2 num_levels' );
    is( $eline->{zoom_factor}, 2, 'ex2 zoom_factor' );
    is( $eline->{points}, 'krchIwzo}@CqKa@}KaAwKwBwOgFw\\mD}SsCgO{Je`@_BqJ', 'ex2 points' ); # bootstrapped
    is( $eline->{levels}, 'PADAEA@CBP', 'ex2 levels' ); # bootstrapped

    my $ipeu = 'krchIwzo}@CqKa@}KaAwKwBwOgFw\\mD}SsCgO{Je`@_BsJ'; # from google
    my $ipeu_points = $encoder->decode_points( $ipeu );
    my $d_points = $encoder->decode_points( $eline->{points} );
    my $d_levels = $encoder->decode_levels( $eline->{levels} );
    is( scalar @$d_points, scalar @$d_levels, 'decode ex2: num levels == num points' );
    is( scalar @$d_points, scalar( @points ) - 2, 'decode ex2: num points == orig num - 2' );
    is( scalar @$d_points, scalar @$ipeu_points, 'decode ex2: num points == num ipeu points' );

  SKIP: {
	eval "use Test::Approx";
	skip 'Test::Approx not available', scalar( @$d_points ) * 2 if $@;

	# compare the decoded & ipeu points, should be only rounding diffs
	for my $i (0 .. $#{$d_points}) {
	    my ($Pa, $Pc) = ($d_points->[$i], $ipeu_points->[$i]);
	    is_approx_num( $Pa->{lon}, $Pc->{lon}, "ex2: d.lon[$i] =~ ipeu.lon[$i]", 1.2e-5 );
	    is_approx_num( $Pa->{lat}, $Pc->{lat}, "ex2: d.lat[$i] =~ ipeu.lat[$i]", 1.2e-5 );
	}
    }


    # now test all points & compare
    $eline = $encoder->visible_threshold( 0.00000001 )->encode( \@points );
    is( $eline->{points}, 'krchIwzo}@CqKa@}KaAwKwBwOyAuJmCaQmD}SsCgOgDuMsEoQ_BqJ', 'ex2 all points' ); # bootstrapped
    is( $eline->{levels}, 'PKMKOEKJMBLP', 'ex2 all levels' );

    my $ipeu_all = 'krchIwzo}@CqKa@}KaAwKwBwOyAuJmCaQmD}SsCgOgDuMsEoQ_BsJ'; # from google
    $ipeu_points = $encoder->decode_points( $ipeu_all );
    $d_points = $encoder->decode_points( $eline->{points} );
    $d_levels = $encoder->decode_levels( $eline->{levels} );
    is( scalar @$d_points, scalar @$d_levels, 'decode ex2 all: num levels == num points' );
    is( scalar @$d_points, scalar( @points ), 'decode ex2 all: num points == orig num' );
    is( scalar @$d_points, scalar @$ipeu_points, 'decode ex2 all: num points == num ipeu points' );

  SKIP: {
	eval "use Test::Approx";
	skip 'Test::Approx not available', scalar( @points ) * 4 if $@;

	# compare the decoded, original & ipeu points, should be only rounding diffs
	for my $i (0 .. $#points) {
	    my ($Pa, $Pb, $Pc) = ($d_points->[$i], $points[$i], $ipeu_points->[$i]);
	    is_approx_num( $Pa->{lon}, $Pb->{lon}, "ex2 all: d.lon[$i] =~ orig.lon[$i]", 1e-5 );
	    is_approx_num( $Pa->{lat}, $Pb->{lat}, "ex2 all: d.lat[$i] =~ orig.lat[$i]", 1e-5 );
	    is_approx_num( $Pa->{lon}, $Pc->{lon}, "ex2 all: d.lon[$i] =~ ipeu.lon[$i]", 1.2e-5 );
	    is_approx_num( $Pa->{lat}, $Pc->{lat}, "ex2 all: d.lat[$i] =~ ipeu.lat[$i]", 1.2e-5 );
	}
    }

}

__END__
