The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use 5.10.0;
use strict;
use warnings;

use Test::More tests => 43;
use Test::Exception;
use Geo::GoogleMaps::FitBoundsZoomer;

# the zoom levels and coordinates were aquired by using GoogleMaps API.
my @max_google_zoom_test_data = (

    # square map
    {
        width       => 500,
        height      => 500,
        marker_coords_array => 
            [{'lat' => 43.71419491549136,
              'long' => -79.38574782259177},
             {'lat' => 43.71458749889745,
              'long' => -79.38511482126425}],
        zoom_level => 20,
    },

    {
        width       => 500,
        height      => 500,
        marker_coords_array => 
            [{'lat' => 43.54790152241965,
              'long' => -80.43324297911833},
             {'lat' => 43.4886485416885,
              'long' => -80.10021990782927}],
        zoom_level => 11,
    },

   {
        width       => 500,
        height      => 500,
        marker_coords_array =>
            [{'lat' => 47.55390637961721,
              'long' => -55.17110949999999},
             {'lat' => 67.08860849498518,
              'long' => -139.5461095}],
        zoom_level => 3,
    },
    
    # small map
    {
        width       => 218,
        height      => 216,
        marker_coords_array =>
            [{'lat' => 43.61578584092233,
              'long' => -80.28310901171874},
             {'lat' => 44.06744876361351,
              'long' => -79.18996936328124}],
        zoom_level => 8,
    },

    # large map
    {
        width       => 425,
        height      => 460,
        marker_coords_array =>
            [{'lat' => 49.15596920563539,
              'long' => -122.90825969226073},
             {'lat' => 49.18683298472213,
              'long' => -122.83822185046385}],
        zoom_level => 13,
    },

    # large map, 4 nodes
    {
        width       => 425,
        height      => 460,
        marker_coords_array =>
            [{'lat' => 53.459178698913874,
              'long' => -113.62339622027586},
             {'lat' => 53.66472718846658,
              'long' => -113.26496726519774},
             {'lat' => 53.476345900203846,
              'long' => -113.24711448199461},
             {'lat' => 53.52045825624463,
              'long' => -113.80879050738524}],
        zoom_level => 10,
    },

    # tall map 1 (markers at top and bottom)
    {
        width       => 200,
        height      => 500,
        marker_coords_array =>
            [{'lat' => 43.22787770197578,
              'long' => -70.59887534625243},
             {'lat' => 56.388582666287625,
              'long' => -72.84008628375243}],
        zoom_level => 5,
    },

    # tall map 2 (markers on left and right)
    {
        width       => 200,
        height      => 500,
        marker_coords_array =>
            [{'lat' => 51.997297168039665,
              'long' => -63.699461283752434},
             {'lat' => 50.79090410074657,
              'long' => -55.481687846252434}],
        zoom_level => 5,
    },

    
    # wide map
    {
        width       => 550,
        height      => 150,
        marker_coords_array =>
            [{'lat' => 50.596038508785256,
              'long' => -119.11450034625243},
             {'lat' => 52.40131573357405,
              'long' => -95.64770347125243}],
        zoom_level => 5,
    },
    
    # earth map
    {
        width       => 700,
        height      => 800,
        marker_coords_array =>
            [{'lat' => 82.14988053130267,
              'long' => -153.96016399999974},
             {'lat' => -83.33284390134914,
              'long' => 145.57108800000026}],
        zoom_level => 1,
    },

    
    # earth map -> smallest single tile map
    {
        width       => 260,
        height      => 260,
        marker_coords_array =>
            [{'lat' => 82.14988053130267,
              'long' => -153.96016399999974},
             {'lat' => -83.33284390134914,
              'long' => 145.57108800000026}],
        zoom_level => 0,
    },
    
    # single map point
    {
        width       => 260,
        height      => 260,
        marker_coords_array =>
            [{'lat' => -83.33284390134914,
              'long' => 145.57108800000026}],
        zoom_level => 20,
    },
    
    # single map point (middle-earth)
    {
        width       => 260,
        height      => 260,
        marker_coords_array =>
            [{'lat' =>  0,
              'long' => 0}],
        zoom_level => 20,
    },
    
    # edge case - pixel map
    {
        width       => 1,
        height      => 1,
        marker_coords_array =>
            [{'lat' =>  0,
              'long' => 0}],
        zoom_level => 20,
    },

    # edge case - extreme point
    {
        width       => 1,
        height      => 1,
        marker_coords_array =>
            [{'lat'  =>  90,
              'long' => 180}],
        zoom_level => 20,
    },


    # edge case - extreme bounding box
    {
        width       => 1,
        height      => 1,
        marker_coords_array =>
            [{'lat'  =>  90,
              'long' => 180},
             {'lat'  => -90,
              'long' => -180}],
        zoom_level => 0,
    },
);

# generic tests - vary points and map geometry
foreach my $test_case (@max_google_zoom_test_data) {
    my $zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new(
                      points => $test_case->{marker_coords_array}, 
			          width  => $test_case->{width}, 
			          height => $test_case->{height}); 
	is (
        $zoomer->max_bounding_zoom(),
		$test_case->{zoom_level}, 
		'max_bounding_zoom - generic test' 
	);
}

