module Data.Internal.Ewkb.Geometry
  ( EwkbGeometryType (..)
  , SridType (..)
  , ewkbGeometryType
  , wkbGeometryType
  ) where

import qualified Control.Monad              as Monad
import qualified Data.Binary.Get            as BinaryGet
import           Data.Bits                  ((.&.))
import qualified Data.Word                  as Word

import qualified Data.Internal.Wkb.Endian   as Endian
import qualified Data.Internal.Wkb.Geometry as Geometry

data SridType = Srid Word.Word32 | NoSrid deriving (Show, Eq)

data EwkbGeometryType = EwkbGeom Geometry.WkbGeometryType SridType deriving (Show, Eq)

ewkbGeometryType :: Endian.EndianType -> BinaryGet.Get EwkbGeometryType
ewkbGeometryType endianType = do
  rawGeometryType <- Endian.fourBytes endianType
  ewkbSrid <- getEwkbSrid endianType rawGeometryType
  geomType <- rawtoWkbGeometryType rawGeometryType
  pure $ EwkbGeom geomType ewkbSrid

wkbGeometryType :: Endian.EndianType -> BinaryGet.Get Geometry.WkbGeometryType
wkbGeometryType endianType = do
  rawGeometryType <- Endian.fourBytes endianType
  _ <- getEwkbSrid endianType rawGeometryType
  rawtoWkbGeometryType rawGeometryType

rawtoWkbGeometryType :: Word.Word32 -> BinaryGet.Get Geometry.WkbGeometryType
rawtoWkbGeometryType rawGeometryType = do
  let geomType = intToGeometryType rawGeometryType
      coordType = intToCoordinateType rawGeometryType
  case geomType of
    Just g -> pure $ Geometry.WkbGeom g coordType
    _      -> Monad.fail $ "Invalid EwkbGeometry: " ++ show rawGeometryType

getEwkbSrid :: Endian.EndianType -> Word.Word32 -> BinaryGet.Get SridType
getEwkbSrid endianType int =
  if int .&. 0x20000000 /= 0 then do
    srid <- Endian.fourBytes endianType
    if srid == 4326 then
      pure $ Srid srid
    else
      Monad.fail $ "Invalid SRID only 4326 supported: " ++ show srid
  else
    pure NoSrid

intToGeometryType :: Word.Word32 -> Maybe Geometry.GeometryType
intToGeometryType int =
  case int .&. 0x0fffffff of
    0 -> Just Geometry.Geometry
    1 -> Just Geometry.Point
    2 -> Just Geometry.LineString
    3 -> Just Geometry.Polygon
    4 -> Just Geometry.MultiPoint
    5 -> Just Geometry.MultiLineString
    6 -> Just Geometry.MultiPolygon
    7 -> Just Geometry.GeometryCollection
    _ -> Nothing

intToCoordinateType :: Word.Word32 -> Geometry.CoordinateType
intToCoordinateType int =
  case (hasZ int, hasM int) of
    (False, False) -> Geometry.TwoD
    (False, True)  -> Geometry.M
    (True, False)  -> Geometry.Z
    (True, True)   -> Geometry.ZM

hasZ :: Word.Word32 -> Bool
hasZ int =
  int .&. 0x80000000 /= 0

hasM :: Word.Word32 -> Bool
hasM int =
  int .&. 0x40000000 /= 0