content: {-# OPTIONS -Wno-unused-top-binds #-}
  
  {-# LANGUAGE DeriveAnyClass #-}
  {-# LANGUAGE DerivingVia    #-}
  
  module Main (main) where
  
  import Control.Applicative ((<|>))
  import Control.Arrow ((>>>))
  import Data.ByteString (ByteString)
  import Data.HashSet (HashSet)
  import Data.Hashable (Hashable)
  import Data.IntSet (IntSet)
  import Data.List.NonEmpty (NonEmpty)
  import Data.Map.Strict (Map)
  import Data.Set (Set)
  import Data.Text (Text)
  import Data.Time (fromGregorian)
  import GHC.Generics (Generic)
  
  import Toml (TomlCodec, TomlParseError (..), pretty, (.=), (<!>))
  import Toml.Codec.Generic (ByteStringAsBytes (..), HasCodec (..), TomlTableStrip (..),
                             stripTypeNameCodec)
  import Toml.Type (TOML (..), Value (..))
  import Toml.Type.Edsl (mkToml, table, (=:))
  
  import qualified Data.Text.IO as TIO
  
  import qualified Toml
  
  
  newtype TestInside = TestInside { unInside :: Text }
  
  insideCodec :: TomlCodec TestInside
  insideCodec = Toml.dimap unInside TestInside $ Toml.text "inside"
  
  data User = User
      { userName :: !Text
      , userAge  :: !Int
      } deriving stock (Eq, Ord, Generic)
        deriving anyclass (Hashable)
  
  userCodec :: TomlCodec User
  userCodec = User
      <$> Toml.text "name" .= userName
      <*> Toml.int  "age"  .= userAge
  
  newtype N = N
      { unN :: Text
      }
  
  data ColorScheme
      = Light
      | Dark
      | HighContrast
      deriving stock (Show, Read, Enum, Bounded)
  
  data UserStatus
      = Registered Text Text
      | Anonymous Text
  
  matchRegistered :: UserStatus -> Maybe (Text, Text)
  matchRegistered (Registered username password) = Just (username, password)
  matchRegistered _                              = Nothing
  
  matchAnonymous :: UserStatus -> Maybe Text
  matchAnonymous (Anonymous username) = Just username
  matchAnonymous _                    = Nothing
  
  userPassCodec :: TomlCodec (Text, Text)
  userPassCodec = Toml.pair
      (Toml.text "username")
      (Toml.text "password")
  
  userStatusCodec :: TomlCodec UserStatus
  userStatusCodec =
      Toml.dimatch matchRegistered (uncurry Registered) (Toml.table userPassCodec "testStatus")
      <|> Toml.dimatch matchAnonymous Anonymous (Toml.text "testStatus")
  
  data Colour
     = Hex Text
     | RGB Rgb
  
  matchHex :: Colour -> Maybe Text
  matchHex = \case
      Hex t -> Just t
      _     -> Nothing
  
  matchRgb :: Colour -> Maybe Rgb
  matchRgb = \case
      RGB rgb -> Just rgb
      _       -> Nothing
  
  colourCodec :: Toml.Key -> TomlCodec Colour
  colourCodec key =
          Toml.dimatch matchHex Hex (Toml.text key)
      <|> Toml.dimatch matchRgb RGB (Toml.table rgbCodec key)
  
  data Rgb = Rgb
      { rgbRed   :: Int
      , rgbGreen :: Int
      , rgbBlue  :: Int
      }
  
  rgbCodec :: TomlCodec Rgb
  rgbCodec = Rgb
      <$> Toml.int "red"   .= rgbRed
      <*> Toml.int "green" .= rgbGreen
      <*> Toml.int "blue"  .= rgbBlue
  
  newtype Inner = Inner
      { val :: Text
      } deriving stock (Show)
  
  innerCodec :: TomlCodec Inner
  innerCodec = Inner <$> Toml.text "val" .= val
  
  newtype MapWithList = MapWithList
      { mapList :: Map Text [Inner]
      } deriving stock (Show)
  
  mapWithListCodec :: TomlCodec MapWithList
  mapWithListCodec = MapWithList
      <$> Toml.tableMap Toml._KeyText (Toml.list innerCodec) "mapList" .= mapList
  
  data Test = Test
      { testB         :: !Bool
      , testI         :: !Int
      , testF         :: !Double
      , testS         :: !Text
      , testA         :: ![Text]
      , testNE        :: !(NonEmpty Text)
      , testNET       :: !(NonEmpty Int)
      , testM         :: !(Maybe Bool)
      , testX         :: !TestInside
      , testY         :: !(Maybe TestInside)
      , testEven      :: !Int
      , testN         :: !N
      , testC         :: !ColorScheme
      , testPair      :: !(Int, Text)
      , testTriple    :: !(Int, Text, Bool)
      , testE1        :: !(Either Integer String)
      , testE2        :: !(Either String Double)
      , testStatus    :: !UserStatus
      , users         :: ![User]
      , susers        :: !(Set User)
      , husers        :: !(HashSet User)
      , intset        :: !IntSet
      , payloads      :: !(Map Text Int)
      , colours       :: !(Map Text Colour)
      , tableList     :: !MapWithList
      , testHardcoded :: !Text
      }
  
  
  testT :: TomlCodec Test
  testT = Test
      <$> Toml.bool "testB" .= testB
      <*> Toml.int "testI" .= testI
      <*> Toml.double "testF" .= testF
      <*> Toml.text "testS" .= testS
      <*> Toml.arrayOf Toml._Text "testA" .= testA
      <*> Toml.arrayNonEmptyOf Toml._Text "testNE" .= testNE
      <*> Toml.nonEmpty (Toml.int "a") "testNET" .= testNET
      <*> Toml.dioptional (Toml.bool "testM") .= testM
      <*> Toml.table insideCodec "testX" .= testX
      <*> Toml.dioptional (Toml.table insideCodec "testY") .= testY
      <*> Toml.validateIf even Toml._Int "testEven" .= testEven
      <*> Toml.diwrap (Toml.text "testN") .= testN
      <*> Toml.enumBounded "testC" .= testC
      <*> Toml.table pairC "testPair" .= testPair
      <*> Toml.table tripleC "testTriple" .= testTriple
      <*> eitherT1 .= testE1
      <*> eitherT2 .= testE2
      <*> userStatusCodec .= testStatus
      <*> Toml.list userCodec "user" .= users
      <*> Toml.set userCodec "suser" .= susers
      <*> Toml.hashSet userCodec "huser" .= husers
      <*> Toml.arrayIntSet "intset" .= intset
      <*> Toml.map (Toml.text "name") (Toml.int "payload") "payloads" .= payloads
      <*> Toml.tableMap Toml._KeyText colourCodec "colours" .= colours
      <*> mapWithListCodec .= tableList
      <*> Toml.hardcoded "abc" Toml._Text "testHardcoded" .= testHardcoded
    where
      pairC :: TomlCodec (Int, Text)
      pairC = Toml.pair (Toml.int "pNum") (Toml.text "pName")
  
      tripleC :: TomlCodec (Int, Text, Bool)
      tripleC = Toml.triple (Toml.int "tNum") (Toml.text "tName") (Toml.bool "isFav")
  
      -- different keys for sum type
      eitherT1 :: TomlCodec (Either Integer String)
      eitherT1 = Toml.match (Toml._Left >>> Toml._Integer)  "either.Left"
             <|> Toml.match (Toml._Right >>> Toml._String) "either.Right"
  
      -- same key for sum type;
      -- doesn't work if you have something like `Either String String`,
      -- you should distinguish these cases by different keys like in `eitherT1` example
      eitherT2 :: TomlCodec (Either String Double)
      eitherT2 = ( Toml.match (Toml._Left >>> Toml._String)
               <!> Toml.match (Toml._Right >>> Toml._Double)
                 ) "either"
  
  data GenericPerson = GenericPerson
      { genericPersonName    :: !Text
      , genericPersonAddress :: !Address
      } deriving stock (Generic)
  
  data Address = Address
      { addressStreet :: !Text
      , addressHouse  :: !Int
      } deriving stock (Generic)
        deriving HasCodec via (TomlTableStrip Address)
  
  testGeneric :: TomlCodec GenericPerson
  testGeneric = stripTypeNameCodec
  
  newtype MyByteString = MyByteString
      { unMyByteString :: ByteString
      } deriving HasCodec via ByteStringAsBytes
  
  main :: IO ()
  main = do
      TIO.putStrLn "=== Printing manually specified TOML ==="
      TIO.putStrLn $ pretty myToml
  
      TIO.putStrLn "=== Trying to print invalid TOML ==="
      content <- TIO.readFile "examples/invalid.toml"
      TIO.putStrLn $ case Toml.parse content of
          Left (TomlParseError e) -> e
          Right toml              -> pretty toml
  
      TIO.putStrLn "=== Testing bidirectional conversion ==="
      biFile <- TIO.readFile "examples/biTest.toml"
      TIO.putStrLn $ case Toml.decode testT biFile of
          Left msgs  -> Toml.prettyTomlDecodeErrors msgs
          Right test -> Toml.encode testT test
  
      TIO.putStrLn "=== Testing Deriving Via ==="
      genericFile <- TIO.readFile "examples/generic.toml"
      TIO.putStrLn $ case Toml.decode testGeneric genericFile of
          Left msg   -> Toml.prettyTomlDecodeErrors msg
          Right test -> Toml.encode testGeneric test
  
  myToml :: TOML
  myToml = mkToml $ do
      "a" =: Bool True
      "list" =: Array ["one", "two"]
      "time" =: Array [Day (fromGregorian 2018 3 29)]
      table "table.name.1" $ do
          "aInner" =: 1
          "listInner" =: Array [Bool True, Bool False]
          table "1" $ do
              "aInner11" =: 11
              "listInner11" =: Array [0, 1]
          table "2" $
              "Inner12" =: "12"
      table "table.name.2" $
          "Inner2" =: 42
  