# zoom limit constructor test
my $test_case = {
        width       => 500,
        height      => 500,
        marker_coords_array => 
        [{'lat' => 43.71419491549136,
          'long' => -79.38574782259177},
         {'lat' => 43.71458749889745,
          'long' => -79.38511482126425}],
         zoom_level => 20
    };

my $zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new(
                  points => $test_case->{marker_coords_array}, 
                  width  => $test_case->{width}, 
                  height => $test_case->{height},
                  zoom_limit => 19); 
is (
    $zoomer->max_bounding_zoom(),
    19, 
    'max_bounding_zoom: zoom limit constructor test' 
);

# getter test (no params) max_bounding_zoom
is ($zoomer->max_bounding_zoom(), 19, 'max_bounding_zoom getter test');

# getter test (no params) bounding_box_center
is_deeply ( $zoomer->bounding_box_center(), 
            { lat => 43.7143912071944, long => -79.385431321928 }, 
            'bounding_box_center: getter test');

# update map parameters for existing instance using max_bounding_zoom
$test_case =  {
        width       => 425,
        height      => 460,
        marker_coords_array =>
            [{'lat' => 53.459178698913874,
              'long' => -113.62339622027586},
             {'lat' => 53.66472718846658,
              'long' => -113.26496726519774},
             {'lat' => 53.476345900203846,
              'long' => -113.24711448199461},
             {'lat' => 53.52045825624463,
              'long' => -113.80879050738524}],
        zoom_level => 10,
    };

is ( $zoomer->max_bounding_zoom(
            points => $test_case->{marker_coords_array}, 
            width  => $test_case->{width}, 
            height => $test_case->{height},
            zoom_limit => 20), # reset zoom limit
            $test_case->{zoom_level},
            'max_bounding_zoom: update existing instance params'
   ); 


# retest getter (no params) bounding_box_center
is_deeply ( $zoomer->bounding_box_center(), 
            { lat => 53.5619529436902, long => -113.52795249469 }, 
            'bounding_box_center: retest getter following param update');

# update with middle-earth point
$test_case = {
        width       => 260,
        height      => 260,
        marker_coords_array =>
            [{'lat' =>  0,
              'long' => 0}],
        zoom_level => 20
    };

is ( $zoomer->max_bounding_zoom(
            points => $test_case->{marker_coords_array}, 
            width  => $test_case->{width}, 
            height => $test_case->{height}),
            $test_case->{zoom_level},
            'max_bounding_zoom: update with middle-earth'
   ); 

# test middle-earth bounding_box_center
is_deeply ( $zoomer->bounding_box_center(), 
            { lat => 0, long => 0 }, 
            'bounding_box_center: middle-earth');

# lat < 90
$test_case = {
        marker_coords_array =>
            [{'lat' =>  -91,
              'long' => 180}],
        zoom_level => 20
    };

throws_ok { $zoomer->max_bounding_zoom(points => $test_case->{marker_coords_array}) }
            qr/Point latitude out of bounds \( < -90 or > 90 \)/,
            'max_bounding_zoom: lat < -90 caught'; 

# lat > 90
$test_case = {
        marker_coords_array =>
            [{'lat'  =>  91,
              'long' => 180}],
        zoom_level => 20
    };

throws_ok { $zoomer->max_bounding_zoom(points => $test_case->{marker_coords_array}) }
            qr/Point latitude out of bounds \( < -90 or > 90 \)/,
            'max_bounding_zoom: lat > 90 caught'; 

# long < -180
$test_case = {
        marker_coords_array =>
            [{'lat' =>  90,
              'long' => -181}],
        zoom_level => 20
    };

throws_ok { $zoomer->max_bounding_zoom(points => $test_case->{marker_coords_array}) }
            qr/Point longitude out of bounds \( < -180 or > 180 \)/,
            'max_bounding_zoom: long < -180 caught'; 

# long > 180
$test_case = {
        marker_coords_array =>
            [{'lat' =>  90,
              'long' => 181}],
        zoom_level => 20
    };

throws_ok { $zoomer->max_bounding_zoom(points => $test_case->{marker_coords_array}) }
            qr/Point longitude out of bounds \( < -180 or > 180 \)/,
            'max_bounding_zoom: lat > 180 caught'; 

# no map `points` param
$zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new(
                  width  => 400,
                  height => 400);

throws_ok { $zoomer->max_bounding_zoom() }
            qr/No map points parameter!/,
            'max_bounding_zoom: no map points parameter'; 

# no map `height` param
$zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new(
                  points => [ { lat => 70, long => 70 } ],
                  width  => 400);

throws_ok { $zoomer->max_bounding_zoom() }
            qr/No map height parameter!/,
            'max_bounding_zoom: no map height parameter'; 

# no map `height` param
$zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new(
                  points => [ { lat => 70, lon => 70 } ],
                  height => 400);

throws_ok { $zoomer->max_bounding_zoom() }
            qr/No map width parameter!/,
            'max_bounding_zoom: no map width parameter'; 

