defmodule Asciinema.Vt.Worker do
  use GenServer

  @vt_script_path "vt/main.js"

  # Client API

  def start_link(_) do
    GenServer.start_link(__MODULE__, nil, [])
  end

  def new(pid, width, height) do
    GenServer.cast(pid, {:new, width, height})
  end

  def feed(pid, data) do
    GenServer.cast(pid, {:feed, data})
  end

  def dump_screen(pid, timeout) do
    GenServer.call(pid, :dump_screen, timeout)
  end

  # Server callbacks

  def init(_) do
    path = System.find_executable("node")
    port = Port.open({:spawn_executable, path}, [:binary, args: [@vt_script_path]])
    {:ok, port}
  end

  def handle_call(:dump_screen, _from, port) do
    send_cmd(port, "dump-screen")

    case read_stout_line(port) do
      {:ok, line} ->
        result = line |> Poison.decode! |> Map.get("result")
        {:reply, {:ok, result}, port}
      {:error, reason} ->
        {:reply, {:error, reason}, port}
    end
  end

  defp read_stout_line(port) do
    read_stout_line(port, "")
  end
  defp read_stout_line(port, line) do
    receive do
      {^port, {:data, data}} ->
        if String.ends_with?(data, "\n") do
          {:ok, line <> String.trim_trailing(data)}
        else
          read_stout_line(port, line <> data)
        end
    end
  end

  def handle_cast({:new, width, height}, port) do
    send_cmd(port, "new", %{width: width, height: height})
    {:noreply, port}
  end
  def handle_cast({:feed, data}, port) do
    send_cmd(port, "feed-str", %{str: data})
    {:noreply, port}
  end

  defp send_cmd(port, cmd, data \\ %{}) do
    json = Map.put(data, :cmd, cmd) |> Poison.encode!
    true = Port.command(port, "#{json}\n")
  end
end
