{-# LANGUAGE TemplateHaskell #-}

-- |
-- The module Data.Thorn.Fold.
module Data.Thorn.Fold (
    unfixdata, unfixdataEx
  , autoin, autoout, autohylo, autofold, autounfold
  , unfixdataMutual, unfixdataMutualEx
  , autoinMutual, autooutMutual, autohyloMutual, autofoldMutual, autounfoldMutual
    ) where

import Data.Thorn.Type
import Data.Thorn.Functor
import Language.Haskell.TH
import Control.Applicative

-- |
-- @unfixdata t@ provides a declaration of a data whose fixpoint is the recursive type @t@.
unfixdata :: TypeQ -> DecsQ
unfixdata = unfixdataEx ("Uf","") ("Uf","") ("&","") ("&","")

-- |
-- Special version of @unfixdata@. Note that
--
-- > unfixdata = unfixdataEx ("Uf","") ("Uf","") ("&","") ("&","")
unfixdataEx ::
    (String,String) -- ^ prefix and suffix of type constructor
 -> (String,String) -- ^ prefix and suffix of data constructor
 -> (String,String) -- ^ prefix and suffix of infix type constructor
 -> (String,String) -- ^ prefix and suffix of infix data constructor
 -> TypeQ -- ^ data type
 -> DecsQ -- ^ declaration of data
unfixdataEx (pretype,suftype) (predata,sufdata) (pretypeinfix,suftypeinfix) (predatainfix,sufdatainfix) t = do
    (n, DataTx nm _ cxs) <- applyFixed 0 =<< type2typex [] [] =<< t
    let modifytx (DataTx nm' vmp cxs') = if nm == nm' then VarTx $ mkName ("self") else DataTx nm' (map (\(nm'',tx) -> (nm'',modifytx tx)) vmp) (map modifycx cxs')
        modifytx tx@(SeenDataTx nm' _) = if nm == nm' then VarTx $ mkName ("self") else modifytx tx
        modifytx (TupleTx txs) = TupleTx (map modifytx txs)
        modifytx (ArrowTx txa txb) = ArrowTx (modifytx txa) (modifytx txb)
        modifytx (ListTx tx) = ListTx (modifytx tx)
        modifytx tx = tx
        modifycx (nm',txs) = (nm',map modifytx txs)
        go (nm',txs) = do
              ts <- map ((,) NotStrict) <$> mapM (typex2type . modifytx) txs
              return $ NormalC (datanm nm') ts
    cns <- mapM go cxs
    return [DataD [] (typenm nm) (map var [0..n-1] ++ [self]) cns []]
    where typenm nm
            | elem (head s) ['A'..'Z'] = mkName $ pretype ++ s ++ suftype
            | head s == '(' = mkName $ ":" ++ pretypeinfix ++ init (drop 2 s) ++ suftypeinfix
            | otherwise = mkName $ ":" ++ pretypeinfix ++ tail s ++ suftypeinfix
            where s = nameBase nm
          datanm nm
            | elem (head s) ['A'..'Z'] = mkName $ predata ++ s ++ sufdata
            | head s == '(' = mkName $ ":" ++ predatainfix ++ init (drop 2 s) ++ sufdatainfix
            | otherwise = mkName $ ":" ++ predatainfix ++ tail s ++ sufdatainfix
            where s = nameBase nm
          var i = PlainTV $ mkName ("t" ++ show i)
          self = PlainTV $ mkName ("self")

autoin ::
    TypeQ -- ^ @u@, un-recursive datatype
 -> TypeQ -- ^ @t@, fixpoint of @u@
 -> ExpQ -- ^ function with a type @u a0 .. an t -> t a0 .. an@
autoin u t = do
    (_,DataTx _ _ cxsu) <- applyFixed 0 =<< type2typex [] [] =<< u
    (_,DataTx _ _ cxst) <- applyFixed 0 =<< type2typex [] [] =<< t
    u1 <- unique
    u2 <- unique
    let go ((nmu,txsu),(nmt,_)) = Match (ConP nmu (map newVarP [u2..u2+length txsu-1])) (NormalB (applistE (ConE nmt) (map newVarE [u2..u2+length txsu-1]))) []
    return $ LamE [newVarP u1] (CaseE (newVarE u1) (map go (zip cxsu cxst)))

autoout ::
    TypeQ -- ^ @u@, un-recursive datatype
 -> TypeQ -- ^ @t@, fixpoint of @u@
 -> ExpQ -- ^ function with a type @t x0 .. xn -> u x0 .. xn t@
autoout u t = do
    (_,DataTx _ _ cxsu) <- applyFixed 0 =<< type2typex [] [] =<< u
    (_,DataTx _ _ cxst) <- applyFixed 0 =<< type2typex [] [] =<< t
    u1 <- unique
    u2 <- unique
    let go ((nmu,txsu),(nmt,_)) = Match (ConP nmt (map newVarP [u2..u2+length txsu-1])) (NormalB (applistE (ConE nmu) (map newVarE [u2..u2+length txsu-1]))) []
    return $ LamE [newVarP u1] (CaseE (newVarE u1) (map go (zip cxsu cxst)))

autohylo ::
    TypeQ -- ^ @u@, un-recursive datatype
 -> ExpQ -- ^ function with a type @(a -> u x0 .. xn a) -> (u x0 .. xn b -> b) -> (a -> b)@
autohylo u = do
    (n,DataTx nm _ cxs) <- applyFixed 0 =<< type2typex [] [] =<< u
    f <- autofmap u
    u <- unique
    return $ LamE [newVarP u, newVarP (u+1)] (LetE [ValD (newVarP (u+3))
        (NormalB (LamE [newVarP (u+2)] (AppE (newVarE (u+1)) (applistE f (replicate (n-1) (mkNameE "Prelude.id") ++ [newVarE (u+3)] ++ [AppE (newVarE u) (newVarE (u+2))])))))
        []] (newVarE (u+3)))

-- |
-- @autofold u t@ provides a folding function for a recursive type @t@.
autofold ::
    TypeQ -- ^ @u@, un-recursive datatype
 -> TypeQ -- ^ @t@, fixpoint of @u@
 -> ExpQ -- ^ function with a type @(u x0 .. xn a -> a) -> (t -> a)@
autofold u t = do
    o <- autoout u t
    h <- autohylo u
    return $ AppE h o

-- |
-- @autounfold t@ provides an unfolding function for the recursive type @t@.
autounfold ::
    TypeQ -- ^ @u@, un-recursive datatype
 -> TypeQ -- ^ @t@, fixpoint of @u@
 -> ExpQ -- ^ function with a type @(a -> u x0 .. xn a) -> (a -> t)@
autounfold u t = do
    i <- autoin u t
    h <- autohylo u
    u1 <- unique
    return $ LamE [newVarP u1] (AppE (AppE h (newVarE u1)) i)

-- |
-- @unfixdataMutual ts@ is a mutual recursion version of @unfixdata t@.
unfixdataMutual :: [TypeQ] -> DecsQ
unfixdataMutual = unfixdataMutualEx ("Uf","") ("Uf","") ("&","") ("&","")

unfixdataMutualEx ::
    (String,String) -- ^ prefix and suffix of type constructor
 -> (String,String) -- ^ prefix and suffix of data constructor
 -> (String,String) -- ^ prefix and suffix of infix type constructor
 -> (String,String) -- ^ prefix and suffix of infix data constructor
 -> [TypeQ] -- ^ data types
 -> DecsQ -- ^ declarations of data
unfixdataMutualEx = undefined

autoinMutual :: [TypeQ] -> DecsQ
autoinMutual ts = fail "oh"

autooutMutual :: [TypeQ] -> DecsQ
autooutMutual ts = fail "oh"

autohyloMutual :: [TypeQ] -> DecsQ
autohyloMutual ts = fail "oh"

-- |
-- @autofoldMutual ts@ provides a folding function for the mutually recursive types @ts@.
autofoldMutual :: [TypeQ] -> ExpQ
autofoldMutual ts = do fail "oh"

-- |
-- @autounfoldMutual ts@ provides an unfolding function for the mutually recursive types @ts@.
autounfoldMutual :: [TypeQ] -> ExpQ
autounfoldMutual ts = do fail "oh"