
module Test.FocusList.Invariants where

import Data.Maybe (catMaybes)
import Data.Semigroup ((<>))
import Hedgehog
  ( Gen
  , Property
  , PropertyT
  , annotate
  , annotateShow
  , failure
  , forAll
  , property
  , success
  )
import Hedgehog.Gen (alphaNum, choice, int, string)
import Hedgehog.Range (constant, linear)

import Data.FocusList
  ( FocusList
  , deleteFL
  , emptyFL
  , insertFL
  , invariantFL
  , isEmptyFL
  , lengthFL
  , lookupFL
  , removeFL
  )

testInvariantsInFocusList :: Property
testInvariantsInFocusList =
  property $ do
    numOfActions <- forAll $ int (linear 1 200)
    let initialState = emptyFL
    let strGen = string (constant 0 25) alphaNum
    -- traceM "----------------------------------"
    -- traceM $ "starting bar, numOfActions: " <> show numOfActions
    runActions numOfActions strGen initialState

data Action a
  = InsertFL Int a
  | RemoveFL Int
  | DeleteFL a
  deriving (Eq, Show)

genInsertFL :: Gen a -> FocusList a -> Maybe (Gen (Action a))
genInsertFL valGen fl
  | isEmptyFL fl = Just $ do
      val <- valGen
      pure $ InsertFL 0 val
  | otherwise = Just $ do
      let len = lengthFL fl
      key <- int $ constant 0 len
      val <- valGen
      pure $ InsertFL key val

genRemoveFL :: FocusList a -> Maybe (Gen (Action a))
genRemoveFL fl
  | isEmptyFL fl = Nothing
  | otherwise = Just $ do
      let len = lengthFL fl
      keyToRemove <- int $ constant 0 (len - 1)
      pure $ RemoveFL keyToRemove

genDeleteFL :: Show a => FocusList a -> Maybe (Gen (Action a))
genDeleteFL fl
  | isEmptyFL fl = Nothing
  | otherwise = Just $ do
      let len = lengthFL fl
      keyForItemToDelete <- int $ constant 0 (len - 1)
      let maybeItemToDelete = lookupFL keyForItemToDelete fl
      case maybeItemToDelete of
        Nothing ->
          let msg =
                "Could not find item in focuslist even though " <>
                "it should be there." <>
                "\nkey: " <>
                show keyForItemToDelete <>
                "\nfocus list: " <>
                show fl
          in error msg
        Just item -> pure $ DeleteFL item

generateAction :: Show a => Gen a -> FocusList a -> Gen (Action a)
generateAction valGen fl = do
  let generators =
        catMaybes
          [ genInsertFL valGen fl
          , genRemoveFL fl
          , genDeleteFL fl
          ]
  case generators of
    [] ->
      let msg =
            "No generators available for fl:\n" <>
            show fl
      in error msg
    _ -> do
      choice generators

performAction :: Eq a => FocusList a -> Action a -> Maybe (FocusList a)
performAction fl (InsertFL key val) = Just $ insertFL key val fl
performAction fl (RemoveFL keyToRemove) = removeFL keyToRemove fl
performAction fl (DeleteFL valToDelete) = Just $ deleteFL valToDelete fl

runActions :: (Eq a, Monad m, Show a) => Int -> Gen a -> FocusList a -> PropertyT m ()
runActions i valGen startingFL
  | i <= 0 = success
  | otherwise = do
    action <- forAll $ generateAction valGen startingFL
    -- traceM $ "runActions, startingFL: " <> show startingFL
    -- traceM $ "runActions, action: " <> show action
    let maybeEndingFL = performAction startingFL action
    case maybeEndingFL of
      Nothing -> do
        annotate "Failed to perform action."
        annotateShow startingFL
        annotateShow action
        failure
      Just endingFL ->
        if invariantFL endingFL
          then runActions (i - 1) valGen endingFL
          else do
            annotate "Ending FocusList failed invariants."
            annotateShow startingFL
            annotateShow action
            annotateShow endingFL
            failure
