module BrownPLT.JavaScript.Contracts.Template 
  ( JavaScriptTemplate
  , exprTemplate
  , stmtTemplate
  , substVar
  , substVarList
  , substIdList
  , substFieldList
  , expandCall
  , templateExpression
  , noPos
  , thunkExpr
  , renderTemplate
  , renameVar
  , templateStatements
  ) where

import Data.Data
import Data.Generics
import Text.ParserCombinators.Parsec (parse, many1)
import Text.ParserCombinators.Parsec.Pos (initialPos, SourcePos)
import Text.PrettyPrint.HughesPJ (render)
import BrownPLT.JavaScript.PrettyPrint (renderExpression, renderStatements)
import BrownPLT.JavaScript.Parser
import BrownPLT.JavaScript.Syntax
import BrownPLT.JavaScript.Instances()
import BrownPLT.JavaScript.Crawl() -- hack for instance 

noPos :: SourcePos
noPos = initialPos "template"

thunkExpr :: ParsedExpression -> ParsedExpression
thunkExpr expr = FuncExpr noPos [] (ReturnStmt noPos (Just expr))

-- We may extend this later so that template definitions explicitly
-- state their free identifiers
data JavaScriptTemplate
  = ExpressionTemplate ParsedExpression
  | StatementTemplate [ParsedStatement]

exprTemplate :: String -> JavaScriptTemplate
exprTemplate str = case parse assignExpr "expression template" str of
  Left err -> error ("Error parsing template: " ++ show err ++ 
                     "; template:\n\n" ++ str)
  Right expr -> ExpressionTemplate expr

stmtTemplate :: String -> JavaScriptTemplate
stmtTemplate str = case parse (many1 parseStatement) "statement template" str of
  Left err -> error ("Error parsing template: " ++ show err ++ 
                     "; template:\n\n" ++ str)
  Right stmts -> StatementTemplate stmts
  
renderTemplate :: JavaScriptTemplate -> String
renderTemplate (ExpressionTemplate expr) = renderExpression expr
renderTemplate (StatementTemplate stmts) = renderStatements stmts

templateExpression :: JavaScriptTemplate -> ParsedExpression
templateExpression (ExpressionTemplate expr) = expr

templateStatements :: JavaScriptTemplate -> [ParsedStatement]
templateStatements (StatementTemplate stmts) = stmts

expandCall :: String -- ^function name to expand
           -> ([ParsedExpression] -> [ParsedExpression]) -- ^argument expander
           -> JavaScriptTemplate
           -> JavaScriptTemplate
expandCall functionId expander (StatementTemplate body) =
  StatementTemplate (everywhere (mkT subst) body) where
    subst (CallExpr p1 fn@(VarRef p2 (Id p3 id')) args) 
      | id' == functionId = CallExpr p1 fn (expander args)
    subst expr = expr

renameVar :: String -- ^original id
          -> String -- ^new id
          -> JavaScriptTemplate
          -> JavaScriptTemplate
renameVar idOld idNew body =
  let -- explicit signatures needed for generics
      substExpr :: ParsedExpression -> ParsedExpression
      substExpr (VarRef p1 (Id p2 thisId))
        | thisId == idOld = VarRef p1 (Id p2 idNew)
      substExpr v = v
      substDecl :: VarDecl SourcePos -> VarDecl SourcePos
      substDecl (VarDecl p1 (Id p2 thisId) val)
        | thisId == idOld = VarDecl p1 (Id p2 idNew) val
      substDecl v = v
    in case body of
         ExpressionTemplate body ->  ExpressionTemplate $
           everywhere ((mkT substDecl) . (mkT substExpr)) body
         StatementTemplate stmts -> StatementTemplate $
           everywhere (mkT substDecl . mkT substExpr) stmts

substVar :: String -- ^free identifier
         -> ParsedExpression -- ^expression to substitute
         -> JavaScriptTemplate
         -> JavaScriptTemplate
substVar id expr body = 
  let subst (VarRef _ (Id _ id')) | id' == id = expr
      subst expr = expr
    in case body of
         ExpressionTemplate body -> 
           ExpressionTemplate (everywhere (mkT subst) body)
         StatementTemplate stmts ->
           StatementTemplate (everywhere (mkT subst) stmts)

substVarList :: String -- ^identifier in a list
             -> [ParsedExpression] -- ^list of expressions to substitute
             -> JavaScriptTemplate
             -> JavaScriptTemplate
substVarList id exprs (ExpressionTemplate body) =
  ExpressionTemplate (everywhere (mkT subst) body) where
    subst [VarRef _ (Id _ id')] | id' == id = exprs
    subst lst = lst

substIdList :: String -- ^identifier in a list
            -> [String] -- ^list of identifiers to substitute
            -> JavaScriptTemplate
            -> JavaScriptTemplate
substIdList id ids (ExpressionTemplate body) =
  ExpressionTemplate (everywhere (mkT subst) body) where
    subst [Id _ id'] | id' == id = map (Id noPos) ids
    subst lst = lst

substFieldList :: String -- ^ placeholder field name
               -> [(String,ParsedExpression)] -- ^list of fields
               -> JavaScriptTemplate
               -> JavaScriptTemplate
substFieldList fieldId fields (ExpressionTemplate body) = 
  ExpressionTemplate (everywhere (mkT subst) body) where
    fields' = map (\(name,expr) -> (PropId noPos (Id noPos name),expr)) fields
    subst [(PropId _ (Id _ id'), _)] | id' == fieldId = fields'
    subst lst = lst