{-# LANGUAGE CPP #-}
{-# LANGUAGE TemplateHaskell #-}
module Control.Isomorphism.Partial.TH
  ( defineIsomorphisms
  , defineIsomorphisms'
  ) where

----------------------------------------
-- SITE-PACKAGES
----------------------------------------
import Language.Haskell.TH ( lamE, tupP, appE, conE, varP, caseE
                           , match, conP, normalB, newName, clause
                           , funD, mkName, reify, varE, nameBase
                           , Info(..), Dec(..), Con(..), wildP, tupE
                           , Name, Q, MatchQ
                           )
import Control.Monad (replicateM)
import Data.List (find)
import Data.Char (toLower)

----------------------------------------
-- LOCAL
----------------------------------------
import Control.Isomorphism.Partial.Iso (Iso, unsafeMakeIso)

defineIsomorphisms :: Name -> Q [Dec]
defineIsomorphisms d = defineIsomorphisms' d (\(x:xs) -> (toLower x):xs)

defineIsomorphisms' :: Name -> (String -> String) -> Q [Dec]
defineIsomorphisms' d renameFun =
    do info <- reify d
       let cs = case info of
#if MIN_VERSION_template_haskell(2,11,0)
                  TyConI (DataD _ _ _ _ cs _) -> cs
                  TyConI (NewtypeD _ _ _ _ c _) -> [c]
#else
                  TyConI (DataD _ _ _ cs _) -> cs
                  TyConI (NewtypeD _ _ _ c _) -> [c]
#endif
                  otherwise -> error $ show d ++
                                       " neither denotes a data or newtype declaration. Found: " ++
                                       show info
       mapM (defFromCon (length cs > 1) renameFun) cs

defFromCon :: Bool -> (String -> String) -> Con -> Q Dec
defFromCon wc renameFun con@(NormalC n fields) = funCreation wc n (length fields) renameFun
defFromCon wc renameFun con@(RecC n fields) = funCreation wc n (length fields) renameFun
defFromCon wc renameFun con@(InfixC _ n _) = funCreation wc n 2 renameFun
defFromCon wc renameFun con@(ForallC _ _ _) = error $ "defineIsomorphisms not available for " ++
                                                      "existential data constructors"

funCreation :: Bool -> Name -> Int -> (String -> String) -> Q Dec
funCreation wc n nfields renameFun =
    funD (mkName $ renameFun $ nameBase n)
      [clause [] (normalB (isoFromCon (wildcard wc) n nfields)) []]

isoFromCon wildcard conName nfields =
    do (paths, exprs)  <-  genPE nfields
       dat <-  newName "x"
       let f = lamE [nested tupP paths]
                    [| Just $(foldl appE (conE conName) exprs) |]
       let g = lamE [varP dat]
                  (caseE (varE dat) $
                    [ match (conP conName paths)
                        (normalB [| Just $(nested tupE exprs) |]) []
                    ] ++ wildcard)
       [| unsafeMakeIso $f $g |]

wildcard :: Bool -> [MatchQ]
wildcard True = [match (wildP) (normalB [| Nothing |]) []]
wildcard _ = []

genPE number = do
  ids <- replicateM number (newName "x")
  return (map varP ids, map varE ids)

checkInfix :: Con -> Bool
checkInfix (InfixC _ _ _) = False
checkInfix _ = True

nested tup []      =  tup []
nested tup [x]     =  x
nested tup (x:xs)  =  tup [x, nested tup xs]