{-# LANGUAGE ScopedTypeVariables #-}
module Data.Geometry.Geos.Topology
  ( envelope
  , intersection
  , convexHull
  , difference
  , symmetricDifference
  , boundary
  , union
  , unaryUnion
  , pointOnSurface
  , centroid
  , node
  , delaunayTriangulation
  , voronoiDiagram
  , polygonize
  , minimumRotatedRectangle
  , minimumClearance
  , minimumClearanceLine
  , minimumWidth
  )
where

import           Control.Monad
import qualified Data.Vector                   as V
import           Data.Geometry.Geos.Raw.Base
import           Data.Geometry.Geos.Geometry
import qualified Data.Geometry.Geos.Raw.Topology
                                               as R
import qualified Data.Geometry.Geos.Raw.Geometry
                                               as RG


geo_1
  :: (RG.GeomConst -> Geos RG.GeomConst) -> Geometry a -> Maybe (Some Geometry)
geo_1 f g = runGeosM $ do
  geo <- convertGeometryToRaw g
  convertGeometryFromRaw =<< f geo

geo_2
  :: (RG.GeomConst -> RG.GeomConst -> Geos RG.GeomConst)
  -> Geometry a
  -> Geometry b
  -> Maybe (Some Geometry)
geo_2 f g1 g2 = runGeosM $ do
  g1' <- convertGeometryToRaw g1
  g2' <- convertGeometryToRaw g2
  convertGeometryFromRaw =<< f g1' g2'

-- | Returns a Polygon that represents the bounding envelope of this geometry. Note that it can also return a Point if the input geometry is a point.
envelope :: Geometry a -> Maybe (Some Geometry)
envelope = geo_1 R.envelope

-- | Returns a Geometry representing the points shared by both geometries.
intersection :: Geometry a -> Geometry b -> Maybe (Some Geometry)
intersection = geo_2 R.intersection

-- | Returns the smallest Polygon that contains all the points in the geometry.
convexHull :: Geometry a -> Maybe (Geometry Polygon)
convexHull g = ensurePolygon =<< geo_1 R.convexHull g

-- | Returns a Geometry representing the points making up this geometry that do not make up other.
difference :: Geometry a -> Geometry b -> Maybe (Some Geometry)
difference = geo_2 R.difference

-- | Returns a Geometry combining the points in this geometry not in other, and the points in other not in this geometry.
symmetricDifference :: Geometry a -> Geometry b -> Maybe (Some Geometry)
symmetricDifference = geo_2 R.symmetricDifference

boundary :: Geometry a -> Maybe (Some Geometry)
boundary = geo_1 R.boundary

-- | Returns a Geometry representing all the points in both geometries.

{-| Computes the union of all the elements of this geometry. Heterogeneous GeometryCollections are fully supported.

The result obeys the following contract:

Unioning a set of LineStrings has the effect of fully noding and dissolving the linework.
Unioning a set of Polygons will always return a Polygonal geometry (unlike {link #union(Geometry)}, which may return geometrys of lower dimension if a topology collapse occurred.
-}
union :: Geometry a -> Geometry b -> Maybe (Some Geometry)
union = geo_2 R.union


unaryUnion :: Geometry a -> Maybe (Some Geometry)
unaryUnion = geo_1 R.unaryUnion

-- | Computes and returns a Point guaranteed to be on the interior of this geometry.
pointOnSurface :: Geometry a -> Maybe (Geometry Point)
pointOnSurface = ensurePoint <=< geo_1 R.pointOnSurface

-- | Returns a Point object representing the geometric center of the geometry. The point is not guaranteed to be on the interior of the geometry.
centroid :: Geometry a -> Maybe (Geometry Point)
centroid = ensurePoint <=< geo_1 R.centroid

node :: Geometry a -> Maybe (Some Geometry)
node = geo_1 R.node

-- | Return a Delaunay triangulation of the vertex of the given geometry @g@, where @tol@ is  the snapping tolerance to use.
delaunayTriangulation :: Geometry a 
                      -> Double 
                      -> Bool
                      -> Maybe (Some Geometry)
delaunayTriangulation g d onlyEdges =
  geo_1 (\g' -> R.delaunayTriangulation g' d onlyEdges) g

-- | Returns the Voronoi polygons of a set of Vertices given as input
voronoiDiagram :: Geometry a          -- ^ the input geometry whose vertex will be used as sites
               -> Maybe (Geometry b)  -- ^ clipping envelope for the returned diagram, automatically determined if Nothing. The diagram will be clipped to the larger of this envelope or an envelope surrounding the sites 
               -> Double              -- ^ snapping tolerance to use for improved robustness
               -> Bool                -- ^ whether to return only edges of the Voronoi cells
               -> Some Geometry
voronoiDiagram g menv tol onlyEdges = runGeos $ do
  g' :: RG.GeomConst <- convertGeometryToRaw g
  env'               <- traverse convertGeometryToRaw menv
  convertGeometryFromRaw =<< R.voronoiDiagram g' env' tol onlyEdges

{-
 Polygonizes a set of Geometries which contain linework that
 represents the edges of a planar graph.
 
 All types of Geometry are accepted as input; the constituent
 linework is extracted as the edges to be polygonized.
 
 The edges must be correctly noded; that is, they must only meet
 at their endpoints.  The set of extracted polygons
 is guaranteed to be edge-disjoint. This is useful when it is known
 that the input lines form a valid polygonal geometry (which may
 include holes or nested polygons).
-}
polygonize :: V.Vector (Geometry a) -> Maybe (Geometry Polygon)
polygonize vec =
  ensurePolygon
    =<< (runGeosM $ do
          listGeom :: [RG.Geom] <- traverse convertGeometryToRaw $ V.toList vec
          pgon                  <- R.polygonize listGeom
          convertGeometryFromRaw pgon
        )

{-
 Returns the minimum rotated rectangular POLYGON which encloses the input geometry. The rectangle has width equal to the minimum diameter, and a longer length. If the convex hull of the input is degenerate (a line or point) Nothing is returned. The minimum rotated rectangle can be used as an extremely generalized representation for the given geometry.
-}

minimumRotatedRectangle :: Geometry a -> Maybe (Geometry Polygon)
minimumRotatedRectangle = ensurePolygon <=< geo_1 R.minimumRotatedRectangle

-- | Returns a LINESTRING geometry which represents the minimum diameter of the geometry. The minimum diameter is defined to be the width of the smallest band that contains the geometry, where a band is a strip of the plane defined  by two parallel lines. This can be thought of as the smallest hole that the geometry  can be moved through, with a single rotation.
minimumWidth :: Geometry a -> Maybe (Geometry LineString)
minimumWidth = ensureLineString <=< geo_1 R.minimumWidth


minimumClearance :: Geometry a -> Maybe Double
minimumClearance geom = runGeosM $ do
  raw :: RG.Geom <- convertGeometryToRaw geom
  R.minimumClearance raw


minimumClearanceLine :: Geometry a -> Maybe (Geometry LineString)
minimumClearanceLine = ensureLineString <=< geo_1 R.minimumClearanceLine