# no map points (empty array ref)
$zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new(
                  points => [],
                  width  => 400,
                  height => 400);

throws_ok { $zoomer->max_bounding_zoom() }
            qr/At least one point must be provided!/,
            'max_bounding_zoom: map points undef'; 

# zero map width
$zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new(
                  points => [ { lat => 70, long => 70 } ],
                  width  => 0,
                  height => 400);

throws_ok { $zoomer->max_bounding_zoom() }
            qr/Map width must be a positive number!/,
            'max_bounding_zoom: zero map width'; 

# zero map height
$zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new(
                  points => [ { lat => 70, long => 70 } ],
                  width  => 400,
                  height => 0);

throws_ok { $zoomer->max_bounding_zoom() }
            qr/Map height must be a positive number!/,
            'max_bounding_zoom: zero map height'; 

# negative zoom limit
$zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new(
                  points => [ { lat => 70, long => 70 } ],
                  width  => 400,
                  height => 400,
                  zoom_limit => -1);

throws_ok { $zoomer->max_bounding_zoom() }
            qr/Zoom limit must be greater of equal to 0!/,
            'max_bounding_zoom: zoom limit less than 0'; 

# cluster center - points, but no bounds 
$zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new(
                  points => [ { lat => 70, long => 70 } ],
                  width  => 400,
                  height => 400,
                  zoom_limit => -1);

throws_ok { $zoomer->bounding_box_center() }
            qr/Map data not initialized! max_bounding_zoom needs to be called first/,
            'bounding_box_center: no bounds'; 



# cluster center - no points 
$zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new(
                  width  => 400,
                  height => 400,
                  zoom_limit => -1);

throws_ok { $zoomer->bounding_box_center() }
            qr/Map data not initialized! max_bounding_zoom needs to be called first/,
            'bounding_box_center: no points'; 


my @get_bounds_test_data = (    # top left quadrant
                                {
                                    marker_coords_array =>
                                        [ {'lat' => 81.92, 'long' => -141.33 }, 
                                          {'lat' => 70.07, 'long' => -99.84 },
                                        ],
                                    bounds => { 'blp' => {'lat' => 70.07, 'long' => -141.33}, 'trp' => {'lat' => 81.92, 'long' => -99.84} }, 
                                },

                                # top right quadrant
                                {
                                    marker_coords_array =>
                                        [ {'lat' => 65.37, 'long' => 106.88 }, 
                                          {'lat' => 78.49, 'long' => 151.88 },
                                        ],
                                    bounds => { 'blp' => {'lat' => 65.37, 'long' => 106.88}, 'trp' => {'lat' => 78.49, 'long' => 151.88} }, 
                                },

                                # bottom left quadrant
                                {
                                    marker_coords_array => 
                                        [ {'lat' => -74.59, 'long' => -148.36 }, 
                                          {'lat' => -39.09, 'long' => -67.70 },
                                        ],
                                    bounds => { 'blp' => {'lat' => -74.59, 'long' => -148.36}, 'trp' => {'lat' => -39.09, 'long' => -67.70} },
                                },

                                # bottom right quadrant
                                {
                                    marker_coords_array => 
                                        [ {'lat' => -28.30, 'long' => 135.70 }, 
                                          {'lat' => -84.47, 'long' => 46.40 },
                                        ],
                                    bounds => { 'blp' => {'lat' => -84.47, 'long' => 46.40}, 'trp' => {'lat' => -28.30, 'long' => 135.70} },
                                },
                                
                                # northen hemisphere
                                {   
                                    marker_coords_array => 
                                        [ {'lat' => -67.33, 'long' => -108.28 }, 
                                          {'lat' =>  21.44, 'long' =>  115.34 },
                                        ],

                                    bounds => { 'blp' => {'lat' => -67.33, 'long' => -108.28}, 'trp' => {'lat' => 21.44, 'long' => 115.34} },
                                },

                                # southern hemisphere
                                {
                                    marker_coords_array =>
                                        [ {'lat' => -29.55, 'long' =>  149.32 }, 
                                          {'lat' => -4.91,  'long' => -164.53 },
                                        ],
                                    bounds => { 'blp' => {'lat' => -29.55, 'long' => -164.53}, 'trp' => {'lat' => -4.91, 'long' => 149.32} },
                                },

                                # earth
                                {
                                    marker_coords_array => 
                                        [ {'lat' =>  80.29, 'long' => -146.25 }, 
                                          {'lat' => -76.99, 'long' => -131.48 },
                                        ],
                                    bounds => { 'blp' => {'lat' => -76.99, 'long' => -146.25}, 'trp' => {'lat' => 80.29, 'long' => -131.48} },
                                },
                           );

foreach my $test_case (@get_bounds_test_data) {

    my $zoomer = Geo::GoogleMaps::FitBoundsZoomer::->new( points => $test_case->{marker_coords_array}, width => 400, height => 400 );
    is_deeply (
        $zoomer->_get_bounds(),
        $test_case->{bounds},
        '_get_bounds'
    );
}