----------------------------------------------------------------------
-- |
-- Maintainer  : PL
-- Stability   : (stable)
-- Portability : (portable)
--
-- > CVS $Date: 2005/09/01 09:53:19 $ 
-- > CVS $Author: peb $
-- > CVS $Revision: 1.7 $
--
-- Calculating the finiteness of each type in a grammar
-----------------------------------------------------------------------------

module GF.Conversion.SimpleToFinite
    (convertGrammar) where

import GF.System.Tracing
import GF.Infra.Print

import GF.Formalism.GCFG
import GF.Formalism.SimpleGFC
import GF.Formalism.Utilities 
import GF.Conversion.Types

import GF.Data.SortedList
import GF.Data.Assoc
import GF.Data.BacktrackM
import GF.Data.Utilities (lookupList)

import GF.Infra.Ident (Ident(..))

type CnvMonad a = BacktrackM () a

convertGrammar :: SGrammar -> SGrammar
convertGrammar rules = tracePrt "SimpleToFinie - nr. 'finite' rules" (prt . length) $
		       solutions cnvMonad ()
    where split = calcSplitable rules
	  cnvMonad = member rules >>= convertRule split

convertRule :: Splitable -> SRule -> CnvMonad SRule
convertRule split (Rule abs cnc) 
    = do newAbs <- convertAbstract split abs
	 return $ Rule newAbs cnc

{-
-- old code
convertAbstract :: Splitable -> Abstract SDecl Name
		-> CnvMonad (Abstract SDecl Name)
convertAbstract split (Abs decl decls name)
    = case splitableFun split (name2fun name) of
        Just cat' -> return $ Abs (Decl anyVar (mergeFun (name2fun name) cat') []) decls name
	Nothing -> expandTyping split name [] decl decls []


expandTyping :: Splitable -> Name -> [(Var, SCat)] -> SDecl -> [SDecl] -> [SDecl] 
	     -> CnvMonad (Abstract SDecl Name)
expandTyping split name env (Decl x cat args) [] decls 
    = return $ Abs decl (reverse decls) name
    where decl = substArgs split x env cat args []
expandTyping split name env typ (Decl x xcat xargs : declsToDo) declsDone
    = do (x', xcat', env') <- calcNewEnv
         let decl = substArgs split x' env xcat' xargs []
	 expandTyping split name env' typ declsToDo (decl : declsDone)
    where calcNewEnv = case splitableCat split xcat of
		         Just newFuns -> do newFun <- member newFuns
                                            let newCat  = mergeFun newFun xcat
		         -- Just newCats -> do newCat <- member newCats
					    return (anyVar, newCat, (x,newCat) : env)
			 Nothing -> return (x, xcat, env)
-}

-- new code
convertAbstract :: Splitable -> Abstract SDecl Name
		-> CnvMonad (Abstract SDecl Name)
convertAbstract split (Abs decl decls name)
    = case splitableFun split fun of
        Just cat' -> return $ Abs (Decl anyVar ([] ::--> (mergeFun fun cat' ::@ []))) decls name
	Nothing -> expandTyping split [] fun profiles [] decl decls []
    where Name fun profiles = name

expandTyping :: Splitable -> [(Var, SCat)]
             -> Fun -> [Profile (SyntaxForest Fun)] -> [Profile (SyntaxForest Fun)] 
             -> SDecl -> [SDecl] -> [SDecl] 
	     -> CnvMonad (Abstract SDecl Name)
expandTyping split env fun [] profiles (Decl x (typargs ::--> (cat ::@ args))) [] decls 
    = return $ Abs decl (reverse decls) (Name fun (reverse profiles))
    where decl = substArgs split x env typargs cat args []
