{-# LANGUAGE CPP #-}

{- |
Module                  : Toml.Codec.Combinator.Set
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 Set-like data
types.

There are two way to represent list-like structures with the @tomland@ library.

* Ordinary array sets of primitives:

    @
    foo = [100, 200, 300]
    @

* Sets 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 set-like
structures. See the following table for the better understanding:

+------------------------+----------------------------------+-------------------------------------+
|      Haskell Type      |              @TOML@              |             'TomlCodec'             |
+========================+==================================+=====================================+
| __@'Set' 'Text'@__     | @a = ["foo", "bar", "baz"]@      | @'arraySetOf' 'Toml._Text' "a"@     |
+------------------------+----------------------------------+-------------------------------------+
| __'IntSet'__           | @a = [11, 42]@                   | @'arrayIntSet' "a"@                 |
+------------------------+----------------------------------+-------------------------------------+
| __@'HashSet' 'Text'@__ | @a = ["foo", "bar"]@             | @'arrayHashSetOf' 'Toml._Text' "a"@ |
+------------------------+----------------------------------+-------------------------------------+
| __@'Set' 'Text'@__     | @x = [{a = "foo"}, {a = "bar"}]@ | @'set' ('Toml.text' "a") "x"@       |
+------------------------+----------------------------------+-------------------------------------+
| __@'HashSet' 'Text'@__ | @x = [{a = "foo"}, {a = "bar"}]@ | @'hashSet' ('Toml.text' "a") "x"@   |
+------------------------+----------------------------------+-------------------------------------+

@since 1.3.0.0
-}

module Toml.Codec.Combinator.Set
    ( -- * Array sets
      arraySetOf
    , arrayIntSet
    , arrayHashSetOf
      -- * Table Sets
    , set
    , intSet
    , hashSet
    ) where

import Data.Hashable (Hashable)
import Data.HashSet (HashSet)
import Data.IntSet (IntSet)
import Data.Set (Set)

import Toml.Codec.BiMap (TomlBiMap)
import Toml.Codec.BiMap.Conversion (_HashSet, _IntSet, _Set)
import Toml.Codec.Combinator.Common (match)
import Toml.Codec.Combinator.List (list)
import Toml.Codec.Di (dimap)
import Toml.Codec.Types (TomlCodec)
import Toml.Type.AnyValue (AnyValue (..))
import Toml.Type.Key (Key)

import qualified Data.HashSet as HS
import qualified Data.IntSet as IS
import qualified Data.Set as S


{- | Codec for sets. Takes converter for single value and
returns a set of values.

__Example:__

Haskell @'Set' 'Int'@ can look like this in your @TOML@ file:

@
foo = [1, 2, 3]
@

In case of the missing field, the following error will be seen:

@
tomland decode error:  Key foo is not found
@

@since 0.5.0
-}
arraySetOf :: Ord a => TomlBiMap a AnyValue -> Key -> TomlCodec (Set a)
arraySetOf = match . _Set
{-# INLINE arraySetOf #-}

{- | Codec for sets of ints. Takes converter for single value and
returns a set of ints.

__Example:__

Haskell @'IntSet'@ can look like this in your @TOML@ file:

@
foo = [1, 2, 3]
@

In case of the missing field, the following error will be seen:

@
tomland decode error:  Key foo is not found
@

@since 0.5.0
-}
arrayIntSet :: Key -> TomlCodec IntSet
arrayIntSet = match _IntSet
{-# INLINE arrayIntSet #-}

{- | Codec for hash sets. Takes converter for single hashable value and
returns a set of hashable values.

__Example:__

Haskell @'HashSet' 'Int'@ can look like this in your @TOML@ file:

@
foo = [1, 2, 3]
@

In case of the missing field, the following error will be seen:

@
tomland decode error:  Key foo is not found
@

@since 0.5.0
-}
arrayHashSetOf
#if MIN_VERSION_hashable(1,4,0)
  :: (Hashable a)
#else
  :: (Eq a, Hashable a)
#endif
    => TomlBiMap a AnyValue
    -> Key
    -> TomlCodec (HashSet a)
arrayHashSetOf = match . _HashSet
{-# INLINE arrayHashSetOf #-}

----------------------------------------------------------------------------
-- Tables and arrays of tables
----------------------------------------------------------------------------

{- | 'Codec' for set of values. Represented in TOML as array of tables.

__Example:__

Haskell @'Set' 'Int'@ can look like this in your @TOML@ file:

@
foo =
  [ {a = 1}
  , {a = 2}
  ]
@

Decodes to an empty 'Set' in case of the missing field in @TOML@.

@since 1.2.0.0
-}
set :: forall a . Ord a => TomlCodec a -> Key -> TomlCodec (Set a)
set codec key = dimap S.toList S.fromList (list codec key)
{-# INLINE set #-}

{- | 'Codec' for 'IntSet'. Represented in TOML as an array of tables.

__Example:__

Haskell 'IntSet' can look like this in your @TOML@ file:

@
foo =
  [ {a = 1}
  , {a = 2}
  ]
@

Decodes to an empty 'IntSet' in case of the missing field in @TOML@.

@since 1.3.0.0
-}
intSet :: TomlCodec Int -> Key -> TomlCodec IntSet
intSet codec key = dimap IS.toList IS.fromList (list codec key)
{-# INLINE intSet #-}

{- | 'Codec' for 'HashSet' of values. Represented in TOML as an array of tables.

__Example:__

Haskell @'HashSet' 'Int'@ can look like this in your @TOML@ file:

@
foo =
  [ {a = 1}
  , {a = 2}
  ]
@

Decodes to an empty 'HashSet' in case of the missing field in @TOML@.

@since 1.2.0.0
-}
hashSet 
    :: forall a 
#if MIN_VERSION_hashable(1,4,0)
    .  (Hashable a)
#else
    .  (Eq a, Hashable a)
#endif
    => TomlCodec a 
    -> Key 
    -> TomlCodec (HashSet a)
hashSet codec key = dimap HS.toList HS.fromList (list codec key)
{-# INLINE hashSet #-}
