----------------------------------------------------------------------
-- |
-- Module      : GF.Speech.VoiceXML
--
-- Creates VoiceXML dialogue systems from PGF grammars.
-----------------------------------------------------------------------------
module GF.Speech.VoiceXML (grammar2vxml) where

import GF.Data.XML
import PGF2
import Data.List (intersperse) -- isPrefixOf, find
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)

--import Debug.Trace

-- | the main function
grammar2vxml :: PGF -> Concr -> String
grammar2vxml pgf cnc = showsXMLDoc (skel2vxml name mb_language start skel qs) ""
    where skel = pgfSkeleton pgf
          name = concreteName cnc
          qs = catQuestions cnc (map fst skel)
          mb_language = languageCode cnc
          (_,start,_) = unType (startCat pgf)

--
-- * VSkeleton: a simple description of the abstract syntax.
--

type Skeleton = [(Cat, [(Fun, [Cat])])]

pgfSkeleton :: PGF -> Skeleton
pgfSkeleton pgf = [(c,[(f,[cat | (_,_,ty) <- hypos, let (_,cat,_) = unType ty]) | f <- functionsByCat pgf c, Just (hypos,_,_) <- [fmap unType (functionType pgf f)]])
                     | c <- categories pgf]

--
-- * Questions to ask 
--

type CatQuestions = [(Cat,String)]

catQuestions :: Concr -> [Cat] -> CatQuestions
catQuestions cnc cats = [(c,catQuestion cnc c) | c <- cats]

catQuestion :: Concr -> Cat -> String
catQuestion cnc cat = fromMaybe cat (printName cnc cat)

getCatQuestion :: Cat -> CatQuestions -> String
getCatQuestion c qs = 
    fromMaybe (error "No question for category " ++ c) (lookup c qs)

--
-- * Generate VoiceXML
--

skel2vxml :: String -> Maybe String -> Cat -> Skeleton -> CatQuestions -> XML
skel2vxml name language start skel qs = 
    vxml language ([startForm] ++ concatMap (uncurry (catForms gr qs)) skel)
  where 
  gr = grammarURI name
  startForm = Tag "form" [] [subdialog "sub" [("src", "#"++catFormId start)] 
                                           [param "old" "{ name : '?' }"]]

grammarURI :: String -> String
grammarURI name = name ++ ".grxml"


catForms :: String -> CatQuestions -> Cat -> [(Fun, [Cat])] -> [XML]
catForms gr qs cat fs = 
    comments [cat ++ " category."]
    ++ [cat2form gr qs cat fs] 

cat2form :: String -> CatQuestions -> Cat -> [(Fun, [Cat])] -> XML
cat2form gr qs cat fs = 
  form (catFormId cat) $ 
      [var "old" Nothing, 
       blockCond "old.name != '?'" [assign "term" "old"],
       field "term" []
           [promptString (getCatQuestion cat qs), 
            vxmlGrammar (gr++"#"++catFormId cat)
           ]
      ]
     ++ concatMap (uncurry (fun2sub gr cat)) fs
     ++ [block [return_ ["term"]{-]-}]]

fun2sub :: String -> Cat -> Fun -> [Cat] -> [XML]
fun2sub gr cat fun args = 
    comments [fun ++ " : (" 
              ++ concat (intersperse ", " args)
              ++ ") " ++ cat] ++ ss
  where 
  ss = zipWith mkSub [0..] args
  mkSub n t = subdialog s [("src","#"++catFormId t),
                           ("cond","term.name == "++string  fun)] 
              [param "old" v,
               filled [] [assign v (s++".term")]]
    where s = fun ++ "_" ++ show n
          v = "term.args["++show n++"]"

catFormId :: Cat -> String
catFormId c = c ++ "_cat"


--
-- * VoiceXML stuff
--

vxml :: Maybe String -> [XML] -> XML
vxml ml = Tag "vxml" $ [("version","2.0"),
                        ("xmlns","http://www.w3.org/2001/vxml")]
                      ++ maybe [] (\l -> [("xml:lang", l)]) ml

form :: String -> [XML] -> XML
form id xs = Tag "form" [("id", id)] xs

field :: String -> [(String,String)] -> [XML] -> XML
field name attrs = Tag "field" ([("name",name)]++attrs)

subdialog :: String -> [(String,String)] -> [XML] -> XML
subdialog name attrs = Tag "subdialog" ([("name",name)]++attrs)

filled :: [(String,String)] -> [XML] -> XML
filled = Tag "filled"

vxmlGrammar :: String -> XML
vxmlGrammar uri = ETag "grammar" [("src",uri)]

prompt :: [XML] -> XML
prompt = Tag "prompt" []

promptString :: String -> XML
promptString p = prompt [Data p]
{-
reprompt :: XML
reprompt = ETag "reprompt" []
-}
assign :: String -> String -> XML
assign n e = ETag "assign" [("name",n),("expr",e)]
{-
value :: String -> XML
value expr = ETag "value" [("expr",expr)]

if_ :: String -> [XML] -> XML
if_ c b = if_else c b []

if_else :: String -> [XML] -> [XML] -> XML
if_else c t f = cond [(c,t)] f

cond :: [(String,[XML])] -> [XML] -> XML
cond ((c,b):rest) els = Tag "if" [("cond",c)] (b ++ es)
  where es = [Tag "elseif" [("cond",c')] b' | (c',b') <- rest] 
             ++ if null els then [] else (Tag "else" [] []:els)

goto_item :: String -> XML
goto_item nextitem = ETag "goto" [("nextitem",nextitem)]
-}
return_ :: [String] -> XML
return_ names = ETag "return" [("namelist", unwords names)]

block :: [XML] -> XML
block = Tag "block" []

blockCond :: String -> [XML] -> XML
blockCond cond = Tag "block" [("cond", cond)]
{-
throw :: String -> String -> XML
throw event msg = Tag "throw" [("event",event),("message",msg)] []

nomatch :: [XML] -> XML
nomatch = Tag "nomatch" []

help :: [XML] -> XML
help = Tag "help" []
-}
param :: String -> String -> XML
param name expr = ETag "param" [("name",name),("expr",expr)]

var :: String -> Maybe String -> XML
var name expr = ETag "var" ([("name",name)]++e)
  where e = maybe [] ((:[]) . (,) "expr") expr
--
-- * ECMAScript stuff
--

string :: String -> String
string s = "'" ++ concatMap esc s ++ "'"
  where esc '\'' = "\\'"
        esc c    = [c]

{-
--
-- * List stuff
--

isListCat :: (CId, [(CId, [CId])]) -> Bool
isListCat (cat,rules) = "List" `isPrefixOf` showIdent cat && length rules == 2
		    && ("Base"++c) `elem` fs && ("Cons"++c) `elem` fs
    where c = drop 4 (showIdent cat)
	  fs = map (showIdent . fst) rules

isBaseFun :: CId -> Bool
isBaseFun f = "Base" `isPrefixOf` showIdent f

isConsFun :: CId -> Bool
isConsFun f = "Cons" `isPrefixOf` showIdent f

baseSize :: (CId, [(CId, [CId])]) -> Int
baseSize (_,rules) = length bs
    where Just (_,bs) = find (isBaseFun . fst) rules
-}
