{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Strict #-}
{-# LANGUAGE Trustworthy #-}

-- | The value reader can handle a delightful mix of binary and
-- textual input.  It is the most general way of reading values, but
-- it is less efficient than using the 'Get' instance if you know that
-- the data will be in the binary format.
module Futhark.Data.Reader
  ( readValues,
  )
where

import Control.Monad
import Data.Binary
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.Char (isPrint, isSpace)
import qualified Data.Text as T
import Futhark.Data
import Futhark.Data.Parser
import qualified Text.Megaparsec as MP
import qualified Text.Megaparsec.Char as MP

dropRestOfLine, dropSpaces :: LBS.ByteString -> LBS.ByteString
dropRestOfLine = LBS.drop 1 . LBS.dropWhile (/= '\n')
dropSpaces t = case LBS.dropWhile isSpace t of
  t'
    | "--" `LBS.isPrefixOf` t' -> dropSpaces $ dropRestOfLine t'
    | otherwise -> t'

readValue :: LBS.ByteString -> Maybe (Value, LBS.ByteString)
readValue full_t
  | Right (t', _, v) <- decodeOrFail full_t =
      Just (v, dropSpaces t')
  -- Some nasty hackery where we take the ASCII prefix of the
  -- bytestring, turn it into a Text, run the value parser, and
  -- prepend the remnant back.
  | otherwise = do
      let (a, b) = LBS.span (\c -> isSpace c || isPrint c) full_t
      case MP.parse
        ((,) <$> parseValue space <*> (MP.stateInput <$> MP.getParserState))
        ""
        (T.pack (LBS.unpack a)) of
        Right (v, a') -> Just (v, LBS.pack (T.unpack a') <> b)
        _ -> Nothing
  where
    space = MP.space *> MP.choice ["--" *> restOfLine *> space, pure ()]
    restOfLine = MP.takeWhileP Nothing (/= '\n') <* MP.choice [void MP.eol, MP.eof]

-- | Parse Futhark values from the given bytestring.
readValues :: LBS.ByteString -> Maybe [Value]
readValues = readValues' . dropSpaces
  where
    readValues' t
      | LBS.null t = Just []
      | otherwise = do
          (a, t') <- readValue t
          (a :) <$> readValues' t'
