module Data.Convert2.TH where

import Prelude

import Language.Haskell.TH

maxKind :: Int
maxKind = 5

appsT :: Foldable f => Type -> f Type -> Type
appsT = foldl (AppT)

showIfNot0 :: Int -> String
showIfNot0 i = if i == 0 then "" else show i

ixedName :: String -> Int -> String
ixedName s i = s <> showIfNot0 i

ixedName' :: String -> Int -> String
ixedName' s i = ixedName s i <> "'"

src, tgt :: Name
src  = mkName "src"
tgt = mkName "tgt"

type IString = Int -> String

swap :: (a,b) -> (b,a)
swap (a,b) = (b,a)

-- > class Convertible1 src tgt where convert1 :: ∀ b. src b -> tgt
genConvertibleClass :: Bool -> IString -> IString -> Int -> Dec
genConvertibleClass inv sname sfname i = cls where
    name   = mkName $ sname  i
    fname  = mkName $ sfname i
    vnames = mkName . ("t" <>) . show <$> [1 .. i]
    binds  = ForallT (PlainTV <$> vnames) []
    (arg,result) = (if inv then swap else id)
                 $ (appsT (VarT src) (VarT <$> vnames), VarT tgt)
    body  = SigD fname (binds $ AppT (AppT ArrowT arg) result)
    cls   = ClassD [] name [PlainTV tgt, PlainTV src] [] [body]

-- > instance {-# OVERLAPPABLE #-} Convertible2 src tgt
-- >       => Convertible1 (src t) tgt where
-- >    convert1 = convert2
-- >    {-# INLINE convert1 #-}
genHigherKindDefInstance :: IString -> IString -> Int -> Dec
genHigherKindDefInstance sname sfname i = inst where
    name    = mkName $ sname  i
    fname   = mkName $ sfname i
    nameH   = mkName $ sname  (i + 1)
    fnameH  = mkName $ sfname (i + 1)
    overlap = Just Overlappable
    ctx     = [AppT (AppT (ConT nameH) (VarT tgt)) (VarT src)]
    body    = ValD (VarP fname) (NormalB (VarE fnameH)) []
    inline  = PragmaD (InlineP fname Inline FunLike AllPhases)
    arg     = appsT (VarT src) [VarT (mkName "t")]
    inst    = InstanceD overlap ctx (AppT (AppT (ConT name) (VarT tgt)) arg)
              [body, inline]

-- > instance TypeError (IdConversionErr src) 
-- >       => Convertible1 src (src t1) where
-- >     convert1 = impossible
-- >     {-# INLINE convert1 #-}
genIdConversionErrorInstance :: IString -> IString -> Int -> Dec
genIdConversionErrorInstance sname sfname i = inst where
    name    = mkName $ sname  i
    fname   = mkName $ sfname i
    vnames  = mkName . ("t" <>) . show <$> [1 .. i]
    err     = AppT (ConT (mkName "TypeError"))
            $ AppT (ConT (mkName "IdConversionErr"))
            $ VarT src
    overlap = Nothing
    ctx     = [err]
    body    = ValD (VarP fname) (NormalB (VarE $ mkName "impossible")) []
    inline  = PragmaD (InlineP fname Inline FunLike AllPhases)
    arg     = appsT (VarT src) (VarT <$> vnames)
    inst    = InstanceD overlap ctx (AppT (AppT (ConT name) arg) (VarT src))
              [body, inline]

genConvertibleClasses :: Bool -> IString -> IString -> Q [Dec]
genConvertibleClasses inv sname sfname = pure $ genConvertibleClass inv sname sfname <$> [0 .. maxKind]

genHigherKindDefInstances :: IString -> IString -> Q [Dec]
genHigherKindDefInstances sname sfname = pure $ genHigherKindDefInstance sname sfname <$> [0 .. maxKind - 1]

genIdConversionErrorInstances :: IString -> IString -> Q [Dec]
genIdConversionErrorInstances sname sfname = pure $ genIdConversionErrorInstance sname sfname <$> [0 .. maxKind]