{-# LANGUAGE AllowAmbiguousTypes   #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell       #-}

-- | Module containing Template Haskell functions to automically intertwine the
-- base functors of the given types.
module Data.Functor.Foldable.Extensions.TH
    ( -- * Template Haskell helpers
      entangleFunctors
    , entanglePair
    ) where

import           Data.Functor.Foldable.Extensions
import           Language.Haskell.TH

-- | Entangle a list of functors. As an example,
--
-- > entangleFunctors [(''Data, ''Codata)]
--
-- will generate
--
-- > instance SubHom DataF CodataF Data Codata
-- > instance SubType Codata
entangleFunctors :: [(Name, Name)] -> Q [Dec]
entangleFunctors = fmap concat . traverse (uncurry entanglePair)

-- | Entangle types, creating a 'SubHom' instance with their base functors.
-- Note that this is rather strict with regards to naming.
entanglePair :: Name -> Name -> Q [Dec]
entanglePair sub top = pure [subHomInstance, subTypeInstance]
    where

          subTypeInstance = InstanceD Nothing [] (subType `AppT` topT) funTypeDecls
          subHomInstance = InstanceD Nothing (fmap (AppT functor) [subFT, topFT]) (subHom `AppT` subFT `AppT` topFT `AppT` subT `AppT` topT) funDecls

          functor = ConT ''Functor
          subHom = ConT ''SubHom
          subType = ConT ''SubType

          toN = mkName . (++ "F") . show
          mN = mkName . show
          toF = ConT . toN
          subFT = toF sub
          topFT = toF top
          subT = ConT sub
          topT = ConT top

          -- TODO this is kind of sloppy.
          getConstructor = mkName . show

          funTypeDecls = [FunD switchN [switchClause, switchBoringClause]]

          switchClause = Clause [ConP (getConstructor top) [ConP (getConstructor sub) [VarP (mkName "a")]]] (NormalB (VarE (mkName "a"))) []
          switchBoringClause = Clause [VarP (mkName "x")] (NormalB (VarE (mkName "x"))) []

          funDecls = [FunD homoN [homoComplicated, homoSimple]]
          dummySig = SigE (VarE dummyN) topT

          homoComplicated = Clause [(VarP taN), (VarP saN), (ConP (toN top) [VarP (mkName "top")])] atlas []
          homoSimple = Clause [WildP, (VarP fN), (VarP eN)] body []

          atlas = NormalB ((ConE (mN top)) `AppE` ((VarE dendroN) `AppE` dummySig `AppE` (VarE saN) `AppE` (VarE taN) `AppE` (VarE (mkName "top"))))
          body = NormalB ((VarE fN) `AppE` (VarE eN))

          homoN = mkName "homo"
          switchN = mkName "switch"
          dendroN = mkName "dendro"
          dummyN = mkName "dummy"
          fN = mkName "f"
          eN = mkName "e"
          saN = mkName "subAlg"
          taN = mkName "topAlg"