-- | Translate grammars to Canonical form
-- (a common intermediate representation to simplify export to other formats)
module GF.Compile.GrammarToCanonical(
       grammar2canonical
       ) where

import GF.Data.ErrM
import GF.Grammar
import GF.Grammar.Lookup(allOrigInfos,lookupOrigInfo)
import GF.Infra.Option(Options,noOptions)
import GF.Infra.CheckM
import GF.Compile.Compute
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Maybe(mapMaybe,fromMaybe)
import Control.Monad (forM)

-- | Generate Canonical code for the named abstract syntax and all associated
-- concrete syntaxes
grammar2canonical :: Options -> ModuleName -> Grammar -> Check Grammar
grammar2canonical opts absname gr = do
  abs <- abstract2canonical absname gr
  cncs <- concretes2canonical opts absname gr
  return (mGrammar (abs:cncs))

-- | Generate Canonical code for the named abstract syntax
abstract2canonical :: ModuleName -> Grammar -> Check Module
abstract2canonical absname gr = do
  let infos = [(id,info) | ((mn,id),info) <- allOrigInfos gr absname]
  return (absname, ModInfo {
                     mtype   = MTAbstract,
                     mstatus = MSComplete,
                     mflags  = convFlags gr absname,
                     mextend = [],
                     mwith   = Nothing,
                     mopens  = [],
                     mexdeps = [],
                     msrc    = "",
                     jments  = Map.fromList infos
                   })

-- | Generate Canonical code for the all concrete syntaxes associated with
-- the named abstract syntax in given the grammar.
concretes2canonical :: Options -> ModuleName -> Grammar -> Check [Module]
concretes2canonical opts absname gr = do
  res <- sequence
           [concrete2canonical gr absname cnc modinfo
               | cnc<-allConcretes gr absname,
                 let Ok modinfo = lookupModule gr cnc]
  let pts = Set.unions (map fst res)
  ms <- closure pts (Set.toList pts) (Map.fromList (map snd res))
  return (Map.toList ms)
  where
    closure pts []            ms = return ms
    closure pts (q@(m,id):qs) ms = do
      (_,info@(ResParam (Just (L _ ps)) _)) <- lookupOrigInfo gr q
      let pts'    = Set.unions [paramTypes ty | (_,ctx) <- ps, (_,_,ty) <- ctx]
          new_pts = Set.difference pts' pts
      closure (Set.union new_pts pts) (Set.toList new_pts++qs) (insert q info ms)

    insert (m,id) info ms =
      let mi0 = fromMaybe emptyRes (Map.lookup m ms)
          mi  = mi0{jments=Map.insert id info (jments mi0)}
      in Map.insert m mi ms

    emptyRes =
      ModInfo {
        mtype   = MTResource,
        mstatus = MSComplete,
        mflags  = noOptions,
        mextend = [],
        mwith   = Nothing,
        mopens  = [],
        mexdeps = [],
        msrc    = "",
        jments  = Map.empty
      }

type QSet = Set.Set (ModuleName,Ident)

-- | Generate Canonical GF for the given concrete module.
concrete2canonical :: Grammar -> ModuleName -> ModuleName -> ModuleInfo -> Check (QSet,Module)
concrete2canonical gr absname cncname modinfo = do
  let g = Gl gr (stdPredef g) False
  infos <- mapM (convInfo g) (allOrigInfos gr cncname)
  let pts = Set.unions (map fst infos)
  return (pts,
          (cncname, ModInfo {
                      mtype   = MTConcrete absname,
                      mstatus = MSComplete,
                      mflags  = convFlags gr cncname,
                      mextend = [],
                      mwith   = Nothing,
                      mopens  = [],
                      mexdeps = [],
                      msrc    = "",
                      jments  = Map.fromList (mapMaybe snd infos)
                    }))
  where
    convInfo g ((mn,id), CncCat (Just (L loc typ)) lindef linref pprn mpmcfg) = do
      typ <- normalForm g typ
      let pts = paramTypes typ
      return (pts,Just (id,CncCat (Just (L loc typ)) lindef linref pprn mpmcfg))
    convInfo g ((mn,id), CncFun mb_ty@(Just r@(_,cat,ctx,lincat)) (Just (L loc def)) pprn mpmcfg) = do
      def <- normalForm g (eta_expand def ctx)
      return (Set.empty,Just (id,CncFun mb_ty (Just (L loc def)) pprn mpmcfg))
    convInfo g  _ = return (Set.empty,Nothing)

    eta_expand t []                   = t
    eta_expand t ((Implicit,x,_):ctx) = Abs Implicit x (eta_expand (App t (ImplArg (Vr x))) ctx)
    eta_expand t ((Explicit,x,_):ctx) = Abs Explicit x (eta_expand (App t (Vr x))           ctx)


paramTypes (RecType fs) = Set.unions (map (\(_,_,t)->paramTypes t) fs)
paramTypes (Table t1 t2) = Set.union (paramTypes t1) (paramTypes t2)
paramTypes (App tf ta) = Set.union (paramTypes tf) (paramTypes ta)
paramTypes (Sort _) = Set.empty
paramTypes (EInt _) = Set.empty
paramTypes (QC q) = Set.singleton q
paramTypes (FV ts) = Set.unions (map paramTypes ts)
paramTypes _ = Set.empty


convFlags :: Grammar -> ModuleName -> Options
convFlags gr mn = err (const noOptions) mflags (lookupModule gr mn)
