module Data.Internal.Wkb.Point
  ( getPoint
  , getMultiPoint
  , getGeoPoint
  , getCoordPoint
  , getCoordPoints
  , builderPoint
  , builderMultiPoint
  , builderCoordPoint
  , builderCoordPoints
  ) where

import qualified Data.Binary.Get                      as BinaryGet
import qualified Data.ByteString.Builder              as ByteStringBuilder
import qualified Data.Foldable                        as Foldable
import qualified Data.Geospatial                      as Geospatial
import           Data.Monoid                          ((<>))
import qualified Data.Monoid                          as Monoid
import qualified Data.Sequence                        as Sequence
import qualified Data.Word                            as Word

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

-- Binary parsers

getPoint :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeospatialGeometry
getPoint endianType coordType = do
  geoPoint <- getGeoPoint endianType coordType
  pure $ Geospatial.Point geoPoint

getMultiPoint :: (Endian.EndianType -> BinaryGet.Get Geometry.WkbGeometryType) -> Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeospatialGeometry
getMultiPoint getWkbGeom endianType _ = do
  numberOfPoints <- Endian.getFourBytes endianType
  geoPoints <- Sequence.replicateM (fromIntegral numberOfPoints) (GeometryCollection.getEnclosedFeature getWkbGeom Geometry.Point getGeoPoint)
  pure $ Geospatial.MultiPoint $ Geospatial.mergeGeoPoints geoPoints

getGeoPoint :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeoPoint
getGeoPoint endianType coordType = do
  p <- getCoordPoint endianType coordType
  pure $ Geospatial.GeoPoint p

getCoordPoint :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeoPositionWithoutCRS
getCoordPoint endianType coordType =
  case coordType of
    Geometry.TwoD -> do
      point <- Geospatial.PointXY <$> getDouble <*> getDouble
      return $ Geospatial.GeoPointXY point
    Geometry.Z -> do
      point <- Geospatial.PointXYZ <$> getDouble <*> getDouble <*> getDouble
      return $ Geospatial.GeoPointXYZ point
    Geometry.M -> do
      point <- Geospatial.PointXYZ <$> getDouble <*> getDouble <*> getDouble
      return $ Geospatial.GeoPointXYZ point
    Geometry.ZM -> do
      point <- Geospatial.PointXYZM <$> getDouble <*> getDouble <*> getDouble <*> getDouble
      return $ Geospatial.GeoPointXYZM point
  where getDouble = Endian.getDouble endianType

getCoordPoints :: Endian.EndianType -> Geometry.CoordinateType -> Word.Word32 -> BinaryGet.Get (Sequence.Seq Geospatial.GeoPositionWithoutCRS)
getCoordPoints endianType coordType numberOfPoints =
  Sequence.replicateM (fromIntegral numberOfPoints) (getCoordPoint endianType coordType)


-- Binary builders

builderPoint :: Geometry.BuilderWkbGeometryType -> Endian.EndianType -> Geospatial.GeoPoint -> ByteStringBuilder.Builder
builderPoint builderWkbGeom endianType (Geospatial.GeoPoint coordPoint) =
  case Geometry.geoPositionWithoutCRSToCoordinateType coordPoint of
    Just coordinateType ->
      Endian.builderEndianType endianType
        <> builderWkbGeom endianType (Geometry.WkbGeom Geometry.Point coordinateType)
        <> builderCoordPoint endianType coordPoint
    Nothing ->
      Monoid.mempty

builderMultiPoint :: Geometry.BuilderWkbGeometryType -> Endian.EndianType -> Geospatial.GeoMultiPoint -> ByteStringBuilder.Builder
builderMultiPoint builderWkbGeom endianType (Geospatial.GeoMultiPoint coordPoints) =
  Endian.builderEndianType endianType
    <> builderWkbGeom endianType (Geometry.WkbGeom Geometry.MultiPoint coordType)
    <> Endian.builderFourBytes endianType (fromIntegral $ length coordPoints)
    <> Foldable.foldMap (builderPoint builderWkbGeom endianType . Geospatial.GeoPoint) coordPoints
  where coordType = Geometry.coordTypeOfSequence coordPoints

builderCoordPoint :: Endian.EndianType -> Geospatial.GeoPositionWithoutCRS -> ByteStringBuilder.Builder
builderCoordPoint endianType coordPoint =
  case coordPoint of
    Geospatial.GeoEmpty -> Monoid.mempty
    Geospatial.GeoPointXY (Geospatial.PointXY x y) ->
      Foldable.foldMap builderDouble [x, y]
    Geospatial.GeoPointXYZ (Geospatial.PointXYZ x y z) ->
      Foldable.foldMap builderDouble [x, y, z]
    Geospatial.GeoPointXYZM (Geospatial.PointXYZM x y z m) ->
      Foldable.foldMap builderDouble [x, y, z, m]
  where builderDouble = Endian.builderDouble endianType

builderCoordPoints :: Endian.EndianType -> Sequence.Seq Geospatial.GeoPositionWithoutCRS -> ByteStringBuilder.Builder
builderCoordPoints endianType =
  Foldable.foldMap (builderCoordPoint endianType)