""" Tests for geo spatial data types"""
import numpy as np
import pytest
from numpy import testing
from pytest import param

import ibis

pytestmark = pytest.mark.geospatial


# TODO find a way to just run for the backends that support geo, without
# skipping if dependencies are missing
pytest.importorskip('geoalchemy2')
pytest.importorskip('geopandas')
shapely = pytest.importorskip('shapely')

# geo literals declaration
point_0 = ibis.literal((0, 0), type='point').name('tmp')
point_0_4326 = ibis.literal((0, 0), type='point;4326').name('tmp')

point_geom_0 = ibis.literal((0, 0), type='point;4326:geometry').name('p')
point_geom_1 = ibis.literal((1, 1), type='point;4326:geometry').name('p')
point_geom_0_srid0 = ibis.literal((0, 0), type='point;0:geometry').name('p')
point_geom_1_srid0 = ibis.literal((1, 1), type='point;0:geometry').name('p')
point_geom_2 = ibis.literal((2, 2), type='point;4326:geometry').name('p')
point_geog_0 = ibis.literal((0, 0), type='point;4326:geography').name('p')
point_geog_1 = ibis.literal((1, 1), type='point;4326:geography').name('p')
point_geog_2 = ibis.literal((2, 2), type='point;4326:geography').name('p')
polygon_0 = ibis.literal(
    (
        ((1, 0), (0, 1), (-1, 0), (0, -1), (1, 0)),
        ((0.1, 0), (0, 0.1), (-0.1, 0), (0, -0.1), (0.1, 0)),
    ),
    type='polygon;4326:geometry',
).name('p')

# test input data with shapely geometries
shp_point_0 = shapely.geometry.Point(0, 0)
shp_point_1 = shapely.geometry.Point(1, 1)
shp_point_2 = shapely.geometry.Point(2, 2)

shp_linestring_0 = shapely.geometry.LineString(
    [shp_point_0, shp_point_1, shp_point_2]
)
shp_linestring_1 = shapely.geometry.LineString(
    [shp_point_2, shp_point_1, shp_point_0]
)
shp_polygon_0 = shapely.geometry.Polygon(shp_linestring_0)
shp_multilinestring_0 = shapely.geometry.MultiLineString(
    [shp_linestring_0, shp_linestring_1]
)
shp_multipoint_0 = shapely.geometry.MultiPoint(
    [shp_point_0, shp_point_1, shp_point_2]
)
shp_multipolygon_0 = shapely.geometry.MultiPolygon([shp_polygon_0])


@pytest.mark.parametrize(
    ('expr', 'expected'),
    [
        (point_0, {'postgres': "'POINT (0 0)'"}),
        (point_0_4326, {'postgres': "'SRID=4326;POINT (0 0)'"}),
        (point_geom_0, {'postgres': "'SRID=4326;POINT (0 0)'::geometry"}),
        (point_geom_1, {'postgres': "'SRID=4326;POINT (1 1)'::geometry"}),
        (point_geom_2, {'postgres': "'SRID=4326;POINT (2 2)'::geometry"}),
        (point_geog_0, {'postgres': "'SRID=4326;POINT (0 0)'::geography"}),
        (point_geog_1, {'postgres': "'SRID=4326;POINT (1 1)'::geography"}),
        (point_geog_2, {'postgres': "'SRID=4326;POINT (2 2)'::geography"}),
    ],
)
def test_literal_geospatial_explicit(con, expr, expected):
    result = str(con.compile(expr))
    result_expected = f"SELECT {expected['postgres']} AS tmp"
    # use `in` op because if name is specified omniscidb doesn't compile
    # with alias but postgresql does. but if name is not provided,
    # omniscidb uses tmp as a default alias but postgres doesn't use alias
    assert result in result_expected


@pytest.mark.parametrize(
    ('shp', 'expected'),
    [
        (shp_point_0, {'postgres': "'POINT (0 0)'"}),
        (shp_point_1, {'postgres': "'POINT (1 1)'"}),
        (shp_point_2, {'postgres': "'POINT (2 2)'"}),
        (shp_linestring_0, {'postgres': "'LINESTRING (0 0, 1 1, 2 2)'"}),
        (shp_linestring_1, {'postgres': "'LINESTRING (2 2, 1 1, 0 0)'"}),
        (shp_polygon_0, {'postgres': "'POLYGON ((0 0, 1 1, 2 2, 0 0))'"}),
        (
            shp_multipolygon_0,
            {'postgres': "'MULTIPOLYGON (((0 0, 1 1, 2 2, 0 0)))'"},
        ),
    ],
)
def test_literal_geospatial_inferred(con, shp, expected):
    result = str(con.compile(ibis.literal(shp)))
    result_expected = f"SELECT {expected['postgres']} AS tmp"
    # omniscidb uses tmp as a default alias but postgres doesn't use alias
    assert result in result_expected


@pytest.mark.parametrize(
    ('shp', 'expected'),
    [
        (
            shp_multilinestring_0,
            {
                'postgres': (
                    "'MULTILINESTRING ((0 0, 1 1, 2 2), (2 2, 1 1, 0 0))'"
                )
            },
        ),
        (shp_multipoint_0, {'postgres': "'MULTIPOINT (0 0, 1 1, 2 2)'"}),
    ],
)
def test_literal_multi_geospatial_inferred(con, shp, expected):
    result = str(con.compile(ibis.literal(shp)))
    result_expected = f"SELECT {expected['postgres']} AS tmp"
    assert result in result_expected


@pytest.mark.parametrize(
    ('expr_fn', 'expected'),
    [
        param(lambda t: t['geo_linestring'].length(), [1.41] * 5, id='length'),
        param(lambda t: t['geo_point'].x(), [0, 1, 2, 3, 4], id='x'),
        param(lambda t: t['geo_point'].y(), [0, 1, 2, 3, 4], id='y'),
        param(
            lambda t: t['geo_linestring'].x_min(),
            [0, 1, 2, 3, 4],
            id='x_min',
            marks=pytest.mark.notimpl(["postgres"]),
        ),
        param(
            lambda t: t['geo_linestring'].x_max(),
            [1, 2, 3, 4, 5],
            id='x_max',
            marks=pytest.mark.notimpl(["postgres"]),
        ),
        param(
            lambda t: t['geo_linestring'].y_min(),
            [0, 1, 2, 3, 4],
            id='y_min',
            marks=pytest.mark.notimpl(["postgres"]),
        ),
        param(
            lambda t: t['geo_linestring'].y_max(),
            [1, 2, 3, 4, 5],
            id='y_max',
            marks=pytest.mark.notimpl(["postgres"]),
        ),
        param(
            lambda t: t['geo_multipolygon'].n_rings(),
            [2, 3, 1, 1, 1],
            id='n_rings',
            marks=pytest.mark.notimpl(["postgres"]),
        ),
        param(
            lambda t: t['geo_polygon'].set_srid(4326).perimeter(),
            [96.34, 114.36, 10.24, 10.24, 10.24],
            id='perimeter',
            marks=pytest.mark.notimpl(
                ['postgres'], reason='TODO: fix different results issue'