{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies      #-}
module Data.Morpheus.Parser.Value
  ( parseValue
  , enumValue
  ) where

import           Data.Functor                       (($>))
import           Data.Morpheus.Parser.Internal      (Parser)
import           Data.Morpheus.Parser.Primitive     (token)
import           Data.Morpheus.Parser.Terms         (parseAssignment)
import           Data.Morpheus.Types.Internal.Value (ScalarValue (..), Value (..), decodeScientific)
import           Data.Text                          (pack)
import           Text.Megaparsec                    (between, anySingleBut, choice, label, many, sepBy, (<|>))
import           Text.Megaparsec.Char               (char, space, string)
import           Text.Megaparsec.Char.Lexer         (scientific)

parseValue :: Parser Value
parseValue = label "value" $ do
  value <- valueNull <|> booleanValue <|> valueNumber <|> stringValue <|> objectValue <|> listValue
  space
  return value

valueNull :: Parser Value
valueNull = string "null" $> Null

booleanValue :: Parser Value
booleanValue = boolTrue <|> boolFalse
  where
    boolTrue = string "true" $> Scalar (Boolean True)
    boolFalse = string "false" $> Scalar (Boolean False)

valueNumber :: Parser Value
valueNumber = Scalar . decodeScientific <$> scientific

enumValue :: Parser Value
enumValue = do
  enum <- Enum <$> token
  space
  return enum

escaped :: Parser Char
escaped = label "escaped" $ do
  x <- anySingleBut '\"'
  if x == '\\'
    then choice (zipWith escapeChar codes replacements)
    else pure x
  where
    replacements = ['\b', '\n', '\f', '\r', '\t', '\\', '\"', '/']
    codes = ['b', 'n', 'f', 'r', 't', '\\', '\"', '/']
    escapeChar code replacement = char code >> return replacement

stringValue :: Parser Value
stringValue = label "stringValue" $
  Scalar . String . pack <$>
    between
      (char '"')
      (char '"')
      (many escaped)

listValue :: Parser Value
listValue = label "listValue" $
  List <$> between
             (char '[' *> space)
             (char ']' *> space)
             (parseValue `sepBy` (char ',' *> space))

objectValue :: Parser Value
objectValue = label "objectValue" $
  Object <$> between
               (char '{' *> space)
               (char '}' *> space)
               (parseAssignment token parseValue `sepBy` (char ',' *> space))