module Sifflet.Foreign.Exporter 
    (Exporter
    , simplifyExpr
    , commonRuleHigherPrec
    , commonRuleAtomic
    , commonRuleLeftToRight
    , commonRuleAssocRight
    , commonRuleFuncOp
    , commonRulesForSimplifyingExprs
    , ruleIfRight
    , ruleRightToLeft
    , applyFirstMatch
    , findFixed
    ) 
where
import Sifflet.Language.Expr
type Exporter = Functions -> FilePath -> IO ()
simplifyExpr :: [Expr -> Expr] -> Expr -> Expr
simplifyExpr rules expr = 
    findFixed (topDown (applyFirstMatch rules)) expr
findFixed :: (Eq a) => (a -> a) -> a -> a
findFixed f x =
    let x' = f x
    in if x' == x then x else findFixed f x'
commonRuleHigherPrec :: Expr -> Expr
commonRuleHigherPrec e =
    case e of
      EOp op1 (EGroup (EOp op2 subleft subright)) right ->
          
          if opPrec op2 > opPrec op1
          then EOp op1 (EOp op2 subleft subright) right
          else e
      EOp op1 left (EGroup (EOp op2 subleft subright)) ->
          
          if opPrec op2 > opPrec op1
          then EOp op1 left (EOp op2 subleft subright)
          else e
      _ -> e
commonRuleAtomic :: Expr -> Expr
commonRuleAtomic e =
    case e of
      EGroup e' ->
          if exprIsAtomic e' 
          then e'
          else e
      _ -> e
commonRuleLeftToRight :: Expr -> Expr
commonRuleLeftToRight e =
    case e of
      EOp op2 (EGroup (EOp op1 a b)) c ->
          if opPrec op1 == opPrec op2 && 
             opGrouping op1 == GroupLtoR &&
             opGrouping op2 == GroupLtoR
          then EOp op2 (EOp op1 a b) c
          else e
      _ -> e
ruleRightToLeft :: Expr -> Expr
ruleRightToLeft e =
    case e of
      EOp op1 a (EGroup (EOp op2 b c)) ->
          if op1 == op2 && opGrouping op1 == GroupRtoL
          then EOp op1 a (EOp op2 b c)
          else e
      _ -> e
commonRuleAssocRight :: Expr -> Expr
commonRuleAssocRight e =
    case e of
      EOp op1 a (EGroup (EOp op2 b c)) -> 
          if op1 == op2 && opAssoc op1
          then EOp op1 a (EOp op2 b c)
          else e
      _ -> e
ruleIfRight :: Expr -> Expr
ruleIfRight e =
    case e of
      EOp op a (EGroup i@(EIf _ _ _)) -> EOp op a i
      _ -> e
commonRuleFuncOp :: Expr -> Expr
commonRuleFuncOp e =
    case e of
      EOp op a (EGroup c@(ECall _ _)) -> EOp op a c
      EOp op (EGroup c@(ECall _ _)) b -> EOp op c b
      _ -> e
commonRulesForSimplifyingExprs :: [Expr -> Expr]
commonRulesForSimplifyingExprs =
    [commonRuleHigherPrec
    , commonRuleAtomic
    , commonRuleLeftToRight
    , commonRuleAssocRight
    , commonRuleFuncOp]
applyFirstMatch :: [Expr -> Expr] -> Expr -> Expr
applyFirstMatch [] e = e
applyFirstMatch (r:rs) e = 
    let e' = r e
    in if e' /= e
       then e'
       else applyFirstMatch rs e
topDown :: (Expr -> Expr) -> Expr -> Expr
topDown f e =
    let tdf = topDown f
        e' = f e
    in case e' of
         EIf c a b -> EIf (tdf c) (tdf a) (tdf b)
         EList xs -> EList (map tdf xs)
         ECall fsym args -> ECall fsym (map tdf args)
         EOp op left right -> EOp op (tdf left) (tdf right)
         EGroup e'' -> EGroup (tdf e'')
         _ -> e'