{-# LANGUAGE CPP, PatternGuards #-}

module Agda.TypeChecking.Rules.LHS.Split where

import Control.Applicative
import Control.Monad.Error
import Data.Monoid (mempty, mappend)
import Data.List
import Data.Traversable hiding (mapM, sequence)

import Agda.Interaction.Options

import Agda.Syntax.Common
import Agda.Syntax.Literal
import Agda.Syntax.Position
import Agda.Syntax.Internal
import Agda.Syntax.Internal.Pattern
import qualified Agda.Syntax.Abstract as A
import qualified Agda.Syntax.Info as A

import Agda.TypeChecking.Monad
import Agda.TypeChecking.Pretty
import Agda.TypeChecking.Reduce
import Agda.TypeChecking.Substitute
import Agda.TypeChecking.Constraints
import Agda.TypeChecking.Conversion
import Agda.TypeChecking.Rules.LHS.Problem
import Agda.TypeChecking.Rules.LHS.ProblemRest
-- import Agda.TypeChecking.Rules.Term
import Agda.TypeChecking.Monad.Builtin
import Agda.TypeChecking.Free
import Agda.TypeChecking.Irrelevance
import Agda.TypeChecking.MetaVars

import Agda.Utils.List
import Agda.Utils.Monad
import Agda.Utils.Permutation
import Agda.Utils.Tuple

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

-- | TODO: move to Agda.Syntax.Abstract.View
asView :: A.Pattern -> ([Name], A.Pattern)
asView (A.AsP _ x p) = (x :) -*- id $ asView p
asView p	     = ([], p)

-- | TODO: move somewhere else
expandLitPattern :: NamedArg A.Pattern -> TCM (NamedArg A.Pattern)
expandLitPattern p = traverse (traverse expand) p
  where
    expand p = case asView p of
      (xs, A.LitP (LitInt r n))
        | n < 0     -> __IMPOSSIBLE__
        | n > 20    -> typeError $ GenericError $
                        "Matching on natural number literals is done by expanding "
                        ++ "the literal to the corresponding constructor pattern, so "
                        ++ "you probably don't want to do it this way."
        | otherwise -> do
          Con z _ <- ignoreSharing <$> primZero
          Con s _ <- ignoreSharing <$> primSuc
          let zero  = A.ConP info (A.AmbQ [setRange r z]) []
              suc p = A.ConP info (A.AmbQ [setRange r s]) [defaultNamedArg p]
              info  = A.PatRange r
              p'    = foldr ($) zero $ genericReplicate n suc
          return $ foldr (A.AsP info) p' xs
      _ -> return p

-- | Split a problem at the first constructor of datatype type. Implicit
--   patterns should have been inserted.
splitProblem :: Problem -> TCM (Either SplitError SplitProblem)
splitProblem (Problem ps (perm, qs) tel pr) = do
    reportS "tc.lhs.split" 20 $ "initiating splitting\n"
    runErrorT $
      splitP ps (permute perm $ zip [0..] $ allHoles qs) tel
  where
    splitP :: [NamedArg A.Pattern] -> [(Int, OneHolePatterns)] -> Telescope -> ErrorT SplitError TCM SplitProblem
    splitP _	    []		 (ExtendTel _ _)	 = __IMPOSSIBLE__
    splitP _	    (_:_)	  EmptyTel		 = __IMPOSSIBLE__
    splitP []	     _		  _			 = throwError $ NothingToSplit
    splitP ps	    []		  EmptyTel		 = __IMPOSSIBLE__
    splitP (p : ps) ((i, q) : qs) tel0@(ExtendTel a tel) = do
      let tryAgain = splitP (p : ps) ((i, q) : qs) tel0
      p <- lift $ expandLitPattern p
      case asView $ namedArg p of

        -- Case: literal pattern
	(xs, p@(A.LitP lit))  -> do
          -- Note that, in the presence of --without-K, this branch is
          -- based on the assumption that the types of literals are
          -- not indexed.

          -- Andreas, 2010-09-07 cannot split on irrelevant args
          when (unusableRelevance $ domRelevance a) $
            typeError $ SplitOnIrrelevant p a
	  b <- lift $ litType lit
	  ok <- lift $ do
	      noConstraints (equalType (unDom a) b)
	      return True
	    `catchError` \_ -> return False
	  if ok
	    then return $
	      Split mempty
		    xs
		    (argFromDom $ fmap (LitFocus lit q i) a)
		    (fmap (\ tel -> Problem ps () tel __IMPOSSIBLE__) tel)
	    else keepGoing

        -- Case: constructor pattern
	(xs, p@(A.ConP _ (A.AmbQ cs) args)) -> do
          let tryInstantiate a'
                | [c] <- cs = do
                    -- Type is blocked by a meta and constructor is unambiguous,
                    -- in this case try to instantiate the meta.
                  ok <- lift $ do
                    Constructor{ conData = d } <- theDef <$> getConstInfo c
                    dt     <- defType <$> getConstInfo d
                    vs     <- newArgsMeta dt
                    Sort s <- ignoreSharing . unEl <$> reduce (apply dt vs)
                    (True <$ noConstraints (equalType a' (El s $ Def d vs)))
                      `catchError` \_ -> return False
                  if ok then tryAgain else keepGoing
                | otherwise = keepGoing
          ifBlockedType (unDom a) (const tryInstantiate) $ \ a' -> do
	  case ignoreSharing $ unEl a' of

            -- Subcase: split type is a Def
	    Def d vs	-> do
	      def <- liftTCM $ theDef <$> getConstInfo d
              unless (defIsRecord def) $
                -- cannot split on irrelevant or non-strict things
                when (unusableRelevance $ domRelevance a) $ do
                  -- Andreas, 2011-10-04 unless allowed by option
                  allowed <- liftTCM $ optExperimentalIrrelevance <$> pragmaOptions
                  unless allowed $ typeError $ SplitOnIrrelevant p a

              let mp = case def of
                        Datatype{dataPars = np} -> Just np
                        Record{recPars = np}    -> Just np
                        _                       -> Nothing
              case mp of
                Nothing -> keepGoing
                Just np ->
		  liftTCM $ traceCall (CheckPattern p EmptyTel (unDom a)) $ do  -- TODO: wrong telescope
                  -- Check that we construct something in the right datatype
                  c <- do
                      cs' <- mapM canonicalName cs
                      d'  <- canonicalName d
                      let cons def = case theDef def of
                            Datatype{dataCons = cs} -> cs
                            Record{recCon = c}      -> [c]
                            _                       -> __IMPOSSIBLE__
                      cs0 <- cons <$> getConstInfo d'
                      case [ c | (c, c') <- zip cs cs', elem c' cs0 ] of
                        [c]   -> return c
                        []    -> typeError $ ConstructorPatternInWrongDatatype (head cs) d
                        cs    -> -- if there are more than one we give up (they might have different types)
                          typeError $ GenericError $
                            "Can't resolve overloaded constructors targeting the same datatype (" ++ show d ++ "):" ++
                            unwords (map show cs)

		  let (pars, ixs) = genericSplitAt np vs
		  reportSDoc "tc.lhs.split" 10 $
		    vcat [ sep [ text "splitting on"
			       , nest 2 $ fsep [ prettyA p, text ":", prettyTCM a ]
			       ]
			 , nest 2 $ text "pars =" <+> fsep (punctuate comma $ map prettyTCM pars)
			 , nest 2 $ text "ixs  =" <+> fsep (punctuate comma $ map prettyTCM ixs)
			 ]

                  whenM (optWithoutK <$> pragmaOptions) $
                    wellFormedIndices a'

		  return $ Split mempty
				 xs
				 (argFromDom $ fmap (Focus c args (getRange p) q i d pars ixs) a)
				 (fmap (\ tel -> Problem ps () tel __IMPOSSIBLE__) tel)
            -- Subcase: split type is not a Def
	    _	-> keepGoing
        -- Case: neither literal nor constructor pattern
	p -> keepGoing
      where
	keepGoing = do
	  let p0 = Problem [p] () (ExtendTel a $ fmap (const EmptyTel) tel) mempty
	  Split p1 xs foc p2 <- underAbstraction a tel $ \tel -> splitP ps qs tel
	  return $ Split (mappend p0 p1) xs foc p2

-- | Takes a type, which must be a data or record type application,
-- and checks that the indices are constructors (or literals) applied
-- to distinct variables which do not occur free in the parameters.
-- For the purposes of this check parameters count as constructor
-- arguments; parameters are reconstructed from the given type.
--
-- Precondition: The type must be a data or record type application.

wellFormedIndices :: Type -> TCM ()
wellFormedIndices t = do
  t <- reduce t

  reportSDoc "tc.lhs.split.well-formed" 10 $
    fsep [ text "Checking if indices are well-formed:"
         , nest 2 $ prettyTCM t
         ]

  (pars, ixs) <- normalise =<< case ignoreSharing $ unEl t of
    Def d args -> do
      def       <- getConstInfo d
      typedArgs <- args `withTypesFrom` defType def

      let noPars = case theDef def of
            Datatype { dataPars = n } -> n
            Record   { recPars  = n } -> n
            _                         -> __IMPOSSIBLE__
          (pars, ixs) = genericSplitAt noPars typedArgs
      return (map fst pars, ixs)

    _ -> __IMPOSSIBLE__

  mvs <- constructorApplications ixs
  vs  <- case mvs of
           Nothing -> typeError $
                        IndicesNotConstructorApplications (map fst ixs)
           Just vs -> return vs

  unless (fastDistinct vs) $
    typeError $ IndexVariablesNotDistinct vs (map fst ixs)

  case map fst $ filter snd $ zip vs (map (`freeIn` pars) vs) of
    [] -> return ()
    vs ->
      typeError $ IndicesFreeInParameters vs (map fst ixs) pars

  where
  -- | If the term consists solely of constructors (or literals)
  -- applied to variables (after parameter reconstruction), then the
  -- variables are returned, and otherwise nothing.
  constructorApplication :: Term
                         -> Type  -- ^ The term's type.
                         -> TCM (Maybe [Nat])
  constructorApplication (Var x [])      _ = return (Just [x])
  constructorApplication (Lit {})        _ = return (Just [])
  constructorApplication (Shared p)      t  = constructorApplication (derefPtr p) t
  constructorApplication (Con c conArgs) (El _ (Def d dataArgs)) = do
    conDef  <- getConstInfo c
    dataDef <- getConstInfo d

    let noPars = case theDef dataDef of
          Datatype { dataPars = n } -> n
          Record   { recPars  = n } -> n
          _                         -> __IMPOSSIBLE__
        pars    = genericTake noPars dataArgs
        allArgs = pars ++ conArgs

    reportSDoc "tc.lhs.split.well-formed" 20 $
      fsep [ text "Reconstructed parameters:"
           , nest 2 $
               prettyTCM (Con c []) <+>
               text "(:" <+> prettyTCM (defType conDef) <> text ")" <+>
               text "<<" <+> prettyTCM pars <+> text ">>" <+>
               prettyTCM conArgs
           ]

    constructorApplications =<< allArgs `withTypesFrom` defType conDef

  constructorApplication _ _ = return Nothing

  constructorApplications :: [(Arg Term, Dom Type)] -> TCM (Maybe [Nat])
  constructorApplications args = do
    xs <- mapM (\(e, t) -> constructorApplication (unArg e) (ignoreSharingType $ unDom t))
               args
    return (concat <$> sequence xs)

-- | @args \`withTypesFrom\` t@ returns the arguments @args@ paired up
-- with their types, taken from @t@, which is assumed to be a @length
-- args@-ary pi.
--
-- Precondition: @t@ has to start with @length args@ pis.

withTypesFrom :: Args -> Type -> TCM [(Arg Term, Dom Type)]
[]           `withTypesFrom` _ = return []
(arg : args) `withTypesFrom` t = do
  t <- reduce t
  case ignoreSharing $ unEl t of
    Pi a b -> ((arg, a) :) <$>
              args `withTypesFrom` absApp b (unArg arg)
    _      -> __IMPOSSIBLE__
