{-# LANGUAGE DeriveDataTypeable #-}

module Idris.Error where

import Prelude hiding (catch)
import Idris.AbsSyntax
import Idris.Delaborate
import Idris.Output

import Idris.Core.Evaluate (ctxtAlist)
import Idris.Core.TT
import Idris.Core.Typecheck
import Idris.Core.Constraints
import Idris.Output

import System.Console.Haskeline
import System.Console.Haskeline.MonadException
import Control.Monad (when)
import Control.Monad.State.Strict
import Control.Monad.Error (throwError, catchError)
import System.IO.Error(isUserError, ioeGetErrorString)
import Data.Char
import Data.List (intercalate, isPrefixOf)
import qualified Data.Text as T
import Data.Typeable
import qualified Data.Traversable as Traversable
import qualified Data.Foldable as Foldable

iucheck :: Idris ()
iucheck = do tit <- typeInType
             when (not tit) $
                do ist <- getIState
                   (tclift $ ucheck (idris_constraints ist)) `idrisCatch`
                              (\e -> do setErrSpan (getErrSpan e)
                                        iputStrLn (pshow ist e))

showErr :: Err -> Idris String
showErr e = getIState >>= return . flip pshow e


report :: IOError -> String
report e
    | isUserError e = ioeGetErrorString e
    | otherwise     = show e

idrisCatch :: Idris a -> (Err -> Idris a) -> Idris a
idrisCatch = catchError


setAndReport :: Err -> Idris ()
setAndReport e = do ist <- getIState
                    case (unwrap e) of
                      At fc e -> do setErrSpan fc
                                    iWarn fc $ pprintErr ist e
                      _ -> do setErrSpan (getErrSpan e)
                              iWarn emptyFC $ pprintErr ist e
  where unwrap (ProofSearchFail e) = e -- remove bookkeeping constructor
        unwrap e = e

ifail :: String -> Idris a
ifail = throwError . Msg

ierror :: Err -> Idris a
ierror = throwError

tclift :: TC a -> Idris a
tclift (OK v) = return v
tclift (Error err@(At fc e)) = do setErrSpan fc; throwError err
tclift (Error err) = throwError err

tctry :: TC a -> TC a -> Idris a
tctry tc1 tc2
    = case tc1 of
           OK v -> return v
           Error err -> tclift tc2

getErrSpan :: Err -> FC
getErrSpan (At fc _) = fc
getErrSpan _ = emptyFC

--------------------------------------------------------------------
-- Specific warnings not included in elaborator
--------------------------------------------------------------------
-- | Issue a warning on "with"-terms whose namespace is empty or nonexistent
warnDisamb :: IState -> PTerm -> Idris ()
warnDisamb ist (PQuote _) = return ()
warnDisamb ist (PRef _ _) = return ()
warnDisamb ist (PInferRef _ _) = return ()
warnDisamb ist (PPatvar _ _) = return ()
warnDisamb ist (PLam _ t b) = warnDisamb ist t >> warnDisamb ist b
warnDisamb ist (PPi _ _ t b) = warnDisamb ist t >> warnDisamb ist b
warnDisamb ist (PLet _ x t b) = warnDisamb ist x >> warnDisamb ist t >> warnDisamb ist b
warnDisamb ist (PTyped x t) = warnDisamb ist x >> warnDisamb ist t
warnDisamb ist (PApp _ t args) = warnDisamb ist t >>
                                 mapM_ (warnDisamb ist . getTm) args
warnDisamb ist (PAppBind _ f args) = warnDisamb ist f >>
                                     mapM_ (warnDisamb ist . getTm) args
warnDisamb ist (PMatchApp _ _) = return ()
warnDisamb ist (PCase _ tm cases) = warnDisamb ist tm >>
                                    mapM_ (\(x,y)-> warnDisamb ist x >> warnDisamb ist y) cases
warnDisamb ist (PTrue _ _) = return ()
warnDisamb ist (PFalse _) = return ()
warnDisamb ist (PRefl _ tm) = warnDisamb ist tm
warnDisamb ist (PResolveTC _) = return ()
warnDisamb ist (PEq _ a b x y) = warnDisamb ist a >> warnDisamb ist b >>
                                 warnDisamb ist x >> warnDisamb ist y
warnDisamb ist (PRewrite _ x y z) = warnDisamb ist x >> warnDisamb ist y >>
                                    Foldable.mapM_ (warnDisamb ist) z
warnDisamb ist (PPair _ _ x y) = warnDisamb ist x >> warnDisamb ist y
warnDisamb ist (PDPair _ _ x y z) = warnDisamb ist x >> warnDisamb ist y >> warnDisamb ist z
warnDisamb ist (PAlternative _ tms) = mapM_ (warnDisamb ist) tms
warnDisamb ist (PHidden tm) = warnDisamb ist tm
warnDisamb ist PType = return ()
warnDisamb ist (PUniverse _) = return ()
warnDisamb ist (PGoal _ x _ y) = warnDisamb ist x >> warnDisamb ist y
warnDisamb ist (PConstant _) = return ()
warnDisamb ist Placeholder = return ()
warnDisamb ist (PDoBlock steps) = mapM_ wStep steps
  where wStep (DoExp _ x) = warnDisamb ist x
        wStep (DoBind _ _ x) = warnDisamb ist x
        wStep (DoBindP _ x y cs) = warnDisamb ist x >> warnDisamb ist y >>
                                   mapM_ (\(x,y)-> warnDisamb ist x >> warnDisamb ist y) cs
        wStep (DoLet _ _ x y) = warnDisamb ist x >> warnDisamb ist y
        wStep (DoLetP _ x y) = warnDisamb ist x >> warnDisamb ist y
warnDisamb ist (PIdiom _ x) = warnDisamb ist x
warnDisamb ist (PReturn _) = return ()
warnDisamb ist (PMetavar _) = return ()
warnDisamb ist (PProof tacs) = mapM_ (Foldable.mapM_ (warnDisamb ist)) tacs
warnDisamb ist (PTactics tacs) = mapM_ (Foldable.mapM_ (warnDisamb ist)) tacs
warnDisamb ist (PElabError _) = return ()
warnDisamb ist PImpossible = return ()
warnDisamb ist (PCoerced tm) = warnDisamb ist tm
warnDisamb ist (PDisamb ds tm) = warnDisamb ist tm >>
                                 mapM_ warnEmpty ds
  where warnEmpty d =
          when (null (filter (isIn d . fst) (ctxtAlist (tt_ctxt ist)))) $
            ierror . Msg $
              "Nothing found in namespace \"" ++
              intercalate "." (map T.unpack d) ++
              "\"."
        isIn d (NS _ ns) = isPrefixOf d ns
        isIn d _ = False

warnDisamb ist (PUnifyLog tm) = warnDisamb ist tm
warnDisamb ist (PNoImplicits tm) = warnDisamb ist tm
warnDisamb ist (PQuasiquote tm goal) = warnDisamb ist tm >>
                                       Foldable.mapM_ (warnDisamb ist) goal
warnDisamb ist (PUnquote tm) = warnDisamb ist tm