{-# LANGUAGE CPP #-}
module Agda.Compiler.Treeless.Uncase (caseToSeq) where

import Data.Monoid

import Agda.Syntax.Treeless
import Agda.Syntax.Literal
import Agda.TypeChecking.Substitute
import Agda.Compiler.Treeless.Subst
import Agda.Compiler.Treeless.Compare

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

caseToSeq :: Monad m => TTerm -> m TTerm
caseToSeq t = return $ uncase t

uncase :: TTerm -> TTerm
uncase t = case t of
  TVar{}    -> t
  TPrim{}   -> t
  TDef{}    -> t
  TApp f es -> tApp (uncase f) (map uncase es)
  TLam b    -> TLam $ uncase b
  TLit{}    -> t
  TCon{}    -> t
  TLet e b  -> tLet (uncase e) (uncase b)
  TCase x t d bs -> doCase x t (uncase d) (map uncaseAlt bs)
  TUnit{}   -> t
  TSort{}   -> t
  TErased{} -> t
  TError{}  -> t
  TCoerce t -> TCoerce (uncase t)
  where
    uncaseAlt (TACon c a b) = TACon c a $ uncase b
    uncaseAlt (TALit l b)   = TALit l $ uncase b
    uncaseAlt (TAGuard g b) = TAGuard (uncase g) (uncase b)

    doCase x t d bs
      | Just u <- mu,
        all (equalTo x u) bs = maybeSeq u
      | otherwise            = fallback
      where
        maybeSeq u | caseLazy t = u
                   | otherwise  = tApp (TPrim PSeq) [TVar x, u]
        fallback = TCase x t d bs
        (fv, mu)
          | isUnreachable d =
            case last bs of
              TACon _ a b -> (a, tryStrengthen a b)
              TALit l b   -> (0, Just b)
              TAGuard _ b -> (0, Just b)
          | otherwise = (0, Just d)

    equalTo :: Int -> TTerm -> TAlt -> Bool
    equalTo x t (TACon c a b)
      | Just b' <- tryStrengthen a b = equalTerms (subst x v t) (subst x v b')
      | otherwise                    = False
      where v = mkTApp (TCon c) (replicate a TErased)
    equalTo x t (TALit l b)   = equalTerms (subst x (TLit l) t) (subst x (TLit l) b)
    equalTo x t (TAGuard _ b) = equalTerms t b

    tLet e b =
      case occursIn 0 b of
        Occurs 0 _ _ -> strengthen __IMPOSSIBLE__ b
        _            -> TLet e b

    -- Primitive operations are already strict
    tApp (TPrim PSeq) [_, b@(TApp (TPrim op) _)]
      | op `elem` [PAdd, PSub, PMul, PLt, PGeq, PRem, PQuot] || isPrimEq op = b
    tApp f es = TApp f es
