{-# LANGUAGE CPP #-}

-- | Translating Agda types to Haskell types. Used to ensure that imported
--   Haskell functions have the right type.

module Agda.Compiler.HaskellTypes where

import Control.Applicative
import Control.Monad.Error
import Data.Char

import Agda.Syntax.Common
import Agda.Syntax.Internal
import Agda.TypeChecking.Monad
import Agda.TypeChecking.Pretty
import Agda.TypeChecking.Reduce
import Agda.TypeChecking.Substitute
import Agda.TypeChecking.Free
import Agda.Utils.Monad
import Agda.Utils.Impossible

#include "../undefined.h"

type HaskellKind = String

hsStar :: HaskellKind
hsStar = "*"

hsKFun :: HaskellKind -> HaskellKind -> HaskellKind
hsKFun k l = "(" ++ k ++ " -> " ++ l ++ ")"

hsFun :: HaskellKind -> HaskellKind -> HaskellKind
hsFun a b = "(" ++ a ++ " -> " ++ b ++ ")"

hsVar :: Name -> HaskellType
hsVar x = "x" ++ concatMap encode (show x)
  where
    okChars = ['a'..'z'] ++ ['A'..'Y'] ++ "_'"
    encode 'Z' = "ZZ"
    encode c
      | c `elem` okChars = [c]
      | otherwise        = "Z" ++ show (fromEnum c)


hsApp :: String -> [HaskellType] -> HaskellType
hsApp d [] = d
hsApp d as = "(" ++ unwords (d : as) ++ ")"

hsForall :: String -> HaskellType -> HaskellType
hsForall x a = "(forall " ++ x ++ ". " ++ a ++ ")"

notAHaskellKind :: MonadTCM tcm => Type -> tcm a
notAHaskellKind a = do
  err <- fsep $ pwords "The type" ++ [prettyTCM a] ++
                pwords "cannot be translated to a Haskell kind."
  typeError $ GenericError $ show err

notAHaskellType :: MonadTCM tcm => Type -> tcm a
notAHaskellType a = do
  err <- fsep $ pwords "The type" ++ [prettyTCM a] ++
                pwords "cannot be translated to a Haskell type."
  typeError $ GenericError $ show err

getHsType :: MonadTCM tcm => QName -> tcm HaskellType
getHsType x = do
  d <- theDef <$> getConstInfo x
  case d of
    Axiom{ axHsDef = Just (HsType t) } -> return t
    Datatype{ dataHsType = Just t }    -> return t
    _                                  -> notAHaskellType (El Prop $ Def x [])

getHsVar :: MonadTCM tcm => Nat -> tcm HaskellCode
getHsVar i = hsVar <$> nameOfBV i

isHaskellKind :: Type -> TCM Bool
isHaskellKind a =
  (const True <$> haskellKind a) `catchError` \_ -> return False

haskellKind :: MonadTCM tcm => Type -> tcm HaskellKind
haskellKind a = do
  a <- reduce a
  case unEl a of
    Sort _  -> return hsStar
    Pi a b  -> hsKFun <$> haskellKind (unArg a) <*> underAbstraction a b haskellKind
    Fun a b -> hsKFun <$> haskellKind (unArg a) <*> haskellKind b
    _       -> notAHaskellKind a

haskellType :: MonadTCM tcm => Type -> tcm HaskellType
haskellType = liftTCM . fromType
  where
    fromArgs = mapM (fromTerm . unArg)
    fromType = fromTerm . unEl
    fromTerm v = do
      v <- reduce v
      case v of
        Var x args -> hsApp <$> getHsVar x <*> fromArgs args
        Def d args -> hsApp <$> getHsType d <*> fromArgs args
        Fun a b    -> hsFun <$> fromType (unArg a) <*> fromType b
        Pi a b ->
          ifM (isHaskellKind $ unArg a)
          (underAbstraction a b $ \b -> do
              x <- getHsVar 0
              b <- fromType b
              return $ hsForall x $ hsFun "()" b
          )
          (if 0 `freeIn` absBody b
           then notAHaskellType (El Prop v)
           else hsFun <$> fromType (unArg a) <*> fromType (absApp b __IMPOSSIBLE__)
          )
        Con{}      -> notAHaskellType (El Prop v)
        Lam{}      -> notAHaskellType (El Prop v)
        Lit{}      -> notAHaskellType (El Prop v)
        Sort{}     -> notAHaskellType (El Prop v)
        MetaV{}    -> notAHaskellType (El Prop v)