expandTyping split env fun (prof:profiles) profsDone typ 
                 (Decl x (xtypargs ::--> (xcat ::@ xargs)) : declsToDo) declsDone
    = do (x', xcat', env', prof') <- calcNewEnv
         let decl = substArgs split x' env xtypargs xcat' xargs []
	 expandTyping split env' fun profiles (prof' : profsDone) typ declsToDo (decl : declsDone)
    where calcNewEnv = case splitableCat split xcat of
			 Nothing      -> return (x, xcat, env, prof)
		         Just newFuns -> do newFun <- member newFuns
                                            let newCat  = mergeFun newFun xcat
                                                newProf = Constant (FNode newFun [[]])
                                            -- should really be using some kind of
                                            -- "profile unification"
					    return (anyVar, newCat, (x,newCat) : env, newProf)

substArgs :: Splitable -> Var -> [(Var, SCat)] -> [FOType SCat]
	  -> SCat -> [TTerm] -> [TTerm] -> SDecl
substArgs split x env typargs cat [] args = Decl x (typargs ::--> (cat ::@ reverse args))
substArgs split x env typargs cat (arg:argsToDo) argsDone
    = case argLookup split env arg of
        Just newCat -> substArgs split x env typargs (mergeArg cat newCat) argsToDo argsDone
	Nothing     -> substArgs split x env typargs cat argsToDo (arg : argsDone)

argLookup split env (TVar x)   = lookup x env 
argLookup split env (con :@ _) = fmap (mergeFun fun) (splitableFun split fun)
    where fun = constr2fun con
      

----------------------------------------------------------------------
-- splitable categories (finite, no dependencies)
-- they should also be used as some dependency

type Splitable = (Assoc SCat [Fun], Assoc Fun SCat)

splitableCat :: Splitable -> SCat -> Maybe [Fun]
splitableCat = lookupAssoc . fst 

splitableFun :: Splitable -> Fun -> Maybe SCat
splitableFun = lookupAssoc . snd

calcSplitable :: [SRule] -> Splitable
calcSplitable rules = (listAssoc splitableCat2Funs, listAssoc splitableFun2Cat)
    where splitableCat2Funs = groupPairs $ nubsort splitableCatFuns

	  splitableFun2Cat = nubsort
			     [ (fun, cat) | (cat, fun) <- splitableCatFuns ]

          -- cat-fun pairs that are splitable
	  splitableCatFuns = tracePrt "SimpleToFinite - splitable functions" prt $
                             [ (cat, name2fun name) |
			       Rule (Abs (Decl _ ([] ::--> (cat ::@ []))) [] name) _ <- rules,
			       splitableCats ?= cat ]

          -- all cats that are splitable
          splitableCats = listSet $
			  tracePrt "SimpleToFinite - finite categories to split" prt $
			  (nondepCats <**> depCats) <\\> resultCats

          -- all result cats for some pure function
	  resultCats = tracePrt "SimpleToFinite - result cats" prt $
                       nubsort [ cat | Rule (Abs (Decl _ (_ ::--> (cat ::@ _))) decls _) _ <- rules,
				 not (null decls) ]

          -- all cats in constants without dependencies
          nondepCats = tracePrt "SimpleToFinite - nondep cats" prt $
                       nubsort [ cat | Rule (Abs (Decl _ ([] ::--> (cat ::@ []))) [] _) _ <- rules ]

          -- all cats occurring as some dependency of another cat
	  depCats = tracePrt "SimpleToFinite - dep cats" prt $
                    nubsort [ cat | Rule (Abs decl decls _) _ <- rules,
			      cat <- varCats [] (decls ++ [decl]) ]

	  varCats _ [] = []
	  varCats env (Decl x (xargs ::--> xtyp@(xcat ::@ _)) : decls)
	      = varCats ((x,xcat) : env) decls ++
		[ cat | (_::@args) <- (xtyp:xargs), arg <- args, 
                  y <- varsInTTerm arg, cat <- lookupList y env ]


----------------------------------------------------------------------
-- utilities
-- mergeing categories

mergeCats :: String -> String -> String -> SCat -> SCat -> SCat
mergeCats before middle after (IC cat) (IC arg) 
    = IC (before ++ cat ++ middle ++ arg ++ after)

mergeFun, mergeArg :: SCat -> SCat -> SCat
mergeFun = mergeCats "{" ":" "}"
mergeArg = mergeCats "" "" ""


