{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}

module Test.Some
  ( someTests
  )
where

import           Data.Type.Equality (TestEquality(testEquality), (:~:)(Refl))
import           Control.Lens (Lens', lens, view, set)

import           Test.Tasty (TestTree, testGroup)
import           Test.Tasty.HUnit (assertEqual, testCase)

import           Data.Parameterized.Classes (ShowF)
import           Data.Parameterized.Some (Some(Some), someLens)

data Item b where
  BoolItem :: Item Bool
  IntItem :: Item Int

instance Show (Item b) where
  show =
    \case
      BoolItem -> "BoolItem"
      IntItem -> "IntItem"

instance TestEquality Item where
  testEquality x y =
    case (x, y) of
      (BoolItem, BoolItem) -> Just Refl
      (IntItem, IntItem) -> Just Refl
      _ -> Nothing

data Pair a b =
  Pair
    { _fir :: a
    , _sec :: Item b
    }

-- This instance isn't compatible with the intended use of TestEquality (which
-- is supposed to be just for singletons), but it seems fine for tests.
instance Eq a => TestEquality (Pair a) where
  testEquality x y =
    case testEquality (_sec x) (_sec y) of
      Just Refl -> if _fir x == _fir y then Just Refl else Nothing
      Nothing -> Nothing

instance (Show a) => Show (Pair a b) where
  show (Pair a b) = "Pair(" ++ show a ++ ", " ++ show b ++ ")"

instance Show a => ShowF (Pair a)

fir :: Lens' (Pair a b) a
fir = lens _fir (\s v -> s { _fir = v })

someFir :: Lens' (Some (Pair a)) a
someFir = someLens fir

someTests :: IO TestTree
someTests =
  testGroup "Some" <$>
    return
      [ testCase "someLens: view . set" $
          assertEqual
            "view l . set l x == const x"
            (view someFir (set someFir 5 (Some (Pair 1 BoolItem))))
            (5 :: Int)
      , testCase "someLens: set . set" $
          assertEqual
            "set l y . set l x == set l y"
            (set someFir 4 (set someFir 5 (Some (Pair 1 IntItem))))
            (Some (Pair (4 :: Int) IntItem))
      ]
