-- | Generates a C program reproducing a given sequence of commands.
-- The generated program relies on definitions in testlib.h and testlib.c.
generateCTest :: [ProcCommand] -> String
generateCTest cmds = let procCmds = groupBy ((==) `on` fst)
                                    $ sortBy (compare `on` fst)
                                    $ concatMap (\(ps,c) -> map (flip (,) c) ps) cmds
                      in render$ includes $$ vcat (map wrapProcCmds procCmds) $$ driver cmds $$ genMain cmds
  where
    cciStatement (ConnectTo _ pd c _) = text "" $$ text ("connect(p,"++show c++",test_uri["++show pd++"]);")
    cciStatement (WaitConnection c) = text "" $$ text ("cci_connection_t* c"++show c++" = wait_connection(p,"++show c++");")
    cciStatement (Send c si msg) = text "" $$ text ("send(p,c"++show c++","++show si++","++show (msgLen msg)++");")
    cciStatement (Disconnect c) = text "" $$ text ("disconnect(p,c"++show c++");")
    cciStatement (WaitSendCompletion cid sid) = text "" $$ text ("wait_send(p,"++show cid++","++show sid++");")
    cciStatement (WaitRecv cid rid) = text "" $$ text ("wait_recv(p,"++show cid++","++show rid++");")
    cciStatement (Accept _) = empty
    cciStatement (RMAReuseRMAHandle cid) = text "" $$ text ("rma_reuse(p,"++show cid++");")
    cciStatement (RMAHandleExchange cid sid) = text "" $$ text ("rma_handle_exchange(p,"++show cid++","++show sid++");")
    cciStatement (RMAFreeHandles cid) = text "" $$ text ("rma_free_handle(p,"++show cid++");")
    cciStatement (RMAWaitExchange cid) = text "" $$ text ("rma_wait_exchange(p,"++show cid++");")
    cciStatement (RMAPrepareRead cid sid) = text "" $$ text ("rma_prepare_read(p,"++show cid++","++show sid++");")
    cciStatement (RMARead cid sid) = text "" $$ text ("rma_read(p,"++show cid++","++show sid++");")
    cciStatement (RMAWaitRead cid sid) = text "" $$ text ("rma_wait_read(p,"++show cid++","++show sid++");")
    cciStatement (RMAWrite cid sid) = text "" $$ text ("rma_write(p,"++show cid++","++show sid++");")
    cciStatement (RMAWaitWrite cid sid) = text "" $$ text ("rma_wait_write(p,"++show cid++","++show sid++");")
    cciStatement cmd = text "" $$ text ("unknown command: " ++ show cmd ++";")

    msgLen (Msg _ l) = l
    msgLen _ = error "generateCTest: unexpected message"

    wrapProcCmds cs@((i,_):_) = 
             text ("void process"++show i++"(proc_t* p) {")
             $$ nest 4 (vcat (map (cciStatement . snd) cs) 
                        $$ text ""
                        $$ text "finalize(p);"
                       )
             $$ char '}'
             $$ text ""
    wrapProcCmds _ = error "TestGen.wrapProcCmds"

    driver cs =
             text "void drive(proc_t** p) {"
             $$ nest 4 (text "char buf[100];")
             $$ nest 4 (vcat$ map readWrites cs)
             $$ char '}'
             $$ text ""

    readWrites (_,Accept _) = empty
    readWrites (ps,_) = vcat$ text "" : map (\i->text$ "write_msg(p["++show i++"],\"\");") ps 
                              ++ map (\i->text$ "read_msg(p["++show i++"],buf);") ps

    includes = vcat$ map text
        [ "#include <stdio.h>"
        , "#include <string.h>"
        , "#include <strings.h>"
        , "#include <stdlib.h>"
        , "#include <unistd.h>"
        , "#include <inttypes.h>"
        , "#include <assert.h>"
        , "#include <sys/time.h>"
        , "#include <time.h>"
        , "#include \"cci.h\""
        , "#include \"testlib.h\""
        , ""
        ]

    genMain cs =
         let maxProcIndex = maximum$ concatMap fst cs
             ps = sort$ nub$ concatMap fst cs
          in text "int main(int argc, char *argv[]) {"
             $$ (nest 4$ vcat$
                   [ text "proc_t* p[100];"
                   , text "memset(p,0,100*sizeof(proc_t*));"
                   , text ""
                   , hcat (map vcat$ map1 (mapFirst (nest 7))$ map (genSpawn (maxProcIndex+1)) ps)
                       <> vcat 
                          [ nest 7$ text "{"
                          , nest 4$ vcat
                              [ text ("write_uris(p,"++show (maxProcIndex+1)++");")
                              , text "drive(p);"
                              , text "wait();"
                              ]
                          , char '}' 
                          , text "return 0;"
                          ]
                   ]
                )
             $$ char '}'

    genSpawn n i = [ text$ "if (!spawn(&p["++show i++"],"++show i++")) {"
                   , nest 4 (
                       text ("read_uris(p["++show i++"],"++show n++");")
                       $$ text ("process"++show i++"(p["++show i++"]);")
                     )
                   , text "} else "
                   ]

    map1 _ [] = []
    map1 f (x:xs) = x : map f xs

    mapFirst _ [] = []
    mapFirst f (x:xs) = f x : xs

