-- inlines everything into everything.
-- NB. Does not yet avoid variable name clashes except for inlets/outlets
module Inline (inline) where

import Graph
import Data.List (filter,minimumBy,(\\),union,mapAccumL)
import UniqueNames

type Def = (String,Graph)
type Defs = [Def]

size graph = length (nodes graph)

inline::Defs->Defs
inline globals = uncurry (++) (fix inlineOne (globals,[]))

fix f x = let x' = f x in
          if x == x' then x else fix f x'

smallest::Defs->Def
smallest = minimumBy (comparing (size.snd))

inlineOne::(Defs,Defs)->(Defs,Defs)
inlineOne ([],inlined) = ([],inlined)
inlineOne (uninlined,inlined) =
    let (name,subgraph) = smallest uninlined
        uninlined1 = filter ((/=name).fst) uninlined
        transform (n,g) = (n,inlineInGraph name subgraph g)
    in (map transform uninlined1,(name,subgraph):map transform inlined)

comparing key a b = compare (key a) (key b)

inlineInGraph name subgraph graph =
    let tabuVars = mentionedVarsInGraph graph ++
                   mentionedStatesInGraph graph
        (substitutions,newNodes) = mapAccumL (inlineInNode name subgraph) tabuVars (nodes graph)
    in graph { nodes=concat newNodes }

inlineInNode::String->Graph->[String]->Node->([String],[Node])
inlineInNode name subgraph tabuVars node@(Node ins (App name2) outs)
    | name == name2
      = let substitutions1 = zip (inlets subgraph) ins ++
                             zip (outlets subgraph) outs
            usedVars = (mentionedVarsInGraph subgraph ++
                       mentionedStatesInGraph subgraph) \\ (inlets subgraph ++ outlets subgraph)
            clashingVars = union usedVars tabuVars
            substituteVars = getUniqueNames tabuVars clashingVars
            substitutions2 = zip clashingVars substituteVars
            substitutions3 = substitutions1++substitutions2
        in (substituteVars++tabuVars
           ,map (renameVarsInNode substitutions3) (nodes subgraph)
           )
    | otherwise
      = (tabuVars,[node])
inlineInNode name subgraph tabuVars node = (tabuVars,[node])


renameVarsInNode substitutions (Node ins op outs) =
    Node (map (rename substitutions) ins) (renameVarsInOp substitutions op) (map (rename substitutions) outs)

renameVarsInOp substitutions op =
    case op of
      Get v -> Get (rename substitutions v)
      Put v -> Put (rename substitutions v)
      _ -> op

rename::[(String,String)]->String->String
rename subs name =
    maybe name id (lookup name subs)


test1 = inline [("f",Graph { inlets=["x"],
                             outlets=["y"],
                             nodes=[Node ["x"] Add ["z"]
                                   ,Node ["z"] Add ["y"]]})
               ,("g",Graph { inlets=["y"],
                             outlets=["z"],
                             nodes=[Node ["y"] (App "f") ["z"]]})
               ]