{-# LANGUAGE ConstraintKinds, RankNTypes, GADTs, TypeFamilies #-}
{-# OPTIONS_GHC -Wno-unused-top-binds #-}
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}

-- | A Typed version of dependencies where the value type depends on the key.
-- See the source for an example.
module Build.Task.Typed (Task, Key (..), showDependencies) where

import Data.Functor.Const
import Data.Functor.Identity

-- | A type class for keys, equipped with an associated type family that
-- can be used to determine the type of value corresponding to the key.
class Key k where
    type Value k :: *
    -- | The name of the key. Useful for avoiding heterogeneous lists of keys.
    showKey :: k -> String

-- | A typed build task.
type Task c k = forall f. c f => (forall k. Key k => k -> f (Value k)) -> k -> Maybe (f (Value k))

-- | Extract the names of dependencies.
showDependencies :: Task Applicative k -> k -> [String]
showDependencies task = maybe [] getConst . task (\k -> Const [showKey k])

------------------------------------ Example -----------------------------------
data ExampleKey a where
    Base       :: ExampleKey Int
    Number     :: ExampleKey Int
    SplitDigit :: ExampleKey (Int, Int)
    LastDigit  :: ExampleKey Int
    BaseDigits :: ExampleKey [Int]

instance Show (ExampleKey a) where
    show key = case key of
        Base       -> "Base"
        Number     -> "Number"
        SplitDigit -> "SplitDigit"
        LastDigit  -> "LastDigit"
        BaseDigits -> "BaseDigits"

instance Key (ExampleKey a) where
    type Value (ExampleKey a) = a
    showKey = show

digits :: Task Applicative (ExampleKey a)
digits fetch SplitDigit = Just $ divMod <$> fetch Number <*> fetch Base
digits fetch LastDigit  = Just $ snd <$> fetch SplitDigit
digits fetch BaseDigits = Just $ enumFromTo 1 <$> fetch Base
digits _ _ = Nothing

fetch :: ExampleKey t -> Identity (Value (ExampleKey t))
fetch key = Identity $ case key of
    Base       -> 10
    Number     -> 2018
    SplitDigit -> (201, 8)
    LastDigit  -> 8
    BaseDigits -> [0..9]