{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE NoImplicitPrelude #-}

{-|
Module      : Headroom.Serialization
Description : Various functions for data (de)serialization
Copyright   : (c) 2019-2020 Vaclav Svejcar
License     : BSD-3-Clause
Maintainer  : vaclav.svejcar@gmail.com
Stability   : experimental
Portability : POSIX

Module providing support for data (de)serialization, mainly from/to /JSON/
and /YAML/.
-}

module Headroom.Serialization
  ( -- * JSON/YAML Serialization
    aesonOptions
  , dropFieldPrefix
  , symbolCase
    -- * Pretty Printing
  , prettyPrintYAML
  )
where

import           Data.Aeson                     ( Options
                                                , ToJSON(..)
                                                , defaultOptions
                                                , fieldLabelModifier
                                                )
import qualified Data.Yaml.Pretty              as YP
import           RIO
import qualified RIO.Char                      as C



-- | Custom /Aeson/ encoding options used for generic mapping between data
-- records and /JSON/ or /YAML/ values. Expects the fields in input to be
-- without the prefix and with words formated in /symbol case/
-- (example: record field @uUserName@, /JSON/ field @user-name@).
aesonOptions :: Options
aesonOptions =
  defaultOptions { fieldLabelModifier = symbolCase '-' . dropFieldPrefix }


-- | Drops prefix from camel-case text.
--
-- >>> dropFieldPrefix "xxHelloWorld"
-- "helloWorld"
dropFieldPrefix :: String -> String
dropFieldPrefix = \case
  (x : n : xs) | C.isUpper x && C.isUpper n -> x : n : xs
  (x : n : xs) | C.isUpper x -> C.toLower x : n : xs
  (_ : xs)                   -> dropFieldPrefix xs
  []                         -> []


-- | Transforms camel-case text into text cased with given symbol.
--
-- >>> symbolCase '-' "fooBar"
-- "foo-bar"
symbolCase :: Char
           -- ^ word separator symbol
           -> String
           -- ^ input text
           -> String
           -- ^ processed text
symbolCase sym = \case
  [] -> []
  (x : xs) | C.isUpper x -> sym : C.toLower x : symbolCase sym xs
           | otherwise   -> x : symbolCase sym xs


-- | Pretty prints given data as /YAML/.
prettyPrintYAML :: ToJSON a
                => a
                -- ^ data to pretty print
                -> Text
                -- ^ pretty printed /YAML/ output
prettyPrintYAML = decodeUtf8Lenient . YP.encodePretty prettyConfig
  where prettyConfig = YP.setConfCompare compare YP.defConfig