content: {- |
  Module                  : Toml.Codec.Combinator.List
  Copyright               : (c) 2018-2022 Kowainik
  SPDX-License-Identifier : MPL-2.0
  Maintainer              : Kowainik <xrom.xkov@gmail.com>
  Stability               : Stable
  Portability             : Portable
  
  TOML-specific combinators for converting between TOML and Haskell list-like data
  types.
  
  There are two way to represent list-like structures with the @tomland@ library.
  
  * Ordinary array lists of primitives:
  
      @
      foo = [100, 200, 300]
      @
  
  * Lists via tables:
  
      @
      foo =
          [ {x = 100}
          , {x = 200}
          , {x = 300}
          ]
  
      __OR__
  
      [[foo]]
          x = 100
      [[foo]]
          x = 200
      [[foo]]
          x = 300
      @
  
  You can find both types of the codecs in this module for different list-like
  structures. See the following table for the better understanding:
  
  +-------------------------+----------------------------------+-------------------------------------+
  |      Haskell Type       |              @TOML@              |             'TomlCodec'             |
  +=========================+==================================+=====================================+
  | __@['Int']@__           | @a = [1, 2, 3]@                  | @'arrayOf' 'Toml._Int' "a"@         |
  +-------------------------+----------------------------------+-------------------------------------+
  | __@'NonEmpty' 'Int'@__  | @a = [11, 42]@                   | @'arrayNonEmptyOf' 'Toml._Int' "a"@ |
  +-------------------------+----------------------------------+-------------------------------------+
  | __@['Text']@__          | @x = [{a = "foo"}, {a = "bar"}]@ | @'list' ('Toml.text' "a") "x"@      |
  +-------------------------+----------------------------------+-------------------------------------+
  | __@'NonEmpty' 'Text'@__ | @x = [{a = "foo"}, {a = "bar"}]@ | @'nonEmpty' ('Toml.text' "a") "x"@  |
  +-------------------------+----------------------------------+-------------------------------------+
  
  @since 1.3.0.0
  -}
  
  module Toml.Codec.Combinator.List
      ( -- * Array lists
        arrayOf
      , arrayNonEmptyOf
  
        -- * Table lists
      , list
      , nonEmpty
      ) where
  
  import Control.Monad.State (gets, modify)
  import Data.List.NonEmpty (NonEmpty (..), toList)
  import Validation (Validation (..))
  
  import Toml.Codec.BiMap (TomlBiMap)
  import Toml.Codec.BiMap.Conversion (_Array, _NonEmpty)
  import Toml.Codec.Code (execTomlCodec)
  import Toml.Codec.Combinator.Common (match)
  import Toml.Codec.Combinator.Table (handleTableErrors)
  import Toml.Codec.Error (TomlDecodeError (..))
  import Toml.Codec.Types (Codec (..), TomlCodec, TomlEnv, TomlState)
  import Toml.Type.AnyValue (AnyValue (..))
  import Toml.Type.Key (Key)
  import Toml.Type.TOML (TOML (..), insertTableArrays)
  
  import qualified Data.HashMap.Strict as HashMap
  
  
  {- | Codec for list of values. Takes converter for single value and
  returns a list of values.
  
  __Example:__
  
  Haskell @['Int']@ can look like this in your @TOML@ file:
  
  @
  foo = [1, 2, 3]
  @
  
  If the key is not present in @TOML@ the following decode error will be spotted:
  
  @
  tomland decode error:  Key foo is not found
  @
  
  @since 0.1.0
  -}
  arrayOf :: TomlBiMap a AnyValue -> Key -> TomlCodec [a]
  arrayOf = match . _Array
  {-# INLINE arrayOf #-}
  
  {- | Codec for non- empty lists of values. Takes converter for single value and
  returns a non-empty list of values.
  
  __Example:__
  
  Haskell @'NonEmpty' 'Int'@ can look like this in your @TOML@ file:
  
  @
  foo = [1, 2, 3]
  @
  
  If you try to decode an empty @TOML@ list you will see the error:
  
  @
  tomland decode error:  Empty array list, but expected NonEmpty
  @
  
  If the key is not present in @TOML@ the following decode error will be spotted:
  
  @
  tomland decode error:  Key foo is not found
  @
  
  @since 0.5.0
  -}
  arrayNonEmptyOf :: TomlBiMap a AnyValue -> Key -> TomlCodec (NonEmpty a)
  arrayNonEmptyOf = match . _NonEmpty
  {-# INLINE arrayNonEmptyOf #-}
  
  {- | 'Codec' for list of values. Represented in TOML as array of tables.
  
  __Example:__
  
  Haskell @['Int']@ can look like this in your @TOML@ file:
  
  @
  foo =
    [ {a = 1}
    , {a = 2}
    , {a = 3}
    ]
  @
  
  Decodes to an empty list @[]@ when the key is not present.
  
  @since 1.0.0
  -}
  list :: forall a . TomlCodec a -> Key -> TomlCodec [a]
  list codec key = Codec
      { codecRead = \toml -> case codecRead nonEmptyCodec toml of
          Success ne -> Success $ toList ne
          Failure [TableArrayNotFound errKey]
              | errKey == key -> pure []
          Failure errs -> Failure errs
      , codecWrite = \case
          [] -> pure []
          l@(x:xs) -> l <$ codecWrite nonEmptyCodec (x :| xs)
      }
    where
      nonEmptyCodec :: TomlCodec (NonEmpty a)
      nonEmptyCodec = nonEmpty codec key
  
  
  {- | 'Codec' for 'NonEmpty' list of values. Represented in TOML as array of
  tables.
  
  __Example:__
  
  Haskell @'NonEmpty' 'Int'@ can look like this in your @TOML@ file:
  
  @
  foo =
    [ {a = 1}
    , {a = 2}
    , {a = 3}
    ]
  @
  
  If you try to decode an empty @TOML@ list you will see the error:
  
  @
  tomland decode error:  Table array [[foo]] is not found
  @
  
  or
  
  @
  tomland decode error:  Key foo.a is not found
  @
  
  If the key is not present in @TOML@ the following decode error will be spotted:
  
  @
  tomland decode error:  Table array [[foo]] is not found
  @
  
  @since 1.0.0
  -}
  nonEmpty :: forall a . TomlCodec a -> Key -> TomlCodec (NonEmpty a)
  nonEmpty codec key = Codec input output
    where
      input :: TomlEnv (NonEmpty a)
      input = \t -> case HashMap.lookup key $ tomlTableArrays t of
          Nothing    -> Failure [TableArrayNotFound key]
          Just tomls -> traverse (handleTableErrors codec key) tomls
  
  
      -- adds all TOML objects to the existing list if there are some
      output :: NonEmpty a -> TomlState (NonEmpty a)
      output as = do
          let tomls = fmap (execTomlCodec codec) as
          mTables <- gets $ HashMap.lookup key . tomlTableArrays
  
          let newTomls = case mTables of
                  Nothing       -> tomls
                  Just oldTomls -> oldTomls <> tomls
  
          as <$ modify (insertTableArrays key newTomls)
  