-- Command generation

-- | Takes the amount of processes, and generates commands for an interaction among them.
generateCTest :: [ProcCommand] -> String
generateCTest cmds = let procCmds = groupBy ((==) `on` fst)
                                    $ sortBy (compare `on` fst)
                                    $ concatMap (\(ps,c) -> map (flip (,) c) ps) cmds
                      in render$ includes $$ vcat (map wrapProcCmds procCmds) $$ driver cmds $$ genMain cmds
  where
    cciStatement (ConnectTo _ pd c _) = text "" $$ text ("connect(p,"++show c++",test_uri["++show pd++"]);")
    cciStatement (WaitConnection c) = text "" $$ text ("cci_connection_t* c"++show c++" = wait_connection(p,"++show c++");")
    cciStatement (Send c si msg) = text "" $$ text ("send(p,c"++show c++","++show si++","++show (msgLen msg)++");")
    cciStatement (Disconnect c) = text "" $$ text ("disconnect(p,c"++show c++");")
    cciStatement (WaitSendCompletion cid sid) = text "" $$ text ("wait_send(p,"++show cid++","++show sid++");")
    cciStatement (WaitRecv cid rid) = text "" $$ text ("wait_recv(p,"++show cid++","++show rid++");")
    cciStatement (Accept _) = empty
    cciStatement (RMAReuseRMAHandle cid) = text "" $$ text ("rma_reuse(p,"++show cid++");")
    cciStatement (RMAHandleExchange cid sid) = text "" $$ text ("rma_handle_exchange(p,"++show cid++","++show sid++");")
    cciStatement (RMAFreeHandles cid) = text "" $$ text ("rma_free_handle(p,"++show cid++");")
    cciStatement (RMAWaitExchange cid) = text "" $$ text ("rma_wait_exchange(p,"++show cid++");")
    cciStatement (RMAPrepareRead cid sid) = text "" $$ text ("rma_prepare_read(p,"++show cid++","++show sid++");")
    cciStatement (RMARead cid sid) = text "" $$ text ("rma_read(p,"++show cid++","++show sid++");")
    cciStatement (RMAWaitRead cid sid) = text "" $$ text ("rma_wait_read(p,"++show cid++","++show sid++");")
    cciStatement (RMAWrite cid sid) = text "" $$ text ("rma_write(p,"++show cid++","++show sid++");")
    cciStatement (RMAWaitWrite cid sid) = text "" $$ text ("rma_wait_write(p,"++show cid++","++show sid++");")
    cciStatement cmd = text "" $$ text ("unknown command: " ++ show cmd ++";")

    msgLen (Msg _ l) = l
    msgLen _ = error "generateCTest: unexpected message"

    wrapProcCmds cs@((i,_):_) = 
             text ("void process"++show i++"(proc_t* p) {")
             $$ nest 4 (vcat (map (cciStatement . snd) cs) 
                        $$ text ""
                        $$ text "finalize(p);"
                       )
             $$ char '}'
             $$ text ""
    wrapProcCmds _ = error "TestGen.wrapProcCmds"

    driver cs =
             text "void drive(proc_t** p) {"
             $$ nest 4 (text "char buf[100];")
             $$ nest 4 (vcat$ map readWrites cs)
             $$ char '}'
             $$ text ""

    readWrites (_,Accept _) = empty
    readWrites (ps,_) = vcat$ text "" : map (\i->text$ "write_msg(p["++show i++"],\"\");") ps 
                              ++ map (\i->text$ "read_msg(p["++show i++"],buf);") ps

    includes = vcat$ map text
        [ "#include <stdio.h>"
        , "#include <string.h>"
        , "#include <strings.h>"
        , "#include <stdlib.h>"
        , "#include <unistd.h>"
        , "#include <inttypes.h>"
        , "#include <assert.h>"
        , "#include <sys/time.h>"
        , "#include <time.h>"
        , "#include \"cci.h\""
        , "#include \"testlib.h\""
        , ""
        ]

    genMain cs =
         let maxProcIndex = maximum$ concatMap fst cs
             ps = sort$ nub$ concatMap fst cs
          in text "int main(int argc, char *argv[]) {"
             $$ (nest 4$ vcat$
                   [ text "proc_t* p[100];"
                   , text "memset(p,0,100*sizeof(proc_t*));"
                   , text ""
                   , hcat (map vcat$ map1 (mapFirst (nest 7))$ map (genSpawn (maxProcIndex+1)) ps)
                       <> vcat 
                          [ nest 7$ text "{"
                          , nest 4$ vcat
                              [ text ("write_uris(p,"++show (maxProcIndex+1)++");")
                              , text "drive(p);"
                              , text "wait();"
                              ]
                          , char '}' 
                          , text "return 0;"
                          ]
                   ]
                )
             $$ char '}'

    genSpawn n i = [ text$ "if (!spawn(&p["++show i++"],"++show i++")) {"
                   , nest 4 (
                       text ("read_uris(p["++show i++"],"++show n++");")
                       $$ text ("process"++show i++"(p["++show i++"]);")
                     )
                   , text "} else "
                   ]

    map1 _ [] = []
    map1 f (x:xs) = x : map f xs

    mapFirst _ [] = []
    mapFirst f (x:xs) = f x : xs

