module Data.Internal.Wkb.Line
  ( Data.Internal.Wkb.Line.getLine
  , getMultiLine
  , builderLine
  , builderMultiLine
  ) where

import qualified Control.Monad                        as Monad
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 qualified Data.LineString                      as LineString
import           Data.Monoid                          ((<>))
import qualified Data.Sequence                        as Sequence


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

-- Binary parsers

getLine :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeospatialGeometry
getLine endianType coordType = do
  gl <- getGeoLine endianType coordType
  pure $ Geospatial.Line gl

getMultiLine :: (Endian.EndianType -> BinaryGet.Get Geometry.WkbGeometryType) -> Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeospatialGeometry
getMultiLine getWkbGeom endianType _ = do
  numberOfLines <- Endian.getFourBytes endianType
  geoLines <- Sequence.replicateM (fromIntegral numberOfLines) (GeometryCollection.getEnclosedFeature getWkbGeom Geometry.LineString getGeoLine)
  pure $ Geospatial.MultiLine $ Geospatial.mergeGeoLines geoLines

getGeoLine :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeoLine
getGeoLine endianType coordType = do
  numberOfPoints <- Endian.getFourBytes endianType
  if numberOfPoints >= 2 then do
    p1 <- Point.getCoordPoint endianType coordType
    p2 <- Point.getCoordPoint endianType coordType
    pts <- Point.getCoordPoints endianType coordType (numberOfPoints - 2)
    pure $ Geospatial.GeoLine $ LineString.makeLineString p1 p2 pts
  else
    Monad.fail "Must have at least two points for a line"


-- Binary builders

builderLine :: Geometry.BuilderWkbGeometryType -> Endian.EndianType -> Geospatial.GeoLine -> ByteStringBuilder.Builder
builderLine builderWkbGeom endianType (Geospatial.GeoLine lineString) = do
  let coordPoints = LineString.toSeq lineString
      coordType = Geometry.coordTypeOfSequence coordPoints
  Endian.builderEndianType endianType
    <> builderWkbGeom endianType (Geometry.WkbGeom Geometry.LineString coordType)
    <> Endian.builderFourBytes endianType (fromIntegral $ length coordPoints)
    <> Foldable.foldMap (Point.builderCoordPoint endianType) coordPoints

builderMultiLine :: Geometry.BuilderWkbGeometryType -> Endian.EndianType -> Geospatial.GeoMultiLine -> ByteStringBuilder.Builder
builderMultiLine builderWkbGeom endianType (Geospatial.GeoMultiLine lineStrings) =
  Endian.builderEndianType endianType
    <> builderWkbGeom endianType (Geometry.WkbGeom Geometry.MultiLineString Geometry.TwoD)
    <> Endian.builderFourBytes endianType (fromIntegral $ length lineStrings)
    <> Foldable.foldMap (builderLine builderWkbGeom endianType . Geospatial.GeoLine) lineStrings