{-# LANGUAGE CPP, PatternGuards #-}
module Agda.TypeChecking.CompiledClause.Match where

import Control.Applicative
import qualified Data.Map as Map
import Data.Traversable
import Data.List

import Agda.Syntax.Internal
import Agda.Syntax.Common
import Agda.TypeChecking.CompiledClause
import Agda.TypeChecking.Monad
import Agda.TypeChecking.Substitute
import Agda.TypeChecking.Reduce
import Agda.TypeChecking.Primitive

import Agda.Utils.List

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

matchCompiled :: CompiledClauses -> MaybeReducedArgs -> TCM (Reduced (Blocked Args) Term)
matchCompiled c args = match c args id []

type Stack = [(CompiledClauses, MaybeReducedArgs, Args -> Args)]

-- TODO: literal/constructor pattern conflict (for Nat)

match :: CompiledClauses -> MaybeReducedArgs -> (Args -> Args) -> Stack -> TCM (Reduced (Blocked Args) Term)
match Fail args patch stack = return $ NoReduction $ NotBlocked (patch $ map ignoreReduced args)
match (Done xs t) args _ _
  | m < n     = return $ YesReduction $ applySubst (parallelS $ reverse $ toTm args)
                                      $ foldr lam t (drop m xs)
  | otherwise = return $ YesReduction $
                  applySubst (parallelS $ reverse $ toTm args0) t `apply` map ignoreReduced args1
  where
    n              = length xs
    m              = length args
    toTm           = map (unArg . ignoreReduced)
    (args0, args1) = splitAt n $ map (fmap $ fmap shared) args
    lam x t        = Lam (argHiding x) (Abs (unArg x) t)
match (Case n bs) args patch stack =
  case genericSplitAt n args of
    (_, []) -> return $ NoReduction $ NotBlocked $ patch $ map ignoreReduced args
    (args0, MaybeRed red (Arg h r v0) : args1) -> do
      w  <- case red of
              Reduced b  -> return $ fmap (const v0) b
              NotReduced ->
                unfoldCorecursion =<< instantiate v0
      cv <- constructorForm $ ignoreBlocking w
      let v      = ignoreBlocking w
          args'  = args0 ++ [MaybeRed red $ Arg h r v] ++ args1
          stack' = maybe [] (\c -> [(c, args', patch)]) (catchAllBranch bs)
                   ++ stack
          patchLit args = patch (args0 ++ [Arg h r v] ++ args1)
            where (args0, args1) = splitAt n args
          patchCon c m args = patch (args0 ++ [Arg h r $ Con c vs] ++ args1)
            where (args0, args1') = splitAt n args
                  (vs, args1)     = splitAt m args1'
      case ignoreSharing <$> w of
        Blocked x _            -> return $ NoReduction $ Blocked x (patch $ map ignoreReduced args')
        NotBlocked (MetaV x _) -> return $ NoReduction $ Blocked x (patch $ map ignoreReduced args')
        NotBlocked (Lit l) -> case Map.lookup l (litBranches bs) of
          Nothing -> match' stack''
          Just cc -> match cc (args0 ++ args1) patchLit stack''
          where
            stack'' = (++ stack') $ case ignoreSharing cv of
              Con c vs -> case Map.lookup c (conBranches bs) of
                Nothing -> []
                Just cc -> [(content cc, args0 ++ map (MaybeRed red) vs ++ args1, patchCon c (length vs))]
              _        -> []
        NotBlocked (Con c vs) -> case Map.lookup c (conBranches bs) of
          Nothing -> match' stack'
          Just cc -> match (content cc)
                           (args0 ++ map (MaybeRed red) vs ++ args1)
                           (patchCon c (length vs))
                           stack'
        NotBlocked _ -> return $ NoReduction $ NotBlocked (patch $ map ignoreReduced args')

match' :: Stack -> TCM (Reduced (Blocked Args) Term)
match' ((c, args, patch):stack) = match c args patch stack
match' [] = typeError $ GenericError "Incomplete pattern matching"

unfoldCorecursion v = case v of
  Def f args -> unfoldDefinition True unfoldCorecursion (Def f []) f args
  Shared{}   -> fmap shared <$> unfoldCorecursion (ignoreSharing v) -- don't update when unfolding corecursion!
  _          -> reduceB v