-- Command generation

-- | Takes the amount of processes, and generates commands for an interaction among them.
generateCTest cmds = let procCmds = groupBy ((==) `on` fst)
                                    $ sortBy (compare `on` fst)
                                    $ concatMap (\(ps,c) -> map (flip (,) c) ps) cmds
                      in render$ includes $$ vcat (map wrapProcCmds procCmds) $$ driver cmds $$ genMain cmds
  where
    cciStatement (ConnectTo _ pd c _) = text "" $$ text ("connect(p,"++show c++",test_uri["++show pd++"]);")
    cciStatement (WaitConnection c) = text "" $$ text ("cci_connection_t* c"++show c++" = wait_connection(p,"++show c++");")
    cciStatement (Send c si msg) = text "" $$ text ("send(p,c"++show c++","++show si++","++show (msgLen msg)++");")
    cciStatement (Disconnect c) = text "" $$ text ("disconnect(p,c"++show c++");")
    cciStatement (WaitSendCompletion cid sid) = text "" $$ text ("wait_send(p,"++show cid++","++show sid++");")
    cciStatement (WaitRecv cid rid) = text "" $$ text ("wait_recv(p,"++show cid++","++show rid++");")
    cciStatement (Accept _) = empty
    cciStatement (RMAReuseRMAHandle cid) = text "" $$ text ("rma_reuse(p,"++show cid++");")
    cciStatement (RMAHandleExchange cid sid) = text "" $$ text ("rma_handle_exchange(p,"++show cid++","++show sid++");")
    cciStatement (RMAFreeHandles cid) = text "" $$ text ("rma_free_handle(p,"++show cid++");")
    cciStatement (RMAWaitExchange cid) = text "" $$ text ("rma_wait_exchange(p,"++show cid++");")
    cciStatement (RMAPrepareRead cid sid) = text "" $$ text ("rma_prepare_read(p,"++show cid++","++show sid++");")
    cciStatement (RMARead cid sid) = text "" $$ text ("rma_read(p,"++show cid++","++show sid++");")
    cciStatement (RMAWaitRead cid sid) = text "" $$ text ("rma_wait_read(p,"++show cid++","++show sid++");")
    cciStatement (RMAWrite cid sid) = text "" $$ text ("rma_write(p,"++show cid++","++show sid++");")
    cciStatement (RMAWaitWrite cid sid) = text "" $$ text ("rma_wait_write(p,"++show cid++","++show sid++");")
    cciStatement cmd = text "" $$ text ("unknown command: " ++ show cmd ++";")

    msgLen (Msg _ l) = l
    msgLen _ = error "generateCTest: unexpected message"

    wrapProcCmds cs@((i,_):_) = 
             text ("void process"++show i++"(proc_t* p) {")
             $$ nest 4 (vcat (map (cciStatement . snd) cs) 
                        $$ text ""
                        $$ text "finalize(p);"
                       )
             $$ char '}'
             $$ text ""
    wrapProcCmds _ = error "TestGen.wrapProcCmds"

    driver cs =
             text "void drive(proc_t** p) {"
             $$ nest 4 (text "char buf[100];")
             $$ nest 4 (vcat$ map readWrites cs)
             $$ char '}'
             $$ text ""

    readWrites (_,Accept _) = empty
    readWrites (ps,_) = vcat$ text "" : map (\i->text$ "write_msg(p["++show i++"],\"\");") ps 
                              ++ map (\i->text$ "read_msg(p["++show i++"],buf);") ps

    includes = vcat$ map text
        [ "#include <stdio.h>"
        , "#include <string.h>"
        , "#include <strings.h>"
        , "#include <stdlib.h>"
        , "#include <unistd.h>"
        , "#include <inttypes.h>"
        , "#include <assert.h>"
        , "#include <sys/time.h>"
        , "#include <time.h>"
        , "#include \"cci.h\""
        , "#include \"testlib.h\""
        , ""
        ]

    genMain cs =
         let maxProcIndex = maximum$ concatMap fst cs
             ps = sort$ nub$ concatMap fst cs
          in text "int main(int argc, char *argv[]) {"
             $$ (nest 4$ vcat$
                   [ text "proc_t* p[100];"
                   , text "memset(p,0,100*sizeof(proc_t*));"
                   , text ""
                   , hcat (map vcat$ map1 (mapFirst (nest 7))$ map (genSpawn (maxProcIndex+1)) ps)
                       <> vcat 
                          [ nest 7$ text "{"
                          , nest 4$ vcat
                              [ text ("write_uris(p,"++show (maxProcIndex+1)++");")
                              , text "drive(p);"
                              , text "wait();"
                              ]
                          , char '}' 
                          , text "return 0;"
                          ]
                   ]
                )
             $$ char '}'

    genSpawn n i = [ text$ "if (!spawn(&p["++show i++"],"++show i++")) {"
                   , nest 4 (
                       text ("read_uris(p["++show i++"],"++show n++");")
                       $$ text ("process"++show i++"(p["++show i++"]);")
                     )
                   , text "} else "
                   ]

    map1 _ [] = []
    map1 f (x:xs) = x : map f xs

    mapFirst _ [] = []
    mapFirst f (x:xs) = f x : xs

-- Command generation

-- | Takes the amount of processes, and generates commands for an interaction among them.