{-# LANGUAGE FlexibleInstances #-}

module Data.Geometry.Geos.Raw.CoordSeq (
    CoordinateSequence (..)
  , CoordSeqConst (CoordSeqConst)
  , CoordSeq (CoordSeq)
  , getCoordinateSequenceX
  , getCoordinateSequenceY
  , getCoordinateSequenceZ
  , getCoordinateSequenceSize
  , getCoordinateSequenceDimensions
  , setCoordinateSequenceX
  , setCoordinateSequenceY
  , setCoordinateSequenceZ
  , setCoordinateSequenceOrd
) where

import qualified Data.Geometry.Geos.Raw.Internal as I
import Data.Geometry.Geos.Raw.Base
import Foreign
import Foreign.C.Types
import Control.Monad

class CoordinateSequence a where
  withCoordinateSequence :: a -> (Ptr I.GEOSCoordSequence -> IO b) -> IO b
  createEmptyCoordinateSequence :: Int -> Int -> Geos a
  createCoordinateSequence :: Ptr I.GEOSCoordSequence -> Geos a



newtype CoordSeq = CoordSeq {
  _unCoordSeq :: ForeignPtr I.GEOSCoordSequence
}
newtype CoordSeqConst = CoordSeqConst {
  _unCoordSeqConst :: Ptr I.GEOSCoordSequence
}

instance Eq CoordSeq where
  (==) = coordSeqEq

instance Eq CoordSeqConst where
  (==) = coordSeqEq

coordSeqEq :: CoordinateSequence a => a -> a -> Bool
coordSeqEq a b = runGeos $ do
  sa <- getCoordinateSequenceSize a
  sb <- getCoordinateSequenceSize b
  da <- getCoordinateSequenceDimensions a
  db <- getCoordinateSequenceDimensions b
  if (sa == sb) && (da == db)
      then foldM (comp (da == 3)) True [0..(sa-1)]
      else return False
  where
    comp zdim acc i = do
      xa <- getCoordinateSequenceX a i
      ya <- getCoordinateSequenceY a i
      xb <- getCoordinateSequenceX b i
      yb <- getCoordinateSequenceY b i
      zd <- if zdim
            then do
              za <- getCoordinateSequenceZ a i
              zb <- getCoordinateSequenceZ b i
              return $ za == zb
             else return True
      return $ (xa == xb)  && (ya == yb) && acc && zd

instance Show CoordSeq where
  show = coordSeqShow

instance Show CoordSeqConst where
  show = coordSeqShow

coordSeqShow :: CoordinateSequence a => a -> String
coordSeqShow a = runGeos $ do
  sa <- getCoordinateSequenceSize a
  unlines `fmap` mapM show' [0..(sa-1)]
  where
    show' i = do
      xa <- getCoordinateSequenceX a i
      ya <- getCoordinateSequenceY a i
      return . show $ (xa, ya)


instance CoordinateSequence CoordSeq where
  withCoordinateSequence (CoordSeq fp) f = withForeignPtr fp f
  createEmptyCoordinateSequence size dim = do
    ptr <- withGeos $ \h ->
      throwIfNull "createEmptyCoordinateSequence" $ I.geos_CoordSeqCreate h (fromIntegral size) (fromIntegral dim)
    createCoordinateSequence ptr

  createCoordinateSequence ptr = withGeos $ \h -> do
      fptr <- newForeignPtrEnv I.geos_CoordSeqDestroy h ptr
      return $ CoordSeq fptr

instance CoordinateSequence CoordSeqConst where
  withCoordinateSequence (CoordSeqConst p) f = f p
  createEmptyCoordinateSequence size dim = do
    ptr <- withGeos $ \h ->
      throwIfNull "createEmptyCoordinateSequence" $ I.geos_CoordSeqCreate h (fromIntegral size) (fromIntegral dim)
    createCoordinateSequence ptr
  createCoordinateSequence ptr = return $ CoordSeqConst ptr




getCoordinateSequenceD_ :: CoordinateSequence a
                          => (I.GEOSContextHandle_t -> Ptr I.GEOSCoordSequence -> CUInt -> Ptr CDouble -> IO CInt)
                          -> a
                          -> Int
                          -> Geos Double
getCoordinateSequenceD_ f cs idx = withGeos $ \h ->
  alloca $ \dptr -> do
    _ <- throwIfZero (mkErrorMessage "getCoordiniateSequenceN") $
          withCoordinateSequence cs $ \pcs -> f h pcs (fromIntegral idx) dptr
    d <- peek dptr
    return $ realToFrac d

getCoordinateSequenceX :: CoordinateSequence a => a -> Int -> Geos Double
getCoordinateSequenceX = getCoordinateSequenceD_ I.geos_CoordSeqGetX

getCoordinateSequenceY :: CoordinateSequence a => a -> Int -> Geos Double
getCoordinateSequenceY = getCoordinateSequenceD_ I.geos_CoordSeqGetY

getCoordinateSequenceZ :: CoordinateSequence a => a -> Int -> Geos Double
getCoordinateSequenceZ = getCoordinateSequenceD_ I.geos_CoordSeqGetZ

getCoordinateSequenceSize :: CoordinateSequence a => a -> Geos Int
getCoordinateSequenceSize c =  withGeos $ \h ->
  alloca $ \ptr -> do
    _ <- throwIfZero (mkErrorMessage "getCoordinateSequenceSize") $
          withCoordinateSequence c $ \pc ->
            I.geos_CoordSeqGetSize h pc ptr
    s <- peek ptr
    return $ fromIntegral s

getCoordinateSequenceDimensions :: CoordinateSequence a => a -> Geos Int
getCoordinateSequenceDimensions c = withGeos $ \h ->
  alloca $ \ptr -> do
    _ <- throwIfZero (mkErrorMessage "getCoordinateSeqenceDimensions") $
            withCoordinateSequence c $ \pc ->
              I.geos_CoordSeqGetDimensions h pc ptr
    s <- peek ptr
    return $ fromIntegral s

---
setCoordinateSequence_ ::  CoordinateSequence a
                        => (I.GEOSContextHandle_t -> Ptr I.GEOSCoordSequence -> CUInt -> CDouble -> IO CInt)
                        -> a
                        -> Int
                        -> Double
                        -> Geos ()
setCoordinateSequence_ f cs idx val = withGeos $ \h -> do
  _ <- throwIfZero (mkErrorMessage "setCoordinateSEquenceN") $
        withCoordinateSequence cs $ \pcs ->
          f h pcs (fromIntegral idx) (realToFrac val)
  return  ()


setCoordinateSequenceX :: CoordinateSequence a => a -> Int -> Double -> Geos ()
setCoordinateSequenceX = setCoordinateSequence_ I.geos_CoordSeqSetX

setCoordinateSequenceY :: CoordinateSequence a => a -> Int -> Double -> Geos ()
setCoordinateSequenceY = setCoordinateSequence_ I.geos_CoordSeqSetY

setCoordinateSequenceZ :: CoordinateSequence a => a -> Int -> Double -> Geos ()
setCoordinateSequenceZ = setCoordinateSequence_ I.geos_CoordSeqSetZ

setCoordinateSequenceOrd :: CoordinateSequence a => a -> Int -> Int  -> Double -> Geos Int
setCoordinateSequenceOrd cs idx dim v = withGeos $ \h -> do
  i <- throwIfZero (mkErrorMessage "setCoordinateSequenceN") $
          withCoordinateSequence cs $ \pcs ->
            I.geos_CoordSeqSetOrdinate h pcs (fromIntegral idx) (fromIntegral dim) (realToFrac v)
  return $ fromIntegral i