{-# LANGUAGE DeriveFoldable            #-}
{-# LANGUAGE DeriveTraversable         #-}
{-# LANGUAGE StandaloneDeriving        #-}
{-# LANGUAGE ScopedTypeVariables       #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE TypeSynonymInstances      #-}
{-# LANGUAGE FlexibleContexts          #-}
{-# LANGUAGE FlexibleInstances         #-}
{-# LANGUAGE TupleSections             #-}
{-# LANGUAGE BangPatterns              #-}
{-# LANGUAGE PatternGuards             #-}
{-# LANGUAGE DeriveFunctor             #-}
{-# LANGUAGE MultiParamTypeClasses     #-}
{-# LANGUAGE OverloadedStrings         #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Language.Haskell.Liquid.Constraint.Axioms (

    expandProofs

    -- * Combining proofs
  , makeCombineType
  , makeCombineVar

  ) where


import Prelude hiding (error)

import Literal

import Coercion
import DataCon
import CoreSyn
import Type
import TyCon
import TypeRep
import Var
import Name
import NameSet

import Text.PrettyPrint.HughesPJ hiding (first, sep)
import Control.Monad.State
import qualified Data.List           as L
import qualified Data.HashMap.Strict as M
import Data.Maybe               (fromJust)
import Language.Fixpoint.Types.Names
import Language.Fixpoint.Utils.Files

import qualified Language.Fixpoint.Types            as F

import Language.Haskell.Liquid.UX.Tidy (panicError)
import Language.Haskell.Liquid.Types.Visitors (freeVars)
import Language.Haskell.Liquid.Types            hiding (binds, Loc, loc, freeTyVars, Def, HAxiom)
import qualified Language.Haskell.Liquid.Types as T
import Language.Haskell.Liquid.WiredIn
import Language.Haskell.Liquid.Types.RefType
import Language.Haskell.Liquid.Types.Visitors         hiding (freeVars)
import Language.Haskell.Liquid.GHC.Misc
import Language.Haskell.Liquid.GHC.SpanStack                 (showSpan)
import Language.Fixpoint.Misc                         hiding (errorstar)
import Language.Haskell.Liquid.Constraint.ProofToCore
import Language.Haskell.Liquid.Transforms.CoreToLogic
import Language.Haskell.Liquid.Constraint.Types

import System.IO.Unsafe

import Prover.Types (Axiom(..), Query(..))
import qualified Prover.Types as P
import Prover.Solve (solve)


import qualified Data.HashSet        as S



class Provable a where

  expandProofs :: GhcInfo -> [(F.Symbol, SpecType)] -> a -> CG a
  expandProofs info sigs x =
    do (x, s) <- runState (expProofs x) <$> initAEEnv info sigs
       modify $ \st -> st {freshIndex = ae_index s}
       return x

  expProofs :: a -> Pr a
  expProofs = return


instance Provable CoreBind where
  -- expProofs (NonRec x e) | returnsProof x =  (\e -> Rec [(traceShow ("\n\nMake it Rec\n\n" ++ show (F.symbol x)) x,e)]) <$> (addRec (x,e) >> expProofs e)
  expProofs (NonRec x e) =
     do e' <- addRec (x,e) >> expProofs e
        if x `elem` freeVars S.empty e'
          then return $ Rec [(x, e')]
          else return $ NonRec x e'
  expProofs (Rec xes)    = Rec      <$> (addRecs xes  >> mapSndM expProofs xes)


instance Provable CoreExpr where
  expProofs ee@(App (App (Tick _ (Var f)) i) e) | isAuto f = grapInt i >>= expandAutoProof ee e
  expProofs ee@(App (App (Var f) i) e)          | isAuto f = grapInt i >>= expandAutoProof ee e
  expProofs ee@(App (Tick _ (App (Tick _ (Var f)) i)) e) | isAuto f = grapInt i >>= expandAutoProof ee e
  expProofs ee@(App (Tick _ (App (Var f) i)) e)          | isAuto f = grapInt i >>= expandAutoProof ee e


  expProofs ee@(App (App (Tick _ (Var f)) i) e) | isCases f = grapInt i >>= expandCasesProof ee e
  expProofs ee@(App (App (Var f) i) e)          | isCases f = grapInt i >>= expandCasesProof ee e
  expProofs ee@(App (Tick _ (App (Tick _ (Var f)) i)) e) | isCases f = grapInt i >>= expandCasesProof ee e
  expProofs ee@(App (Tick _ (App (Var f) i)) e)          | isCases f = grapInt i >>= expandCasesProof ee e

  expProofs (App e1 e2) = liftM2 App (expProofs e1) (expProofs e2)
  expProofs (Lam x e)   = addVar x >> liftM  (Lam x) (expProofs e)
  expProofs (Let b e)   = do b' <- expProofs b
                             addBind b'
                             liftM (Let b') (expProofs e)
  expProofs (Case e v t alts) = liftM2 (\e -> Case e v t) (expProofs e) (mapM (expProofsCase e) alts)
  expProofs (Cast e c)   = liftM (`Cast` c) (expProofs e)
  expProofs (Tick t e)   = liftM (Tick t) (expProofs e)

  expProofs (Var v)      = return $ Var v
  expProofs (Lit l)      = return $ Lit l
  expProofs (Type t)     = return $ Type t
  expProofs (Coercion c) = return $ Coercion c



expProofsCase :: CoreExpr -> CoreAlt -> Pr CoreAlt
expProofsCase (Var x) (DataAlt c, xs, e)
  = do addVars xs
       t <- L.lookup (symbol c) . ae_sigs <$> get
       addAssert $ makeRefinement t (x:xs)
       res <- liftM (DataAlt c,xs,) (expProofs e)
       rmAssert
       return res

expProofsCase _ (c, xs, e)
  = addVars xs >> liftM (c,xs,) (expProofs e)


instance Provable CoreAlt where
  expProofs (c, xs, e) = addVars xs >> liftM (c,xs,) (expProofs e)

expandCasesProof :: CoreExpr -> CoreExpr -> Integer -> Pr CoreExpr
expandCasesProof inite e it
  = do vs <-  reverse . ae_vars <$> get
       case L.find (isAlgType . varType) vs of
          Nothing -> return inite
          Just v  -> makeCases v inite e it

makeDataCons v = data_cons $ algTyConRhs tc
  where
    t  = varType v
    tc = fst $ splitTyConApp t

makeCases v inite e it = Case (Var v) v (varType v) <$> (mapM go $ makeDataCons v)
  where
    go c = do xs <- makeDataConArgs v c
              addVars xs
              t <- L.lookup (symbol c) . ae_sigs <$> get
              addAssert $ makeRefinement t (v:xs)
              proof <- expandAutoProof inite (e) it
              rmAssert
              return (DataAlt c, xs, proof)

makeDataConArgs v dc = mapM freshVar ts
  where
    ts = dataConInstOrigArgTys dc ats
    ats = snd $ splitTyConApp $ varType v


expandAutoProof :: CoreExpr -> CoreExpr -> Integer -> Pr CoreExpr
expandAutoProof inite e it
  =  do ams  <- ae_axioms  <$> get
        vs'  <- ae_vars    <$> get
        cts  <- ae_consts  <$> get
        ds   <- ae_assert  <$> get
        cmb  <- ae_cmb     <$> get
        lmap <- ae_lmap    <$> get
        isHO <- ae_isHO    <$> get 
        e'   <- unANFExpr e

        foldM (\lm x -> (updateLMap lm (dummyLoc $ F.symbol x) x >> (ae_lmap <$> get))) lmap vs'

        let (vs, vlits)  = L.partition (`elem` readVars e') $ nub' vs'
        let allvs        = nub'  ((fst . aname <$> ams) ++ cts  ++ vs')
        let (cts', vcts) = L.partition (isFunctionType . varType) allvs
        let usedVs = nub' (vs++vcts)

        env    <- makeEnvironment ((L.\\) allvs usedVs) ((L.\\) vlits usedVs)
        ctors  <- mapM makeCtor cts'
        pvs    <- mapM makeVar usedVs
        le     <- makeGoalPredicate e'
        fn     <- freshFilePath
        axioms <- makeAxioms
        let sol = unsafePerformIO (solve $ makeQuery fn it isHO le axioms ctors ds env pvs)
        return $ {-
          traceShow (
            "\n\nTo prove\n" ++ show (showpp le) ++
            "\n\nWe need \n" ++ show sol         ++
            "\n\nExpr =  \n" ++ show (toCore cmb inite sol)         ++
            "\n\n"
           ) $ -}
          traceShow "\nexpandedExpr\n" $ toCore cmb inite sol

nub' = L.nubBy (\v1 v2 -> F.symbol v1 == F.symbol v2)

-- TODO: merge this with the Bare.Axiom.hs
updateLMap :: LogicMap  -> LocSymbol -> Var -> Pr ()
updateLMap _ _ v | not (isFun $ varType v)
  = return ()
  where
    isFun (FunTy _ _)    = True
    isFun (ForAllTy _ t) = isFun t
    isFun  _             = False

updateLMap _ x vv
  = insertLogicEnv x' ys (F.eApps (F.EVar $ val x) (F.EVar <$> ys))
  where
    nargs = dropWhile isClassType $ ty_args $ toRTypeRep $ ((ofType $ varType vv) :: RRType ())

    ys = zipWith (\i _ -> symbol (("x" ++ show i) :: String)) [1..] nargs
    x' = simpleSymbolVar vv

insertLogicEnv x ys e
  = modify $ \be -> be {ae_lmap = (ae_lmap be) {logic_map = M.insert x (LMap x ys e) $ logic_map $ ae_lmap be}}

simpleSymbolVar  x = dropModuleNames $ symbol $ showPpr $ getName x

-------------------------------------------------------------------------------
----------------   From Haskell to Prover  ------------------------------------
-------------------------------------------------------------------------------





makeEnvironment :: [Var] -> [Var] -> Pr [P.LVar]
makeEnvironment avs vs
  = do lits <- ae_lits <$> get
       let lts'  = filter (\(x,_) -> not (x `elem` (F.symbol <$> avs))) (normalize lits)
       let lts1  = [P.Var x s () | (x, s) <- lts']
       lts2  <- mapM makeLVar vs
       return (lts1 ++ lts2)



makeQuery :: FilePath -> Integer -> Bool -> F.Expr -> [HAxiom] -> [HVarCtor] -> [F.Expr] -> [P.LVar] ->  [HVar] -> HQuery
makeQuery fn i isHO p axioms cts ds env vs 
 = Query   { q_depth  = fromInteger i
           , q_goal   = P.Pred p

           , q_vars   = checkVar  <$> vs      -- local variables
           , q_ctors  = cts                   -- constructors: globals with function type
           , q_env    = checkEnv  <$> env     -- environment: anything else that can appear in the logic

           , q_fname  = fn
           , q_axioms = axioms
           , q_decls  = (P.Pred <$> ds)
           , q_isHO   = isHO 
           }

checkEnv pv@(P.Var x s _)
  | isBaseSort s = pv
  | otherwise    = panic Nothing ("\nEnv:\nNon Basic " ++ show x ++ "  ::  " ++ show s)

checkVar pv@(P.Var x s _)
  | isBaseSort s = pv
  | otherwise    = panic Nothing ("\nVar:\nNon Basic " ++ show x ++ "  ::  " ++ show s)

makeAxioms =
  do recs <- ae_recs    <$> get
     tce  <- ae_emb     <$> get
     sigs <- ae_sigs    <$> get
     gs   <- ae_globals <$> get
     let (rgs, gs') = L.partition (`elem` (fst <$> recs)) $ filter returnsProof gs
     let as1 = varToPAxiom tce sigs <$> gs'
     let as2 = varToPAxiomWithGuard tce sigs recs <$> rgs
     return (as1 ++ as2)

unANFExpr e = (foldl (flip Let) e . ae_binds) <$> get

makeGoalPredicate e =
  do lm   <- ae_lmap    <$> get
     tce  <- ae_emb     <$> get
     case runToLogic tce lm (ErrOther (showSpan "makeGoalPredicate") . text) (coreToPred e) of
       Left p    -> return p
       Right err -> panicError err

makeRefinement :: Maybe SpecType -> [Var] -> F.Expr
makeRefinement Nothing  _ = F.PTrue
makeRefinement (Just t) xs = rr
  where trep = toRTypeRep t
        ys   = [x | (x, t') <- zip (ty_binds trep) (ty_args trep), not (isClassType t')]
        rr   = case stripRTypeBase $ ty_res trep of
                 Nothing  -> F.PTrue
                 Just ref -> let F.Reft(v, r) = F.toReft ref
                                 su = F.mkSubst $ zip (v:ys) (F.EVar . F.symbol <$> xs)
                             in F.subst su r



makeCtor :: Var -> Pr HVarCtor
makeCtor c
  = do tce  <- ae_emb     <$> get
       sigs <- ae_sigs    <$> get
       lmap <- ae_lmap    <$> get
       lvs  <- ae_vars    <$> get
       return $ makeCtor' tce lmap sigs (c `elem` lvs) c

makeCtor' :: F.TCEmb TyCon -> LogicMap -> [(F.Symbol, SpecType)] -> Bool -> Var -> HVarCtor
makeCtor' tce _ _ islocal  v | islocal
  = P.VarCtor (P.Var (F.symbol v) (typeSort tce $ varType v) v) [] (P.Pred F.PTrue)

makeCtor' tce lmap sigs _  v
  = case M.lookup v (axiom_map lmap) of
    Nothing -> P.VarCtor (P.Var (F.symbol v) (typeSort tce $ varType v)      v) vs r
    Just x  -> P.VarCtor (P.Var x            (typeSort tce $ varType v) v) [] (P.Pred F.PTrue)

  where
    x    = F.symbol v
    (vs, r) = case L.lookup x sigs of
                Nothing -> ([], P.Pred F.PTrue)
                Just t  -> let trep = toRTypeRep t
                           in case stripRTypeBase $ ty_res trep of
                               Nothing -> ([], P.Pred F.PTrue)
                               Just r  -> let (F.Reft(v, p)) = F.toReft r
                                              xts = [(x,t) | (x, t) <- zip (ty_binds trep) (ty_args trep), not $ isClassType t]
                                              e  = F.mkEApp (dummyLoc x) (F.EVar . fst  <$> xts)
                                          in ([P.Var x (rTypeSort tce t) ()  | (x, t) <- xts], P.Pred $ F.subst1 p (v, e))

makeVar :: Var -> Pr HVar
makeVar v = do {tce <- ae_emb <$> get; return $ makeVar' tce v}

makeVar'  tce v = P.Var (F.symbol v) (typeSort tce $ varType v) v

makeLVar :: Var -> Pr P.LVar
makeLVar v = do {tce <- ae_emb <$> get; return $ makeLVar' tce v}

makeLVar' tce v = P.Var (F.symbol v) (typeSort tce $ varType v) ()



varToPAxiomWithGuard :: F.TCEmb TyCon -> [(Symbol, SpecType)] -> [(Var, [Var])] -> Var -> HAxiom
varToPAxiomWithGuard tce sigs recs v
  = P.Axiom { axiom_name = makeVar' tce v
            , axiom_vars = vs
            , axiom_body = P.Pred $ F.PImp q bd
            }
  where
    q = makeGuard $ zip (symbol <$> args) xts
    args = fromJust $ L.lookup v recs
    x = F.symbol v
    (vs, xts, bd) = case L.lookup x sigs of
                     Nothing -> panic Nothing ("haxiomToPAxiom: " ++ show x ++ " not found")
                     Just t -> let trep = toRTypeRep t
                                   bd'  = case stripRTypeBase $ ty_res trep of
                                            Nothing -> F.PTrue
                                            Just r  -> let (F.Reft(_, p)) = F.toReft r in p
                                   xts   = filter (not . isClassType . snd) $ zip (ty_binds trep) (ty_args trep)
                                   vs'   = [P.Var x (rTypeSort tce t) () | (x, t) <- xts]
                               in  (vs', xts, bd')

makeGuard :: [(F.Symbol, (F.Symbol, SpecType))] -> F.Expr
makeGuard xs = F.POr $ go [] xs
  where
    go _ []
      = []
    go acc ((x, (x', RApp c _ _ _)):xxs)
     | Just f <- sizeFunction $ rtc_info c
     = (F.PAnd (F.PAtom F.Lt (f x') (f x):acc)) : go (F.PAtom F.Le (f x') (f x):acc) xxs
    go acc (_:xxs)
     = go acc xxs


varToPAxiom :: F.TCEmb TyCon -> [(Symbol, SpecType)] -> Var -> HAxiom
varToPAxiom tce sigs v
  = P.Axiom { axiom_name = makeVar' tce v
            , axiom_vars = vs
            , axiom_body = P.Pred bd
            }
  where
    x = F.symbol v
    (vs, bd) = case L.lookup x sigs of
                Nothing -> panic Nothing ("haxiomToPAxiom: " ++ show x ++ " not found")
                Just t -> let trep = toRTypeRep t
                              bd'  = case stripRTypeBase $ ty_res trep of
                                       Nothing -> F.PTrue
                                       Just r  -> let (F.Reft(_, p)) = F.toReft r in p
                              vs'   = [P.Var x (rTypeSort tce t) () | (x, t) <- zip (ty_binds trep) (ty_args trep), not $ isClassType t]
                          in  (vs', bd')


-------------------------------------------------------------------------------
-------------  Proof State Environment ----------------------------------------
-------------------------------------------------------------------------------

type Pr = State AEnv

data AEnv = AE { ae_axioms  :: [T.HAxiom]            -- axiomatized functions
               , ae_binds   :: [CoreBind]            -- local bindings, tracked st they are expanded in logic
               , ae_lmap    :: LogicMap              -- logical mapping
               , ae_consts  :: [Var]                 -- Data constructors and imported variables
               , ae_globals :: [Var]                 -- Global definitions, like axioms
               , ae_vars    :: [Var]                 -- local variables in scope
               , ae_emb     :: F.TCEmb TyCon         -- type constructor information
               , ae_lits    :: [(Symbol, F.Sort)]    -- literals
               , ae_index   :: Integer               -- unique integer
               , ae_sigs    :: [(Symbol, SpecType)]  -- Refined type signatures
               , ae_target  :: FilePath              -- file name of target source coude
               , ae_recs    :: [(Var, [Var])]        -- axioms that are used recursively:
                                                     -- these axioms are guarded to used only with "smaller" arguments
               , ae_assert  :: [F.Expr]              --
               , ae_cmb     :: CoreExpr -> CoreExpr -> CoreExpr  -- how to combine proofs
               , ae_isHO    :: Bool                  -- allow higher order binders 
               }


initAEEnv info sigs
    = do tce    <- tyConEmbed  <$> get
         lts    <- lits        <$> get
         i      <- freshIndex  <$> get
         modify $ \s -> s{freshIndex = i + 1}
         return $ AE { ae_axioms  = axioms spc
                     , ae_binds   = []
                     , ae_lmap    = logicMap spc
                     , ae_consts  = L.nub vs
                     , ae_globals = L.nub tp
                     , ae_vars    = []
                     , ae_emb     = tce
                     , ae_lits    = wiredSortedSyms ++ lts
                     , ae_index   = i
                     , ae_sigs    = sigs
                     , ae_target  = target info
                     , ae_recs    = []
                     , ae_assert  = []
                     , ae_cmb     = \x y -> (App (App (Var by) x) y)
                     , ae_isHO    = higherorder $ config spc 
                     }
    where
      spc        = spec info
      vs         = filter validVar (snd <$> freeSyms spc)
      tp         = filter validExp (defVars info)

      isExported = flip elemNameSet (exports $ spec info) . getName
      validVar   = not . canIgnore
      validExp x = validVar x && isExported x
      by         = makeCombineVar $ makeCombineType τProof
      τProof     = proofType $ spec info




addBind b     = modify $ \ae -> ae{ae_binds = b:ae_binds ae}
addAssert p   = modify $ \ae -> ae{ae_assert = p:ae_assert  ae}
rmAssert      = modify $ \ae -> ae{ae_assert = tail $ ae_assert ae}
addRec  (x,e) = modify $ \ae -> ae{ae_recs  = (x, grapArgs e):ae_recs  ae}
addRecs xes   = modify $ \ae -> ae{ae_recs  = [(x, grapArgs e) | (x, e) <- xes] ++ ae_recs  ae}

addVar  x | canIgnore x = return ()
          | otherwise   = modify $ \ae -> ae{ae_vars  = x:ae_vars  ae}


addVars x = modify $ \ae -> ae{ae_vars  = x' ++ ae_vars  ae}
  where
    x' = filter (not . canIgnore) x

getUniq :: Pr Integer
getUniq
  = do modify (\s -> s{ae_index = 1 + (ae_index s)})
       ae_index <$> get


freshVar :: Type -> Pr Var
freshVar t =
  do n <- getUniq
     return $ stringVar ("x" ++ show n) t

freshFilePath :: Pr FilePath
freshFilePath =
  do fn <- ae_target <$> get
     n  <- getUniq
     return $ (extFileName (Auto $ fromInteger n) fn)


-------------------------------------------------------------------------------
--------------  Playing with Fixpoint  ----------------------------------------
-------------------------------------------------------------------------------


isBaseSort _ = True 



-------------------------------------------------------------------------------
--------------  Playing with GHC Core  ----------------------------------------
-------------------------------------------------------------------------------

-- hasBaseType = isBaseTy . varType

isFunctionType (FunTy _ _)    = True
isFunctionType (ForAllTy _ t) = isFunctionType t
isFunctionType _              = False


resultType (ForAllTy _ t) = resultType t
resultType (FunTy _ t)    = resultType t
resultType  t             = t


grapArgs (Lam x e) | isTyVar x  = grapArgs e
grapArgs (Lam x e) | isClassPred $ varType x = grapArgs e
grapArgs (Lam x e) = x : grapArgs e
grapArgs (Let _ e) = grapArgs e
grapArgs _         = []



grapInt (Var v)
  = do bs <- ae_binds <$> get
       let (e:_) = [ex | NonRec x ex <- bs, x == v]
       return $ go e
  where
    go (Tick _ e) = go e
    go (App _ l)  = go l
    go (Lit l)    = litToInt l
    go e          = panic Nothing $ ("grapInt called with wrong argument " ++ showPpr e)

    litToInt (MachInt i) = i
    litToInt (MachInt64 i) = i
    litToInt _             = panic Nothing "litToInt: non integer literal"

grapInt (Tick _ e) = grapInt e
grapInt _          = return 2


-------------------------------------------------------------------------------
--------------------  Combine Proofs  ----------------------------------------
-------------------------------------------------------------------------------

makeCombineType Nothing
  = panic Nothing "proofType not found"
makeCombineType (Just τ)
  = FunTy τ (FunTy τ τ)


makeCombineVar τ =  stringVar combineProofsName τ
-------------------------------------------------------------------------------
-------------------  Helper Functions  ----------------------------------------
-------------------------------------------------------------------------------

canIgnore v = isInternal v || isTyVar v
isAuto    v = isPrefixOfSym "auto"  $ dropModuleNames $ F.symbol v
isCases   v = isPrefixOfSym "cases" $ dropModuleNames $ F.symbol v
isProof   v = isPrefixOfSym "Proof" $ dropModuleNames $ F.symbol v


returnsProof :: Var -> Bool
returnsProof = isProof' . resultType . varType
  where
    isProof' (TyConApp tc _) = isProof tc
    isProof' _               = False


normalize xts = filter hasBaseSort $ L.nub xts
  where
    hasBaseSort = isBaseSort . snd


mapSndM act xys = mapM (\(x, y) -> (x,) <$> act y) xys
