{-# LANGUAGE CPP #-}
module Data.Geometry.Geos.Raw.Topology (
    envelope
  , intersection
  , convexHull
  , difference
  , symmetricDifference
  , boundary
  , union
  , unaryUnion
  , pointOnSurface
  , centroid
  , node
  , delaunayTriangulation
  , voronoiDiagram
  , polygonize
  , minimumRotatedRectangle
  , minimumWidth
  , minimumClearanceLine
  , minimumClearance
) where
import qualified Data.Geometry.Geos.Raw.Internal as I
import Data.Geometry.Geos.Raw.Base
import qualified Data.Geometry.Geos.Raw.Geometry as R

import Foreign hiding (throwIfNull, throwIf)

geo_1 :: R.Geometry a 
      => (I.GEOSContextHandle_t -> Ptr I.GEOSGeometry -> IO (Ptr I.GEOSGeometry)) 
      -> String 
      -> a
      -> Geos a
geo_1 f s g = withGeos' $ \h -> do
  eitherPtr <- throwIfNull' s $ R.withGeometry g $ f h 
  traverse (R.constructGeometry h) eitherPtr

geo_2 :: R.Geometry a
      => (I.GEOSContextHandle_t -> Ptr I.GEOSGeometry -> Ptr I.GEOSGeometry -> IO (Ptr I.GEOSGeometry))
      -> String
      -> a
      -> a
      -> Geos a
geo_2 f s g1 g2  =  withGeos' $ \h -> do
  eitherPtr <- throwIfNull' s $  R.withGeometry g1 $ \gp -> 
      R.withGeometry g2 (f h gp)
  traverse (R.constructGeometry h) eitherPtr



envelope :: R.Geometry a => a -> Geos a
envelope = geo_1 I.geos_Envelope "envelope" 

intersection :: R.Geometry a => a -> a -> Geos a
intersection = geo_2 I.geos_Intersection "intersection"

convexHull :: R.Geometry a => a -> Geos a
convexHull = geo_1 I.geos_ConvexHull "convexHull"

difference :: R.Geometry a => a -> a -> Geos a
difference = geo_2 I.geos_Difference "difference"

symmetricDifference :: R.Geometry a => a -> a-> Geos a
symmetricDifference = geo_2 I.geos_SymDifference "symmetricDifference"

boundary :: R.Geometry a => a -> Geos a
boundary = geo_1 I.geos_Boundary "boundary"

union :: R.Geometry a => a -> a -> Geos a
union = geo_2 I.geos_Union "union"

unaryUnion :: R.Geometry a => a -> Geos a
unaryUnion = geo_1 I.geos_UnaryUnion "unaryUnion"

pointOnSurface :: R.Geometry a => a -> Geos a
pointOnSurface = geo_1 I.geos_PointsOnSurface "pointOnSurface"

centroid :: R.Geometry a => a -> Geos a
centroid = geo_1 I.geos_GetCentroid "getCentroid"

node :: R.Geometry a => a -> Geos a
node = geo_1 I.geos_Node "node"

{-
 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 :: R.Geometry a => [ a ] -> Geos a
polygonize geoms = withGeos' $ \h -> alloca $ \arrPtr -> do
  _ <- traverse (writeIndexed arrPtr) $ [0 ..] `zip` geoms
  eitherPtr <- throwIfNull' "polygonize" $ I.geos_Polygonize_valid h arrPtr $ fromIntegral (length geoms)
  traverse (R.constructGeometry h) eitherPtr 
  where
    writeIndexed arrayPtr (idx, geom) = R.withGeometry geom $ \geoPtr -> pokeElemOff arrayPtr idx geoPtr


{-
 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) a LINESTRING or POINT is returned. The minimum rotated rectangle can be used as an extremely generalized representation for the given geometry.
-}
minimumRotatedRectangle :: R.Geometry a => a -> Geos a
minimumRotatedRectangle = geo_1 I.geos_MinimumRotatedRectangle "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 :: R.Geometry a => a -> Geos a
minimumWidth = geo_1 I.geos_MinimumWidth "minimumWidth"

-- | Returns a LineString whose endpoints define the minimum clearance of a geometry. If the geometry has no minimum clearance, an empty LineString will be returned.

minimumClearanceLine :: R.Geometry a => a -> Geos a
minimumClearanceLine = geo_1 I.geos_MinimumClearanceLine "minimumClearanceLine"

-- | Computes the minimum clearance of a geometry.  The minimum clearance is the smallest amount by which  a vertex could be move to produce an invalid polygon, a non-simple linestring, or a multipoint with  repeated points.  If a geometry has a minimum clearance of 'eps', it can be said that:
-- |  -  No two distinct vertices in the geometry are separated by less than 'eps'
-- |  -  No vertex is closer than 'eps' to a line segment of which it is not an endpoint.
--  If the minimum clearance cannot be defined for a geometry (such as with a single point, or a multipoint whose points are identical, a value of Infinity will be calculated.
minimumClearance :: R.Geometry a => a -> Geos Double
minimumClearance geom = withGeos' $ \h ->
  R.withGeometry geom $ \gptr ->  alloca $ \dptr ->  do
    eitherInt <-  throwIf' (0 /=) (mkErrorMessage "minimumClearance") $ I.geos_MinimumClearance h gptr dptr
    traverse (\_ -> realToFrac <$> peek dptr) eitherInt


-- | Return a Delaunay triangulation of the vertex of the given geometry @g@, where @tol@ is  the snapping tolerance to use.
delaunayTriangulation :: R.Geometry a 
                      => a 
                      -> Double 
                      -> Bool
                      -> Geos a
delaunayTriangulation g tol onlyEdges = withGeos' $ \h -> do
  eitherPtr <- throwIfNull' "delaunayTriangulation" $ R.withGeometry g $ \gp -> 
      I.geos_DelaunayTriangulation h gp (realToFrac tol) $ fromBool onlyEdges
  traverse (R.constructGeometry h) eitherPtr
            
voronoiDiagram :: R.Geometry a => a -> Maybe a -> Double -> Bool -> Geos a
voronoiDiagram g menv tol oe = withGeos' $ \h -> do
  eitherPtr <- throwIfNull' "voronoiDiagram" $ R.withGeometry g $ \gp -> 
    R.withMaybeGeometry menv $ \ep -> I.geos_VoronoiDiagram h gp ep (realToFrac tol) $ fromBool oe 
  traverse (R.constructGeometry h) eitherPtr