{-# LANGUAGE BangPatterns #-}
module GF.Compile.OptimizePGF(optimizePGF) where

import PGF2(Cat,Fun)
import PGF2.Transactions
import Data.Array.ST
import Data.Array.Unboxed
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.IntSet as IntSet
import qualified Data.IntMap as IntMap
import qualified Data.List as List
import Control.Monad.ST

type ConcrData = ()
{-([(FId,[FunId])],          -- ^ Lindefs
                  [(FId,[FunId])],          -- ^ Linrefs
                  [(FId,[Production])],     -- ^ Productions
                  [(Fun,[SeqId])],          -- ^ Concrete functions   (must be sorted by Fun)
                  [[Symbol]],               -- ^ Sequences            (must be sorted)
                  [(Cat,FId,FId,[String])]) -- ^ Concrete categories
-}
optimizePGF :: Cat -> ConcrData -> ConcrData
optimizePGF startCat = error "TODO: optimizePGF" {- topDownFilter startCat . bottomUpFilter

catString = "String"
catInt    = "Int"
catFloat  = "Float"
catVar    = "__gfVar"

topDownFilter :: Cat -> ConcrData -> ConcrData
topDownFilter startCat (lindefs,linrefs,prods,cncfuns,sequences,cnccats) =
  let env0             = (Map.empty,Map.empty)
      (env1,lindefs')  = List.mapAccumL (\env (fid,funids) -> let (env',funids') = List.mapAccumL (optimizeFun fid [PArg [] fidVar]) env funids in (env',(fid,funids')))
                                        env0
                                        lindefs
      (env2,linrefs')  = List.mapAccumL (\env (fid,funids) -> let (env',funids') = List.mapAccumL (optimizeFun fidVar [PArg [] fid]) env funids in (env',(fid,funids')))
                                        env1
                                        linrefs
      (env3,prods')    = List.mapAccumL (\env (fid,set)    -> let (env',set') = List.mapAccumL (optimizeProd fid) env set in (env',(fid,set')))
                                        env2
                                        prods
      cnccats' = map filterCatLabels cnccats
      (sequences',cncfuns') = env3
  in (lindefs',linrefs',prods',mkSetArray cncfuns',mkSetArray  sequences',cnccats')
  where
    cncfuns_array   = listArray (0,length cncfuns-1)   cncfuns   :: Array FunId (Fun, [SeqId])
    sequences_array = listArray (0,length sequences-1) sequences :: Array SeqId [Symbol]
    prods_map  = IntMap.fromList prods
    fid2catMap = IntMap.fromList ((fidVar,catVar) :  [(fid,cat) | (cat,start,end,lbls) <- cnccats,
                                                                  fid <- [start..end]])

    fid2cat fid =
      case IntMap.lookup fid fid2catMap of
        Just cat -> cat
        Nothing  -> case [fid | Just set <- [IntMap.lookup fid prods_map], PCoerce fid <- set] of
                      (fid:_) -> fid2cat fid
                      _       -> error "unknown forest id"

    starts =
      [(startCat,lbl) | (cat,_,_,lbls) <- cnccats, cat==startCat, lbl <- [0..length lbls-1]]

    allRelations =
      Map.unionsWith Set.union
                     [rel fid prod | (fid,set) <- prods, prod <- set]
      where
        rel fid (PApply funid args) = Map.fromList [((fid2cat fid,lbl),deps args seqid) | (lbl,seqid) <- zip [0..] lin]
          where
            (_,lin) = cncfuns_array ! funid
        rel fid _                   = Map.empty

        deps args seqid = Set.fromList [let PArg _ fid = args !! r in (fid2cat fid,d) | SymCat r d <- seq]
          where
            seq = sequences_array ! seqid

    -- here we create a mapping from a category to an array of indices.
    -- An element of the array is equal to -1 if the corresponding index
    -- is not going to be used in the optimized grammar, or the new index
    -- if it will be used
    closure :: Map.Map Cat [Int]
    closure = runST $ do
      set <- initSet
      addLitCat catString set
      addLitCat catInt    set
      addLitCat catFloat  set
      addLitCat catVar    set
      closureSet set starts
      doneSet set
      where
        initSet :: ST s (Map.Map Cat (STUArray s Int Int))
        initSet =
          fmap Map.fromList $ sequence
                        [fmap ((,) cat) (newArray (0,length lbls-1) (-1))
                                             | (cat,_,_,lbls) <- cnccats]

        addLitCat cat set =
          case Map.lookup cat set of
            Just indices -> writeArray indices 0 0
            Nothing      -> return ()

        closureSet set []                 = return ()
        closureSet set (x@(cat,index):xs) =
          case Map.lookup cat set of
            Just indices -> do v <- readArray indices index
                               writeArray indices index 0
                               if v < 0
                                 then case Map.lookup x allRelations of
                                        Just ys -> closureSet set (Set.toList ys++xs)
                                        Nothing -> closureSet set xs
                                 else closureSet set xs
            Nothing      -> error "unknown cat"

        doneSet :: Map.Map Cat (STUArray s Int Int) -> ST s (Map.Map Cat [Int])
        doneSet set =
          fmap Map.fromAscList $ mapM done (Map.toAscList set)
          where
            done (cat,indices) = do
              indices <- fmap (reindex 0) (getElems indices)
              return (cat,indices)

            reindex k []     = []
            reindex k (v:vs)
              | v < 0        = v : reindex k     vs
              | otherwise    = k : reindex (k+1) vs

    optimizeProd res env (PApply funid args) =
      let (env',funid') = optimizeFun res args env funid
      in (env', PApply funid' args)
    optimizeProd res env prod = (env,prod)

    optimizeFun res args (seqs,funs) funid =
      let (seqs',lin')   = List.mapAccumL addUnique seqs [map updateSymbol (sequences_array ! seqid) | 
                                                                   (idx,seqid) <- zip (indicesOf res) lin, idx >= 0]
          (funs',funid') = addUnique funs (fun, lin')
      in ((seqs',funs'), funid')
      where
        (fun,lin) = cncfuns_array ! funid

        indicesOf fid
          | fid < 0   = [0]
          | otherwise =
              case Map.lookup (fid2cat fid) closure of
                Just indices -> indices
                Nothing      -> error "unknown category"

        addUnique seqs seq =
          case Map.lookup seq seqs of
            Just seqid -> (seqs,seqid)
            Nothing    -> let seqid = Map.size seqs
                          in (Map.insert seq seqid seqs, seqid)

        updateSymbol (SymCat r d) = let PArg _ fid = args !! r in SymCat r (indicesOf fid !! d)
        updateSymbol s            = s

    filterCatLabels (cat,start,end,lbls) =
      case Map.lookup cat closure of
        Just indices -> let lbls' = [lbl | (idx,lbl) <- zip indices lbls, idx >= 0]
                        in (cat,start,end,lbls')
        Nothing      -> error ("unknown category")

    mkSetArray map = sortSnd (Map.toList map)
      where
        sortSnd = List.map fst . List.sortBy (\(_,i) (_,j) -> compare i j)


bottomUpFilter :: ConcrData -> ConcrData
bottomUpFilter (lindefs,linrefs,prods,cncfuns,sequences,cnccats) =
  (lindefs,linrefs,filterProductions IntMap.empty IntSet.empty prods,cncfuns,sequences,cnccats)

filterProductions prods0 hoc0 prods
  | prods0 == prods1 = IntMap.toList prods0
  | otherwise        = filterProductions prods1 hoc1 prods
  where
    (prods1,hoc1) = foldl foldProdSet (IntMap.empty,IntSet.empty) prods

    foldProdSet (!prods,!hoc) (fid,set)
      | null set1 = (prods,hoc)
      | otherwise = (IntMap.insert fid set1 prods,hoc1)
      where
        set1 = filter filterRule set
        hoc1 = foldl accumHOC hoc set1

    filterRule (PApply funid args) = all (\(PArg _ fid) -> isLive fid) args
    filterRule (PCoerce fid)       = isLive fid
    filterRule _                   = True

    isLive fid = isPredefFId fid || IntMap.member fid prods0 || IntSet.member fid hoc0

    accumHOC hoc (PApply funid args) = List.foldl' (\hoc (PArg hypos _) -> List.foldl' (\hoc fid -> IntSet.insert fid hoc) hoc (map snd hypos)) hoc args
    accumHOC hoc _                   = hoc
-}
