evalPipe :: [(String, [Value])] -> Handle.Handle -> EvalExprFn -> WithState Value
evalPipe commands stdin evalProgram = do
  -- TODO: when you call a pipe, what do you do with the "output"? Obviously,
  -- you stream it to the parent. And occasionally the parent will be stdout. So
  -- clearly, we need to pass - implicitly - the handle from the calling
  -- function.
  -- However, that breaks our metaphor of "returning" a packet with the streams in it...
  -- TODO: we need to handle stderr too.
  -- TODO support exit codes

  stdout <- RT.getStdout
  stderr <- RT.getStderr

  do
    pipes :: [(Handle.Handle, Handle.Handle)] <- liftIO $ mapM (const Proc.createPipe) commands
    let pipes1 :: [(Handle.Handle, Handle.Handle)] = tail pipes
    let pipes2 :: [Handle.Handle] = foldl (\c (a,b) -> c ++ [a, b]) [] pipes1
    let pipes3 :: [Handle.Handle] = [stdin] ++ pipes2 ++ [stdout]
    let joiner = (\case
                   (a:b:cs) -> [Handles a b stderr] ++ (joiner cs)
                   [_] -> error "shouldn't happen"
                   [] -> [])

    let pipes4 :: [Handles] = joiner pipes3

    let entirity = zip pipes4 commands

    procs <- mapM buildSegment entirity

    -- TODO: if an exit code is fail, stop further processes
    codes <- liftIO $ mapM waitForProcess procs
    let rv = last codes
    return $ VPacket rv


  where
    buildSegment :: (Handles, (String, [Value])) -> WithState (Process)
    buildSegment (handles, (cmd, args)) = do
      ft <- RT.getFuncTable
      let func = Map.lookup cmd ft
      procHandle <- case func of
        Just f -> createFuncThread f args handles evalProgram
        Nothing -> liftIO $ createBackgroundProc cmd args handles
      return procHandle
evalPipe :: [(String, [Value])] -> Handle.Handle -> EvalExprFn -> WithState Value
evalPipe commands stdin evalProgram = do
  -- TODO: when you call a pipe, what do you do with the "output"? Obviously,
  -- you stream it to the parent. And occasionally the parent will be stdout. So
  -- clearly, we need to pass - implicitly - the handle from the calling
  -- function.
  -- However, that breaks our metaphor of "returning" a packet with the streams in it...
  -- TODO: we need to handle stderr too.
  -- TODO support exit codes

  stdout <- RT.getStdout
  stderr <- RT.getStderr

  do
    pipes :: [(Handle.Handle, Handle.Handle)] <- liftIO $ mapM (const Proc.createPipe) commands
    let pipes1 :: [(Handle.Handle, Handle.Handle)] = tail pipes
    let pipes2 :: [Handle.Handle] = foldl (\c (a,b) -> c ++ [a, b]) [] pipes1
    let pipes3 :: [Handle.Handle] = [stdin] ++ pipes2 ++ [stdout]
    let joiner = (\case
                   (a:b:cs) -> [Handles a b stderr] ++ (joiner cs)
                   [_] -> error "shouldn't happen"
                   [] -> [])

    let pipes4 :: [Handles] = joiner pipes3

    let entirity = zip pipes4 commands

    procs <- mapM buildSegment entirity

    -- TODO: if an exit code is fail, stop further processes
    codes <- liftIO $ mapM waitForProcess procs
    let rv = last codes
    return $ VPacket rv


  where
    buildSegment :: (Handles, (String, [Value])) -> WithState (Process)
    buildSegment (handles, (cmd, args)) = do
      ft <- RT.getFuncTable
      let func = Map.lookup cmd ft
      procHandle <- case func of
        Just f -> createFuncThread f args handles evalProgram
        Nothing -> liftIO $ createBackgroundProc cmd args handles
      return procHandle
evalPipe commands stdin evalProgram = do
  -- TODO: when you call a pipe, what do you do with the "output"? Obviously,
  -- you stream it to the parent. And occasionally the parent will be stdout. So
  -- clearly, we need to pass - implicitly - the handle from the calling
  -- function.
  -- However, that breaks our metaphor of "returning" a packet with the streams in it...
  -- TODO: we need to handle stderr too.
  -- TODO support exit codes

  stdout <- RT.getStdout
  stderr <- RT.getStderr

  do
    pipes :: [(Handle.Handle, Handle.Handle)] <- liftIO $ mapM (const Proc.createPipe) commands
    let pipes1 :: [(Handle.Handle, Handle.Handle)] = tail pipes
    let pipes2 :: [Handle.Handle] = foldl (\c (a,b) -> c ++ [a, b]) [] pipes1
    let pipes3 :: [Handle.Handle] = [stdin] ++ pipes2 ++ [stdout]
    let joiner = (\case
                   (a:b:cs) -> [Handles a b stderr] ++ (joiner cs)
                   [_] -> error "shouldn't happen"
                   [] -> [])

    let pipes4 :: [Handles] = joiner pipes3

    let entirity = zip pipes4 commands

    procs <- mapM buildSegment entirity

    -- TODO: if an exit code is fail, stop further processes
    codes <- liftIO $ mapM waitForProcess procs
    let rv = last codes
    return $ VPacket rv


  where
    buildSegment :: (Handles, (String, [Value])) -> WithState (Process)
    buildSegment (handles, (cmd, args)) = do
      ft <- RT.getFuncTable
      let func = Map.lookup cmd ft
      procHandle <- case func of
        Just f -> createFuncThread f args handles evalProgram
        Nothing -> liftIO $ createBackgroundProc cmd args handles
      return procHandle