{-# LANGUAGE CPP #-}

module Agda.TypeChecking.Datatypes where

import Control.Applicative ((<$>))
import Data.List

import Agda.Syntax.Common
import Agda.Syntax.Internal
import Agda.TypeChecking.Monad
import Agda.TypeChecking.Reduce
import Agda.TypeChecking.Telescope
import Agda.TypeChecking.Substitute

import Agda.Utils.Size
import Agda.Utils.Impossible
#include "../undefined.h"

-- | Get the name of the datatype constructed by a given constructor.
--   Precondition: The argument must refer to a constructor
getConstructorData :: QName -> TCM QName
getConstructorData c = do
  def <- getConstInfo c
  case theDef def of
    Constructor{conData = d} -> return d
    _                        -> __IMPOSSIBLE__


-- | Return the number of non-parameter arguments to a data constructor,
--   or the field names of a record constructor.
--
--   For getting just the arity of constructor @c@,
--   use @either id size <$> getConstructorArity c@.
getConstructorArity :: QName -> TCM (Either Nat [Arg QName])
getConstructorArity c = do
  Defn{ defType = t, theDef = def } <- getConstInfo c
  case def of
    Constructor{ conData = d, conPars = n } -> do
      def <- theDef <$> getConstInfo d
      case def of
        Record{ recFields = fs } ->
           return $ Right fs
        Datatype{} -> do
          -- TODO: I do not want to take the type of constructor apart
          -- to see its arity!
          TelV tel _ <- telView t
          return $ Left $ size tel - n
        _ -> __IMPOSSIBLE__
    _ -> __IMPOSSIBLE__


-- | Check if a name refers to a datatype or a record with a named constructor.
isDatatype :: QName -> TCM Bool
isDatatype d = do
  def <- getConstInfo d
  case theDef def of
    Datatype{}                   -> return True
    Record{recNamedCon = namedC} -> return namedC
    _                            -> return False

data DataOrRecord = IsData | IsRecord
  deriving (Eq, Ord, Show)

-- | Check if a name refers to a datatype or a record.
isDataOrRecordType :: QName -> TCM (Maybe DataOrRecord)
isDataOrRecordType d = do
  def <- getConstInfo d
  case theDef def of
    Datatype{} -> return $ Just IsData
    Record{}   -> return $ Just IsRecord
    _          -> return $ Nothing

isDataOrRecord :: Term -> TCM (Maybe QName)
isDataOrRecord (Def d _)  = fmap (const d) <$> isDataOrRecordType d
isDataOrRecord (Shared p) = isDataOrRecord (derefPtr p)
isDataOrRecord _          = return Nothing

getNumberOfParameters :: QName -> TCM (Maybe Nat)
getNumberOfParameters d = do
  def <- getConstInfo d
  case theDef def of
    Datatype{ dataPars = n} -> return $ Just n
    Record{ recPars = n}    -> return $ Just n
    _                       -> return Nothing

{- UNUSED
data DatatypeInfo = DataInfo
  { datatypeName   :: QName
  , datatypeParTel :: Telescope
  , datatypePars   :: Args
  , datatypeIxTel  :: Telescope
  , datatypeIxs    :: Args
  }

-- | Get the name and parameters from a type if it's a datatype or record type
--   with a named constructor.
getDatatypeInfo :: Type -> TCM (Maybe DatatypeInfo)
getDatatypeInfo t = do
  t <- reduce t
  case unEl t of
    Def d args -> do
      n          <- getDefFreeVars d
      args       <- return $ genericDrop n args
      def        <- instantiateDef =<< getConstInfo d
      TelV tel _ <- telView (defType def)
      let npars  = case theDef def of
            Datatype{dataPars = np} -> Just np
            Record{recPars = np, recNamedCon = True}
              | genericLength args == np -> Just np
              | otherwise                -> __IMPOSSIBLE__
            _                            -> Nothing
      return $ do
        np <- npars
        let (pt, it) = genericSplitAt np $ telToList tel
            parTel   = telFromList pt
            ixTel    = telFromList it
            (ps, is) = genericSplitAt np args
        return $ DataInfo { datatypeName = d
                          , datatypeParTel = parTel
                          , datatypePars   = ps
                          , datatypeIxTel  = ixTel
                          , datatypeIxs    = is
                          }
    _ -> return Nothing
-}
