                      #out: :err | :buffer | pid | {:file, ...},
                      #err: :out | :buffer | pid | {:file, ...})
 
-#p = Porc.call("cat", in: "Hello world!")
+#Porc.call("cat", in: "Hello world!")
 # ==>
 #p = Port.open({:spawn_executable, '/usr/local/bin/go'}, [{:args, ["run", "main.go", "cat"]}, :binary, {:packet, 2}, :exit_status])
 

    end
  end

  defp split(args) when is_binary(args) do
    String.split args, " "
  end

  defp open_port(opts) do
    go = :os.find_executable 'go'
    Port.open { :spawn_executable, go }, opts
  end

  defp init_port_connection(cmd, args, options) do
    port = open_port(port_options(options, cmd, args))

    input  = process_input_opts(port, options[:in])
    output = process_output_opts(port, options[:out])
    error  = process_error_opts(port, options[:err])

    { port, input, output, error }
  end

  @doc """

    communicate(port, input, output, error)
  end

  @file_block_size 1024

  # Synchronous communication with a port
  defp communicate(port, input, output, error) do
    case input do
      bin when is_binary(bin) and byte_size(bin) > 0 ->
        #IO.puts "sending input #{bin}"
        Port.command(port, input)

      {:file, fid} ->
        Stream.repeatedly(fn -> IO.read(fid, @file_block_size) end)
        |> Enum.take_while(fn  # FIXME: needs to be Stream.take_while
          :eof -> false
          {:error, _} -> false
          _ -> true
        end)
        |> Enum.each(Port.command(port, &1))

      _ -> nil
    end
    # Send EOF to indicate the end of input or no input
    Port.command(port, "")

    collect_output(port, output, error, 0)
  end

  # Runs in a recursive loop until the process exits
  defp collect_output(port, output, error, status) do
    #IO.puts "Collecting output"
    receive do
      { ^port, {:data, <<?o, data :: binary>>} } ->
        #IO.puts "Did receive out"
        output = process_port_output(output, data, :stdout)
        collect_output(port, output, error, status)

      { ^port, {:data, <<?e, data :: binary>>} } ->
        #IO.puts "Did receive err"
        error = process_port_output(error, data, :stderr)
        collect_output(port, output, error, status)

      { ^port, {:exit_status, status} } ->
        { status, flatten(output), flatten(error) }

      #{ ^port, :eof } ->
        #collect_output(port, output, out_data, err_data, true, did_see_exit, status)
    end
  end

  defp process_port_output({ :buffer, out_data }, in_data, _) do
    {:buffer, [out_data, in_data]}
  end

  defp process_port_output({ :file, fid }=a, in_data, _) do
    :ok = IO.write fid, in_data
    a
  end

  defp process_port_output({ pid, ref }=a, in_data, type) when is_pid(pid) do
    pid <- { ref, type, in_data }
    a
  end

  # Takes the output which is a nested list of binaries and produces a single
  # binary from it
  defp flatten({:buffer, iolist}) do
    #IO.puts "Flattening an io list #{inspect iolist}"
    {:ok, bin} = String.from_char_list iolist
    bin
  end

  defp flatten(other) do
    #IO.puts "Flattening #{inspect other}"
    other
  end

  @doc """
