module GF.Grammar.Analyse (
        stripSourceGrammar,
        constantDepsTerm,
        sizeTerm,
        sizeConstant,
        sizesModule,
        sizesGrammar,
        printSizesGrammar
        ) where

import GF.Grammar.Grammar
import GF.Infra.Ident
import GF.Text.Pretty(render)
--import GF.Infra.Option ---
import GF.Grammar.Macros
import GF.Grammar.Lookup

import GF.Data.Operations

import qualified Data.Map as Map
import Data.List (nub)
--import Debug.Trace

stripSourceGrammar :: Grammar -> Grammar
stripSourceGrammar sgr = mGrammar [(i, m{jments = Map.map stripInfo (jments m)}) | (i,m) <- modules sgr]

stripInfo :: Info -> Info
stripInfo i = case i of
  AbsCat _ -> i
  AbsFun mt me -> AbsFun mt Nothing
  ResParam mp mt -> ResParam mp Nothing
  ResValue lt _ -> i ----
  ResOper mt md -> ResOper mt Nothing
  ResOverload is fs -> ResOverload is [(lty, L loc (EInt 0)) | (lty,L loc _) <- fs]
  CncCat mty mte _ mtf mpmcfg -> CncCat mty Nothing Nothing Nothing Nothing
  CncFun mict mte mtf mpmcfg -> CncFun mict Nothing Nothing Nothing
  AnyInd b f -> i

constantsInTerm :: Term -> [QIdent]
constantsInTerm = nub . consts where
  consts t = case t of
    Q c  -> [c]
    QC c -> [c]
    _ -> collectOp consts t

constantDeps :: Grammar -> QIdent -> Err [QIdent]
constantDeps sgr f = return $ nub $ iterFix more start where
  start = constants f
  more  = concatMap constants
  constants c = (c :) $ fromErr [] $ do
    ts  <- termsOfConstant sgr c
    return $ concatMap constantsInTerm ts

getIdTerm :: Term -> Err QIdent
getIdTerm t = case t of
  Q i  -> return i
  QC i -> return i
  P (Vr r) l -> return (MN r,label2ident l) --- needed if term is received from parser
  _ -> Bad ("expected qualified constant, not " ++ show t)

constantDepsTerm :: Grammar -> Term -> Err [Term]
constantDepsTerm sgr t = do
  i <- getIdTerm t
  cs <- constantDeps sgr i
  return $ map Q cs  --- losing distinction Q/QC

termsOfConstant :: Grammar -> QIdent -> Err [Term]
termsOfConstant sgr c = case lookupOverload sgr c of
  Ok tts -> return $ concat [[ty,tr] | (_,(ty,tr)) <- tts]
  _ -> return $
         [ty | Ok ty <- [lookupResType sgr c]] ++  -- type sig may be missing
         [ty | Ok ty <- [lookupResDef sgr c]]

sizeConstant :: Grammar -> Term -> Int
sizeConstant sgr t = err (const 0) id $ do
  c  <- getIdTerm t
  fmap (sum . map sizeTerm) $ termsOfConstant sgr c

-- the number of constructors in a term, ignoring position information and unnecessary types
-- ground terms count as 1, i.e. as "one work" each
sizeTerm :: Term -> Int
sizeTerm t = case t of
  App c a      -> sizeTerm c + sizeTerm a  -- app nodes don't count 
  Abs _ _ b    -> 2 + sizeTerm b
  Prod _ _ a b -> 2 + sizeTerm a + sizeTerm b 
  S c a        -> 1 + sizeTerm c + sizeTerm a
  Table a c    -> 1 + sizeTerm a + sizeTerm c
  ExtR a c     -> 1 + sizeTerm a + sizeTerm c
  R r          -> 1 + sum [1 + sizeTerm a | (_,(_,a)) <- r]  -- label counts as 1, type ignored
  RecType r    -> 1 + sum [1 + sizeTerm a | (_,_,a)   <- r]  -- label counts as 1
  P t i        -> 2 + sizeTerm t
  T _ cc       -> 1 + sum [1 + sizePatt p + sizeTerm v | (p,v) <- cc]
  V ty cc      -> 1 + sizeTerm ty + sum [1 + sizeTerm v | v <- cc]
  Let (x,(mt,a)) b -> 2 + maybe 0 sizeTerm mt + sizeTerm a + sizeTerm b
  C s1 s2      -> 1 + sizeTerm s1 + sizeTerm s2 
  Glue s1 s2   -> 1 + sizeTerm s1 + sizeTerm s2 
  Alts t aa    -> 1 + sizeTerm t + sum [sizeTerm p + sizeTerm v | (p,v) <- aa]
  FV ts        -> 1 + sum (map sizeTerm ts)
  Strs tt      -> 1 + sum (map sizeTerm tt)
  _            -> 1

