{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Main where

import qualified Data.Foldable as Fold
import Data.Function (on)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import qualified Data.Ini as I1
import qualified Data.Ini.Config.Raw as I2
import Data.List (nubBy)
import qualified Data.Sequence as Seq
import Data.Text (Text)
import qualified Data.Text as T
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range

propIniEquiv :: Property
propIniEquiv = property $ do
  raw <- forAll mkIni
  let printed = I1.printIniWith I1.defaultWriteIniSettings raw
      i1 = I1.parseIni printed
      i2 = I2.parseRawIni printed
  case (i1, i2) of
    (Right i1', Right i2') ->
      let i1'' = lower i1'
          i2'' = toMaps i2'
       in i1'' === i2''
    _ -> failure

propRevIniEquiv :: Property
propRevIniEquiv = property $ do
  raw <- forAll mkRichIni
  let printed = I2.printRawIni raw
      i1 = I1.parseIni printed
      i2 = I2.parseRawIni printed
  case (i1, i2) of
    (Right i1', Right i2') ->
      lower i1' === toMaps i2'
    _ -> failure

propIniSelfEquiv :: Property
propIniSelfEquiv = property $ do
  raw <- forAll mkRichIni
  Right (toMaps raw) === fmap toMaps (I2.parseRawIni (I2.printRawIni raw))

lower :: I1.Ini -> HashMap Text (HashMap Text Text)
lower (I1.Ini sections _) =
  HM.fromList
    [ (T.toLower sectionName, HM.fromList [(T.toLower k, v) | (k, v) <- section])
      | (sectionName, section) <- HM.toList sections
    ]

toMaps :: I2.RawIni -> HashMap Text (HashMap Text Text)
toMaps (I2.RawIni m) = conv (fmap sectionToPair m)
  where
    sectionToPair (name, section) =
      (I2.normalizedText name, conv (fmap valueToPair (I2.isVals section)))
    valueToPair (name, value) =
      (I2.normalizedText name, T.strip (I2.vValue value))
    conv = HM.fromList . Fold.toList

textChunk :: Gen Text
textChunk = Gen.text (Range.linear 1 20) Gen.alphaNum

mkIni :: Gen I1.Ini
mkIni = do
  ss <- Gen.list (Range.linear 0 10) $ do
    name <- textChunk
    section <-
      Gen.list (Range.linear 0 10) $
        (,) <$> textChunk <*> textChunk
    return (name, section)
  return (I1.Ini (HM.fromList ss) [])

mkComments :: Gen (Seq.Seq I2.BlankLine)
mkComments =
  fmap Seq.fromList $
    Gen.list (Range.linear 0 5) $
      Gen.choice
        [ return I2.BlankLine,
          I2.CommentLine <$> Gen.element (";#" :: String) <*> textChunk
        ]

mkRichIni :: Gen I2.RawIni
mkRichIni = do
  ss <- Gen.list (Range.linear 0 100) $ do
    name <- textChunk
    section <- Gen.list (Range.linear 0 100) $ do
      k <- textChunk
      v <- textChunk
      cs <- mkComments
      return
        ( I2.normalize k,
          I2.IniValue 0 k v cs False '='
        )
    cs <- mkComments
    return
      ( I2.normalize name,
        I2.IniSection name (Seq.fromList (nubBy ((==) `on` fst) section)) 0 0 cs
      )
  return (I2.RawIni (Seq.fromList (nubBy ((==) `on` fst) ss)))

main :: IO ()
main = do
  _ <-
    checkParallel $
      Group
        "Test.Example"
        [ ("propIniEquiv", propIniEquiv),
          ("propRevIniEquiv", propRevIniEquiv),
          ("propIniSelfEquiv", propIniSelfEquiv)
        ]
  return ()
