{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-| Convert Dhall to YAML

-}
module Dhall.Yaml
    ( Options(..)
    , Dhall.JSON.Yaml.defaultOptions
    , dhallToYaml
    ) where

import Data.ByteString      (ByteString)
import Data.ByteString.Lazy (toStrict)
import Data.Text            (Text)
import Dhall.JSON           (SpecialDoubleMode (..), codeToHeaderAndValue)
import Dhall.JSON.Yaml      (Options (..))
import Dhall.Parser         (Header (..))

import qualified Data.Aeson
import qualified Data.ByteString
import qualified Data.Char          as Char
import qualified Data.Text          as Text
import qualified Data.Text.Encoding
import qualified Data.Vector
import qualified Data.YAML          as Y
import qualified Data.YAML.Aeson
import qualified Data.YAML.Event    as YE
import qualified Data.YAML.Schema   as YS
import qualified Data.YAML.Token    as YT
import qualified Dhall
import qualified Dhall.JSON.Yaml

{-| Convert a piece of 'Text' carrying a Dhall inscription to an equivalent @YAML@ 'ByteString'
-}
dhallToYaml
  :: Options
  -> Maybe FilePath  -- ^ The source file path. If no path is given, imports
                     -- are resolved relative to the current directory.
  -> Text  -- ^ Input text.
  -> IO ByteString
dhallToYaml Options{..} mFilePath code = do

  let explaining = if explain then Dhall.detailed else id

  let adapt (header, value) = (header, omission value)

  (Header comment, json) <- adapt <$> explaining (codeToHeaderAndValue conversion UseYAMLEncoding mFilePath code)

  let suffix
          | preserveHeader = Data.Text.Encoding.encodeUtf8 comment
          | otherwise      = mempty

  let header =
          if noEdit
          then Dhall.JSON.Yaml.generatedCodeNotice <> suffix
          else suffix

  return $ header <> jsonToYaml json documents quoted

-- | Transform json representation into yaml
jsonToYaml
    :: Data.Aeson.Value
    -> Bool
    -> Bool
    -> ByteString
jsonToYaml json documents quoted =
  case (documents, json) of
    (True, Data.Aeson.Array elems) -> document elems
    (True, value) -> document (pure value)
    _ -> Data.ByteString.Lazy.toStrict (encoder [json])
  where
    document elems =
      Data.ByteString.intercalate "\n"
         $ (("---\n" <>) . Data.ByteString.Lazy.toStrict . encoder . (:[])) <$> Data.Vector.toList elems

    style (Y.SStr s)
        | "\n" `Text.isInfixOf` s =
            Right (YE.untagged, YE.Literal YE.Clip YE.IndentAuto, s)
        | quoted || Text.all isNumberOrDateRelated s || isBoolString =
            Right (YE.untagged, YE.SingleQuoted, s)
      where
        -- For backwards compatibility with YAML 1.1, we need to add the following to the set of boolean values:
        -- https://yaml.org/type/bool.html
        isBoolString = Text.length s <= 5 &&
                      Text.toLower s `elem` ["y", "yes", "n", "no", "true", "false", "on", "off"]
        isNumberOrDateRelated c = Char.isDigit c || c == '.' || c == 'e' || c == '-'
    style s =
        YS.schemaEncoderScalar Y.coreSchemaEncoder s

    schemaEncoder = YS.setScalarStyle style Y.coreSchemaEncoder
    encoder = Data.YAML.Aeson.encodeValue' schemaEncoder YT.UTF8