sizePatt :: Patt -> Int
sizePatt p = case p of
  PC c pp   -> 1 + sum (map sizePatt pp)
  PP c pp   -> 1 + sum (map sizePatt pp)
  PR r      -> 1 + sum [sizePatt p | (l,p) <- r]
  PT _ p    -> sizePatt p
  PAs _ p   -> sizePatt p
  PSeq _ _ a _ _ b  -> 1 + sizePatt a + sizePatt b
  PAlt a b  -> 1 + sizePatt a + sizePatt b
  PRep _ _ a-> 1 + sizePatt a
  PNeg a    -> 1 + sizePatt a
  _         -> 1

-- the size of a judgement
sizeInfo :: Info -> Int
sizeInfo i = case i of
  AbsCat (Just (L _ co)) -> 1 + sum [1 + sizeTerm ty | (_,_,ty) <- co]
  AbsFun mt me -> 1 + msize mt + 
    sum [sum (map sizePatt ps) + sizeTerm t | Just (_,es) <- [me], L _ (ps,t) <- es]
  ResParam mp mt -> 
    1 + sum [1 + sum [1 + sizeTerm ty | (_,_,ty) <- co] | Just (L _ ps) <- [mp], (_,co) <- ps]
  ResValue _ _ -> 0
  ResOper mt md -> 1 + msize mt + msize md
  ResOverload is fs -> 1 + sum [sizeTerm ty + sizeTerm tr | (L _ ty, L _ tr) <- fs]
  CncCat mty _ _ _ _ -> 1 + msize mty   -- ignoring lindef, linref and printname
  CncFun mict mte mtf _ -> 1 + msize mte  -- ignoring type and printname
  AnyInd b f -> -1  -- just to ignore these in the size
  _ -> 0
 where 
  msize mt = case mt of
    Just (L _ t) -> sizeTerm t
    _ -> 0
{-
-- the size of a module
sizeModule :: SourceModule -> Int
sizeModule = fst . sizesModule
-}
sizesModule :: SourceModule -> (Int, [(Ident,Int)])
sizesModule (_,m) = 
  let 
    js = Map.toList (jments m) 
    tb = [(i,k) | (i,j) <- js, let k = sizeInfo j, k >= 0]
  in (length tb + sum (map snd tb),tb)
{-
-- the size of a grammar
sizeGrammar :: Grammar -> Int
sizeGrammar = fst . sizesGrammar
-}
sizesGrammar :: Grammar -> (Int,[(ModuleName,(Int,[(Ident,Int)]))])
sizesGrammar g = 
  let 
    ms = modules g 
    mz = [(i,sizesModule m) | m@(i,j) <- ms]
  in (length mz + sum (map (fst . snd) mz), mz)

printSizesGrammar :: Grammar -> String
printSizesGrammar g = unlines $ 
  ("total" +++ show s):
  [render m +++ "total" +++ show i ++++ 
   unlines [indent 2 (showIdent j +++ show k) | (j,k) <- js]
     | (m,(i,js)) <- sg
  ]
 where
   (s,sg) = sizesGrammar g


