<|start_filename|>examples/responders/great_success.ex<|end_filename|>
defmodule Hedwig.Responders.GreatSuccess do
  @moduledoc false

  use Hedwig.Responder

  @links [
    "http://mjanja.co.ke/wordpress/wp-content/uploads/2013/09/borat_great_success.jpg",
    "http://s2.quickmeme.com/img/13/1324dfd733535e58dba70264e6d05c9b70346204d2cacef65abef9c702746d1c.jpg",
    "https://www.youtube.com/watch?v=r13riaRKGo0"
  ]

  @usage """
  great success - Displays a random Borat image.
  """
  hear ~r/great success(!)?/i, msg do
    reply msg, random(@links)
  end
end


<|start_filename|>lib/hedwig/adapters/console/reader.ex<|end_filename|>
defmodule Hedwig.Adapters.Console.Reader do
  @moduledoc false

  use GenServer

  def start_link(user) do
    GenServer.start_link(__MODULE__, {self(), user})
  end

  def init({owner, user}) do
    GenServer.cast(self(), :get_io)
    {:ok, {owner, user}}
  end

  def handle_cast(:get_io, {owner, user}) do
    me = self()
    Task.start fn -> send(me, get_io(user)) end
    {:noreply, {owner, user}}
  end

  def handle_info(:eof, state) do
    {:stop, :normal, state}
  end

  def handle_info({:error, :terminated}, state) do
    {:stop, :normal, state}
  end

  def handle_info({ref, _msg}, state) when is_reference(ref) do
    {:noreply, state}
  end

  def handle_info(text, {owner, user}) when is_binary(text) do
    Kernel.send(owner, {:message, String.trim(text)})
    Process.sleep(200)
    GenServer.cast(self(), :get_io)

    {:noreply, {owner, user}}
  end

  defp prompt(name) do
    [:white, :bright, name, "> ", :normal, :default_color]
  end

  defp get_io(name) do
    name
    |> prompt()
    |> IO.ANSI.format()
    |> IO.gets()
  end
end


<|start_filename|>lib/hedwig/adapters/test.ex<|end_filename|>
defmodule Hedwig.Adapters.Test do
  @moduledoc false

  use Hedwig.Adapter

  def init({robot, opts}) do
    GenServer.cast(self(), :after_init)
    {:ok, %{conn: nil, opts: opts, robot: robot}}
  end

  def handle_cast(:after_init, %{robot: robot} = state) do
    Hedwig.Robot.handle_connect(robot)
    {:noreply, state}
  end

  def handle_cast({:send, msg}, %{conn: conn} = state) do
    Kernel.send(conn, {:message, msg})
    {:noreply, state}
  end

  def handle_cast({:reply, %{text: text, user: user} = msg}, %{conn: conn} = state) do
    Kernel.send(conn, {:message, %{msg | text: "#{user}: #{text}"}})
    {:noreply, state}
  end

  def handle_cast({:emote, %{text: text} = msg}, %{conn: conn} = state) do
    Kernel.send(conn, {:message, %{msg | text: "* #{text}"}})
    {:noreply, state}
  end

  def handle_info({:message, msg}, %{robot: robot} = state) do
    msg = %Hedwig.Message{robot: robot, text: msg.text, user: msg.user}
    Hedwig.Robot.handle_in(robot, msg)
    {:noreply, state}
  end

  def handle_info(msg, %{robot: robot} = state) do
    Hedwig.Robot.handle_in(robot, msg)
    {:noreply, state}
  end
end


<|start_filename|>lib/hedwig/responder/supervisor.ex<|end_filename|>
defmodule Hedwig.Responder.Supervisor do
  @moduledoc false

  def start_link do
    import Supervisor.Spec, warn: false

    children = [
      worker(Hedwig.Responder, [], restart: :transient)
    ]

    Supervisor.start_link(children, strategy: :simple_one_for_one)
  end
end


<|start_filename|>lib/hedwig/robot.ex<|end_filename|>
defmodule Hedwig.Robot do
  @moduledoc """
  Defines a robot.

  Robots receive messages from a chat source (XMPP, Slack, Console, etc), and
  dispatch them to matching responders. See the documentation for
  `Hedwig.Responder` for details on responders.

  When used, the robot expects the `:otp_app` as option. The `:otp_app` should
  point to an OTP application that has the robot configuration. For example,
  the robot:

      defmodule MyApp.Robot do
        use Hedwig.Robot, otp_app: :my_app
      end

  Could be configured with:

      config :my_app, MyApp.Robot,
        adapter: Hedwig.Adapters.Console,
        name: "alfred"

  Most of the configuration that goes into the `config` is specific to the
  adapter.  Be sure to check the documentation for the adapter in use for all
  of the available options.

  ## Robot configuration

  * `adapter` - the adapter module name.
  * `name` - the name the robot will respond to.
  * `aka` - an alias the robot will respond to.
  * `log_level` - the level to use when logging output.
  * `responders` - a list of responders specified in the following format:
    `{module, kwlist}`.
  """

  defstruct adapter: nil,
            aka: nil,
            name: "",
            opts: [],
            responder_sup: nil,
            responders: []

  defmacro __using__(opts) do
    quote location: :keep, bind_quoted: [opts: opts] do
      use GenServer
      require Logger

      {otp_app, adapter, robot_config} =
        Hedwig.Robot.Supervisor.parse_config(__MODULE__, opts)

      @adapter adapter
      @before_compile adapter
      @config  robot_config
      @log_level robot_config[:log_level] || :debug
      @otp_app otp_app

      def start_link(opts \\ []) do
        Hedwig.start_robot(__MODULE__, opts)
      end

      def stop(robot) do
        Hedwig.stop_robot(robot)
      end

      def config(opts \\ []) do
        Hedwig.Robot.Supervisor.config(__MODULE__, @otp_app, opts)
      end

      def log(msg) do
        Logger.unquote(@log_level)(fn ->
          msg
        end, [])
      end

      def __adapter__, do: @adapter

      def init({robot, opts}) do
        opts = robot.config(opts)
        aka = Keyword.get(opts, :aka)
        name = Keyword.get(opts, :name)
        {responders, opts} = Keyword.pop(opts, :responders, [])

        unless responders == [] do
          GenServer.cast(self(), {:install_responders, responders})
        end

        {:ok, adapter} = @adapter.start_link(robot, opts)
        {:ok, responder_sup} = Hedwig.Responder.Supervisor.start_link()

        {:ok, %Hedwig.Robot{
          adapter: adapter,
          aka: aka,
          name: name,
          opts: opts,
          responder_sup: responder_sup,
          responders: responders}}
      end

      def handle_connect(state) do
        {:ok, state}
      end

      def handle_disconnect(_reason, state) do
        {:reconnect, state}
      end

      def handle_in(%Hedwig.Message{} = msg, state) do
        {:dispatch, msg, state}
      end
      def handle_in(_msg, state) do
        {:noreply, state}
      end

      def handle_call(:name, _from, %{name: name} = state) do
        {:reply, name, state}
      end

      def handle_call(:responders, _from, %{responders: responders} = state) do
        {:reply, responders, state}
      end

      def handle_call(:handle_connect, _from, state) do
        case handle_connect(state) do
          {:ok, state} ->
            {:reply, :ok, state}
          {:stop, reason, state} ->
            {:stop, reason, state}
        end
      end

      def handle_call({:handle_disconnect, reason}, _from, state) do
        case handle_disconnect(reason, state) do
          {:reconnect, state} ->
            {:reply, :reconnect, state}
          {:reconnect, timer, state} ->
            {:reply, {:reconnect, timer}, state}
          {:disconnect, reason, state} ->
            {:stop, reason, {:disconnect, reason}, state}
        end
      end

      def handle_cast({:send, msg}, %{adapter: adapter} = state) do
        @adapter.send(adapter, msg)
        {:noreply, state}
      end

      def handle_cast({:reply, msg}, %{adapter: adapter} = state) do
        @adapter.reply(adapter, msg)
        {:noreply, state}
      end

      def handle_cast({:emote, msg}, %{adapter: adapter} = state) do
        @adapter.emote(adapter, msg)
        {:noreply, state}
      end

      def handle_cast({:handle_in, msg}, %{responder_sup: sup} = state) do
        case handle_in(msg, state) do
          {:dispatch, %Hedwig.Message{} = msg, state} ->
            responders = Supervisor.which_children(sup)
            Hedwig.Responder.dispatch(msg, responders)
            {:noreply, state}

          {:dispatch, _msg, state} ->
            log_incorrect_return(:dispatch)
            {:noreply, state}

          {fun, {%Hedwig.Message{} = msg, text}, state} when fun in [:send, :reply, :emote] ->
            apply(Hedwig.Responder, fun, [msg, text])
            {:noreply, state}

          {fun, {_msg, _text}, state} when fun in [:send, :reply, :emote] ->
            log_incorrect_return(fun)
            {:noreply, state}

          {:noreply, state} ->
            {:noreply, state}
        end
      end

      def handle_cast({:install_responders, responders}, %{aka: aka, name: name} = state) do
        for {module, opts} <- responders do
          args = [module, {aka, name, opts, self()}]
          Supervisor.start_child(state.responder_sup, args)
        end
        {:noreply, state}
      end

      def handle_info(msg, state) do
        {:noreply, state}
      end

      def terminate(_reason, _state) do
        :ok
      end

      def code_change(_old, state, _extra) do
        {:ok, state}
      end

      defp log_incorrect_return(atom) do
        Logger.warn """
        #{inspect atom} return value from `handle_in/2` only works with `%Hedwig.Message{}` structs.
        """
      end

      defoverridable [
        {:handle_connect, 1},
        {:handle_disconnect, 2},
        {:handle_in, 2},
        {:terminate, 2},
        {:code_change, 3},
        {:handle_info, 2}
      ]
    end
  end

  @doc false
  def start_link(robot, opts) do
    GenServer.start_link(robot, {robot, opts})
  end

  @doc """
  Send a message via the robot.
  """
  def send(pid, msg) do
    GenServer.cast(pid, {:send, msg})
  end

  @doc """
  Send a reply message via the robot.
  """
  def reply(pid, msg) do
    GenServer.cast(pid, {:reply, msg})
  end

  @doc """
  Send an emote message via the robot.
  """
  def emote(pid, msg) do
    GenServer.cast(pid, {:emote, msg})
  end

  @doc """
  Get the name of the robot.
  """
  def name(pid) do
    GenServer.call(pid, :name)
  end

  @doc """
  Get the list of the robot's responders.
  """
  def responders(pid) do
    GenServer.call(pid, :responders)
  end

  @doc """
  Invokes a user defined `handle_in/2` function, if defined.

  This function should be called by an adapter when a message arrives but
  should be handled by the user module.

  Returning `{:dispatch, msg, state}` will dispatch the message
  to all installed responders.

  Returning `{:send, {msg, text}, state}`, `{:reply, {msg, text}, state}`,
  or `{:emote, {msg, text}, state}` will send the message directly to the
  adapter without dispatching to any responders.

  Returning `{:noreply, state}` will ignore the message.
  """
  @spec handle_in(pid, any) :: :ok
  def handle_in(robot, msg) do
    GenServer.cast(robot, {:handle_in, msg})
  end

  @doc """
  Invokes a user defined `handle_connect/1` function, if defined.

  If the user has defined an `handle_connect/1` in the robot module, it will be
  called with the robot's state. It is expected that the function return
  `{:ok, state}` or `{:stop, reason, state}`.
  """
  @spec handle_connect(pid, integer) :: :ok
  def handle_connect(robot, timeout \\ 5000) do
    GenServer.call(robot, :handle_connect, timeout)
  end

  @doc """
  Invokes a user defined `handle_disconnect/1` function, if defined.

  If the user has defined an `handle_disconnect/1` in the robot module, it will be
  called with the robot's state. It is expected that the function return
  `{:reconnect, state}` `{:reconnect, integer, state}`, or `{:disconnect, reason, state}`.
  """
  @spec handle_disconnect(pid, any, integer) :: :reconnect | {:reconnect, integer} | {:disconnect, any}
  def handle_disconnect(robot, reason, timeout \\ 5000) do
    GenServer.call(robot, {:handle_disconnect, reason}, timeout)
  end
end


<|start_filename|>lib/hedwig/message.ex<|end_filename|>
defmodule Hedwig.Message do
  @moduledoc """
  Hedwig Message
  """

  @type matches :: list | map
  @type private :: map
  @type ref     :: reference
  @type robot   :: pid
  @type room    :: binary
  @type text    :: binary
  @type type    :: binary
  @type user    :: Hedwig.User.t

  @type t :: %__MODULE__{
    matches: matches,
    private: private,
    ref:     ref,
    robot:   robot,
    room:    room,
    text:    text,
    type:    type,
    user:    user
  }

  defstruct matches: nil,
            private: %{},
            ref:     nil,
            robot:   nil,
            room:    nil,
            text:    nil,
            type:    nil,
            user:    %Hedwig.User{}
end


<|start_filename|>test/hedwig/adapters/console_test.exs<|end_filename|>
defmodule Hedwig.Adapters.ConsoleTest do
  use ExUnit.Case

  import ExUnit.CaptureIO
  alias Hedwig.Adapters.Console

  test "console handles messages from the connection" do
    capture_io fn ->
      {:ok, adapter} = Hedwig.Adapter.start_link(Console, name: "hedwig", user: "testuser")

      handle_connect()
      # Simulate an incoming message from the connection process
      msg = {:message, %{"text" => "ping", "user" => "testuser"}}
      send(adapter, msg)
      assert_receive {:"$gen_cast", {:handle_in, %Hedwig.Message{text: "ping", user: "testuser"}}}
    end
  end

  describe "sending messages to the connection process" do
    test "send/2" do
      capture_io fn ->
        {:ok, adapter} = Hedwig.Adapter.start_link(Console, name: "hedwig", user: "testuser")

        handle_connect()
        # replace the adapter's connection pid to the test process
        replace_connection_pid(adapter)

        msg = %Hedwig.Message{text: "pong", user: "testuser"}
        Console.send(adapter, msg)

        assert_receive {:reply, ^msg}
      end
    end

    test "reply/2 includes the reply user's name" do
      capture_io fn ->
        {:ok, adapter} = Hedwig.Adapter.start_link(Console, name: "hedwig", user: "testuser")

        handle_connect()
        # replace the adapter's connection pid to the test process
        replace_connection_pid(adapter)

        msg = %Hedwig.Message{text: "pong", user: "testuser"}
        Console.reply(adapter, msg)

        assert_receive {:reply, %Hedwig.Message{text: "testuser: pong"}}
      end
    end

    test "emote/2" do
      capture_io fn ->
        {:ok, adapter} = Hedwig.Adapter.start_link(Console, name: "hedwig", user: "testuser")

        handle_connect()
        # replace the adapter's connection pid to the test process
        replace_connection_pid(adapter)

        msg = %Hedwig.Message{text: "pong", user: "testuser"}
        Console.emote(adapter, msg)

        assert_receive {:reply, ^msg}
      end
    end
  end

  defp handle_connect do
    receive do
      {:"$gen_call", from, :handle_connect} ->
        GenServer.reply(from, :ok)
    end
  end

  defp replace_connection_pid(adapter) do
    test_process = self()
    :sys.replace_state(adapter, fn state -> %{state | conn: test_process} end)
  end
end


<|start_filename|>test/hedwig/responders/help_test.exs<|end_filename|>
defmodule Hedwig.Responders.HelpTest do
  use Hedwig.RobotCase

  @tag start_robot: true, name: "alfred"
  test "help - displays the usage for all installed responders", %{adapter: adapter, msg: msg} do
    send adapter, {:message, %{msg | text: "<NAME>"}}
    assert_receive {:message, %{text: text}}
    assert String.contains?(text, "Displays all help commands that match <query>")
  end

  @tag start_robot: true, name: "alfred"
  test "help <query> - displays the usage for responders that match query", %{adapter: adapter, msg: msg} do
    send adapter, {:message, %{msg | text: "<NAME>"}}
    assert_receive {:message, %{text: text}}
    assert text == "(this is a test) - did someone say test?"
  end
end


<|start_filename|>test/hedwig/responder_test.exs<|end_filename|>
defmodule Hedwig.ResponderTest do
  use Hedwig.RobotCase

  alias Hedwig.Responder

  test "respond_pattern" do
    assert Responder.respond_pattern(~r/hey there/i, "alfred", nil) ==
      ~r/^\s*[@]?alfred[:,]?\s*(?:hey there)/i

    assert Responder.respond_pattern(~r/this\s*should\s*escape/i, "alfred", nil) ==
      ~r/^\s*[@]?alfred[:,]?\s*(?:this\s*should\s*escape)/i

    assert Responder.respond_pattern(~r/this\s*should\s*escape/i, "alfred", "/") ==
      ~r/^\s*[@]?(?:alfred[:,]?|\/[:,]?)\s*(?:this\s*should\s*escape)/i
  end

  @tag start_robot: true, name: "alfred"
  test "responding to messages", %{adapter: adapter, msg: msg} do
    send adapter, {:message, %{msg | text: "this is a test"}}
    assert_receive {:message, %{text: "did someone say test?"}}

    send adapter, {:message, %{msg | text: "alfred do you hear me?"}}
    assert_receive {:message, %{text: "testuser: loud and clear!"}}

    send adapter, {:message, %{msg | text: "i love cats"}}
    assert_receive {:message, %{text: "testuser: then why don't you marry cats!?"}}

    send adapter, {:message, %{msg | text: "i like pie"}}
    assert_receive {:message, %{text: "* likes pie too!"}}

    send adapter, {:message, %{msg | text: "randomness"}}
    assert_receive {:message, %{text: text}}
    assert text in 1..1000
  end
end


<|start_filename|>test/hedwig/robot_test.exs<|end_filename|>
defmodule Hedwig.RobotTest do
  use ExUnit.Case
  use Hedwig.RobotCase

  @tag start_robot: true, name: "alfred"
  test "name/1 returns the name of the robot", %{robot: robot} do
    assert "alfred" = Hedwig.Robot.name(robot)
  end

  @tag start_robot: true, responders: [{TestResponder, []}]
  test "responders/1 returns the list of configured responders", %{robot: robot} do
    assert [{TestResponder, []}] = Hedwig.Robot.responders(robot)
  end

  @tag start_robot: true
  test "handle_connect/1", %{robot: robot} do
    assert ^robot = :global.whereis_name("hedwig")
  end

  @tag start_robot: true
  test "handle_disconnect/1", %{robot: robot} do
    import Hedwig.Robot
    assert :reconnect == handle_disconnect(robot, :reconnect)
    assert {:reconnect, 5000} == handle_disconnect(robot, {:reconnect, 5000})
    assert {:disconnect, :normal} == handle_disconnect(robot, :error)
  end
end


<|start_filename|>lib/hedwig/adapters/console/writer.ex<|end_filename|>
defmodule Hedwig.Adapters.Console.Writer do
  @moduledoc false
  use GenServer

  def start_link(name) do
    GenServer.start_link(__MODULE__, {self(), name})
  end

  def puts(pid, msg) do
    GenServer.cast(pid, {:puts, msg})
  end

  def clear(pid) do
    GenServer.cast(pid, :clear)
  end

  def init({owner, name}) do
    GenServer.cast(self(), :after_init)
    {:ok, {owner, name}}
  end

  def handle_cast(:after_init, state) do
    clear_screen()
    display_banner()
    {:noreply, state}
  end

  def handle_cast(:clear, {owner, name}) do
    clear_screen()
    {:noreply, {owner, name}}
  end

  def handle_cast({:puts, msg}, {owner, name}) do
    handle_result(msg, name)
    {:noreply, {owner, name}}
  end

  defp print(message) do
    message
    |> IO.ANSI.format()
    |> IO.puts()
  end

  defp handle_result(msg, name) do
    print prompt(name) ++ [:normal, :default_color, msg.text]
  end

  defp prompt(name) do
    [:yellow, name, "> ", :default_color]
  end

  defp clear_screen do
    print [:clear, :home]
  end

  defp display_banner do
    print """
    Hedwig Console - press Ctrl+C to exit.

    The console adapter is useful for quickly verifying how your
    bot will respond based on the current installed responders

    """
  end
end


<|start_filename|>lib/hedwig/adapters/console/connection.ex<|end_filename|>
defmodule Hedwig.Adapters.Console.Connection do
  @moduledoc false
  use GenServer

  alias Hedwig.Adapters.Console.{Connection, Reader, Writer}

  defstruct name: nil, owner: nil, reader: nil, user: nil, writer: nil

  def start(opts) do
    name = Keyword.get(opts, :name)
    user = Keyword.get(opts, :user, get_system_user())

    GenServer.start(__MODULE__, {self(), name, user})
  end

  def init({owner, name, user}) do
    GenServer.cast(self(), :after_init)
    {:ok, %Connection{name: name, owner: owner, user: user}}
  end

  def handle_cast(:after_init, %{name: name, user: user} = state) do
    {:ok, writer} = Writer.start_link(name)
    {:ok, reader} = Reader.start_link(user)
    {:noreply, %{state | reader: reader, writer: writer}}
  end

  def handle_info({:reply, text}, %{writer: writer} = state) do
    Writer.puts(writer, text)
    {:noreply, state}
  end

  @doc false
  def handle_info({:message, "clear"}, %{writer: writer} = state) do
    Writer.clear(writer)
    {:noreply, state}
  end

  def handle_info({:message, text}, %{owner: owner, user: user} = state) do
    Kernel.send(owner, {:message, %{"text" => text, "user" => user}})
    {:noreply, state}
  end

  defp get_system_user do
    "whoami"
    |> System.cmd([])
    |> elem(0)
    |> String.trim()
  end
end


<|start_filename|>examples/responders/ship_it.ex<|end_filename|>
defmodule Hedwig.Responders.ShipIt do
  @moduledoc false

  use Hedwig.Responder

  @squirrels [
    "http://img.skitch.com/20100714-d6q52xajfh4cimxr3888yb77ru.jpg",
    "https://img.skitch.com/20111026-r2wsngtu4jftwxmsytdke6arwd.png",
    "http://cl.ly/1i0s1r3t2s2G3P1N3t3M/Screen_Shot_2011-10-27_at_9.36.45_AM.png",
    "http://shipitsquirrel.github.com/images/squirrel.png"
  ]

  @usage """
  ship it - Display a motivation squirrel
  """
  hear ~r/ship it/i, msg do
    send msg, random(@squirrels)
  end
end


<|start_filename|>lib/hedwig/adapters/console.ex<|end_filename|>
defmodule Hedwig.Adapters.Console do
  @moduledoc """
  Hedwig Console Adapter

  The console adapter is useful for testing out responders without a remote
  chat service.

      config :my_app, MyApp.Robot,
        adapter: Hedwig.Adapters.Console,
        ...

  Start your application with `mix run --no-halt` and you will have a console
  interface to your bot.
  """
  use Hedwig.Adapter
  alias Hedwig.Adapters.Console.Connection

  @doc false
  def init({robot, opts}) do
    {:ok, conn} = Connection.start(opts)
    Kernel.send(self(), :connected)
    {:ok, %{conn: conn, opts: opts, robot: robot}}
  end

  @doc false
  def handle_cast({:send, msg}, %{conn: conn} = state) do
    Kernel.send(conn, {:reply, msg})
    {:noreply, state}
  end

  @doc false
  def handle_cast({:reply, %{user: user, text: text} = msg}, %{conn: conn} = state) do
    Kernel.send(conn, {:reply, %{msg | text: "#{user}: #{text}"}})
    {:noreply, state}
  end

  @doc false
  def handle_cast({:emote, msg}, %{conn: conn} = state) do
    Kernel.send(conn, {:reply, msg})
    {:noreply, state}
  end

  @doc false
  def handle_info({:message, %{"text" => text, "user" => user}}, %{robot: robot} = state) do
    msg = %Hedwig.Message{
      ref: make_ref(),
      robot: robot,
      text: text,
      type: "chat",
      user: user
    }

    Hedwig.Robot.handle_in(robot, msg)

    {:noreply, state}
  end

  def handle_info(:connected, %{robot: robot} = state) do
    :ok = Hedwig.Robot.handle_connect(robot)
    {:noreply, state}
  end
end


<|start_filename|>test/hedwig/responders/ping_test.exs<|end_filename|>
defmodule Hedwig.Responders.PingTest do
  use Hedwig.RobotCase

  @tag start_robot: true, name: "alfred", responders: [{Hedwig.Responders.Ping, []}]
  test "ping responds with pong", %{adapter: adapter, msg: msg} do
    send adapter, {:message, %{msg | text: "alfred ping"}}
    assert_receive {:message, %{text: "testuser: pong"}}
  end
end


<|start_filename|>lib/hedwig/test/test_robot.ex<|end_filename|>
Code.ensure_compiled(Hedwig.Adapters.Test)

defmodule Hedwig.TestRobot do
  use Hedwig.Robot, otp_app: :hedwig, adapter: Hedwig.Adapters.Test

  @moduledoc false

  def handle_connect(%{name: name} = state) do
    if :undefined == :global.whereis_name(name) do
      :yes = :global.register_name(name, self())
    end

    {:ok, state}
  end

  def handle_disconnect(:error, state),
    do: {:disconnect, :normal, state}
  def handle_disconnect(:reconnect, state),
    do: {:reconnect, state}
  def handle_disconnect({:reconnect, timer}, state),
    do: {:reconnect, timer, state}

  def handle_in(%Hedwig.Message{} = msg, state) do
    {:dispatch, msg, state}
  end

  def handle_in({:ping, from}, state) do
    Kernel.send(from, :pong)
    {:noreply, state}
  end

  def handle_in(msg, state) do
    super(msg, state)
  end
end


<|start_filename|>lib/hedwig/responders/ping.ex<|end_filename|>
defmodule Hedwig.Responders.Ping do
  @moduledoc """
  Responds to 'ping' with 'pong'
  """

  use Hedwig.Responder

  @usage """
  hedwig: ping - Responds with 'pong'
  """
  respond ~r/ping$/i, msg do
    reply msg, "pong"
  end
end


<|start_filename|>lib/hedwig/robot/supervisor.ex<|end_filename|>
defmodule Hedwig.Robot.Supervisor do
  @moduledoc false

  use Supervisor

  def start_link(opts \\ []) do
    Supervisor.start_link(__MODULE__, [], opts)
  end

  def config(robot, otp_app, opts) do
    if robot_config = Application.get_env(otp_app, robot) do
      robot_config
      |> Keyword.put(:otp_app, otp_app)
      |> Keyword.put(:robot, robot)
      |> Keyword.merge(opts)
    else
      raise ArgumentError,
        "configuration for #{inspect robot} not specified in #{inspect otp_app} environment"
    end
  end

  def parse_config(robot, opts) do
    otp_app = Keyword.fetch!(opts, :otp_app)
    robot_config  = Application.get_env(otp_app, robot, [])
    adapter = opts[:adapter] || robot_config[:adapter]

    unless adapter do
      raise ArgumentError, "missing `:adapter` configuration for " <>
                           "#{inspect otp_app}, #{inspect robot}"
    end

    unless Code.ensure_loaded?(adapter) do
      raise ArgumentError, "adapter #{inspect adapter} was not compiled, " <>
                           "ensure it is correct and it is included as a " <>
                           "project dependency."
    end

    {otp_app, adapter, robot_config}
  end

  def init(_) do
    children = [
      worker(Hedwig.Robot, [], restart: :transient)
    ]

    supervise(children, strategy: :simple_one_for_one)
  end
end


<|start_filename|>lib/hedwig/responder.ex<|end_filename|>
defmodule Hedwig.Responder do
  @moduledoc ~S"""
  Base module for building responders.

  A responder is a module which setups up handlers for hearing and responding
  to incoming messages.

  ## Hearing & Responding

  Hedwig can hear messages said in a room or respond to messages directly
  addressed to it. Both methods take a regular expression, the message and a block
  to execute when there is a match. For example:

      hear ~r/(hi|hello)/i, msg do
        # your code here
      end

      respond ~r/help$/i, msg do
        # your code here
      end

  ## Using captures

  Responders support regular expression captures. It supports both normal
  captures and named captures. When a message matches, captures are handled
  automatically and added to the message's `:matches` key.

  Accessing the captures depends on the type of capture used in the responder's
  regex. If named captures are used, captures will be available by the name,
  otherwise it will be available by an index, starting with 0.


  ### Example:

      # with indexed captures
      hear ~r/i like (\w+), msg do
        emote msg, "likes #{msg.matches[1]} too!"
      end

      # with named captures
      hear ~r/i like (?<subject>\w+), msg do
        emote msg, "likes #{msg.matches["subject"]} too!"
      end
  """

  defmacro __using__(_opts) do
    quote location: :keep do
      import unquote(__MODULE__)
      import Kernel, except: [send: 2]

      Module.register_attribute __MODULE__, :hear, accumulate: true
      Module.register_attribute __MODULE__, :respond, accumulate: true
      Module.register_attribute __MODULE__, :usage, accumulate: true

      @before_compile unquote(__MODULE__)
    end
  end

  def start_link(module, {aka, name, opts, robot}) do
    GenServer.start_link(module, {aka, name, opts, robot})
  end

  @doc """
  Sends a message via the underlying adapter.

  ## Example

      send msg, "Hello there!"
  """
  def send(%Hedwig.Message{robot: robot} = msg, text) do
    Hedwig.Robot.send(robot, %{msg | text: text})
  end

  @doc """
  Send a reply message via the underlying adapter.

  ## Example

      reply msg, "Hello there!"
  """
  def reply(%Hedwig.Message{robot: robot} = msg, text) do
    Hedwig.Robot.reply(robot, %{msg | text: text})
  end

  @doc """
  Send an emote message via the underlying adapter.

  ## Example

      emote msg, "goes and hides"
  """
  def emote(%Hedwig.Message{robot: robot} = msg, text) do
    Hedwig.Robot.emote(robot, %{msg | text: text})
  end

  @doc """
  Returns a random item from a list or range.

  ## Example

      send msg, random(["apples", "bananas", "carrots"])
  """
  def random(list) do
    :rand.seed(:exsplus, :os.timestamp)
    Enum.random(list)
  end

  @doc false
  def dispatch(msg, responders) do
    Enum.map(responders, fn {_, pid, _, _} ->
      GenServer.cast(pid, {:dispatch, msg})
    end)
  end

  @doc """
  Matches messages based on the regular expression.

  ## Example

      hear ~r/hello/, msg do
        # code to handle the message
      end
  """
  defmacro hear(regex, msg, state \\ Macro.escape(%{}), do: block) do
    name = unique_name(:hear)
    quote do
      @hear {unquote(regex), unquote(name)}
      @doc false
      def unquote(name)(unquote(msg), unquote(state)) do
        unquote(block)
      end
    end
  end

  @doc """
  Setups up an responder that will match when a message is prefixed with the bot's name.

  ## Example

      # Give our bot's name is "alfred", this responder
      # would match for a message with the following text:
      # "alfred hello"
      respond ~r/hello/, msg do
        # code to handle the message
      end
  """
  defmacro respond(regex, msg, state \\ Macro.escape(%{}), do: block) do
    name = unique_name(:respond)
    quote do
      @respond {unquote(regex), unquote(name)}
      @doc false
      def unquote(name)(unquote(msg), unquote(state)) do
        unquote(block)
      end
    end
  end

  defp unique_name(type) do
    String.to_atom("#{type}_#{System.unique_integer([:positive, :monotonic])}")
  end

  @doc false
  def respond_pattern(pattern, name, aka) do
    pattern
    |> Regex.source
    |> rewrite_source(name, aka)
    |> Regex.compile!(Regex.opts(pattern))
  end

  defp rewrite_source(source, name, nil) do
    "^\\s*[@]?#{name}[:,]?\\s*(?:#{source})"
  end
  defp rewrite_source(source, name, aka) do
    [a, b] = if String.length(name) > String.length(aka), do: [name, aka], else: [aka, name]
    "^\\s*[@]?(?:#{a}[:,]?|#{b}[:,]?)\\s*(?:#{source})"
  end

  defmacro __before_compile__(_env) do
    quote location: :keep do
      @doc false
      def usage(name) do
        import String
        Enum.map(@usage, &(&1 |> trim |> replace("hedwig", name)))
      end

      def init({aka, name, opts, robot}) do
        :ok = GenServer.cast(self(), :compile_responders)

        {:ok, %{
          aka: aka,
          name: name,
          opts: opts,
          responders: [],
          robot: robot}}
      end

      def handle_cast(:compile_responders, %{aka: aka, name: name} = state) do
        {:noreply, %{state | responders: compile_responders(name, aka)}}
      end

      def handle_cast({:dispatch, msg}, state) do
        {:noreply, dispatch_responders(msg, state)}
      end

      defp dispatch_responders(msg, %{responders: responders} = state) do
        Enum.reduce responders, state, fn responder, new_state ->
          case dispatch_responder(responder, msg, new_state) do
            :ok ->
              new_state
            {:ok, new_state} ->
              new_state
          end
        end
      end

      defp dispatch_responder({regex, fun}, %{text: text} = msg, state) do
        if Regex.match?(regex, text) do
          msg = %{msg | matches: find_matches(regex, text)}
          apply(__MODULE__, fun, [msg, state])
        else
          :ok
        end
      end

      defp find_matches(regex, text) do
        case Regex.names(regex) do
          []  ->
            matches = Regex.run(regex, text)
            Enum.reduce(Enum.with_index(matches), %{}, fn {match, index}, acc ->
              Map.put(acc, index, match)
            end)
          _ ->
            Regex.named_captures(regex, text)
        end
      end

      defp compile_responders(name, aka) do
        responders = for {regex, fun} <- @respond do
          regex = Hedwig.Responder.respond_pattern(regex, name, aka)
          {regex, fun}
        end

        List.flatten([@hear, responders])
      end
    end
  end
end


<|start_filename|>lib/hedwig/adapter.ex<|end_filename|>
defmodule Hedwig.Adapter do
  @moduledoc """
  Hedwig Adapter Behaviour

  An adapter is the interface to the service your bot runs on. To implement an
  adapter you will need to translate messages from the service to the
  `Hedwig.Message` struct and call `Hedwig.Robot.handle_in(robot, msg)`.
  """

  @doc false
  defmacro __using__(_opts) do
    quote do
      import Kernel, except: [send: 2]

      @behaviour Hedwig.Adapter
      use GenServer

      def send(pid, %Hedwig.Message{} = msg) do
        GenServer.cast(pid, {:send, msg})
      end

      def reply(pid, %Hedwig.Message{} = msg) do
        GenServer.cast(pid, {:reply, msg})
      end

      def emote(pid, %Hedwig.Message{} = msg) do
        GenServer.cast(pid, {:emote, msg})
      end

      @doc false
      def start_link(robot, opts) do
        Hedwig.Adapter.start_link(__MODULE__, opts)
      end

      @doc false
      def stop(pid, timeout \\ 5000) do
        ref = Process.monitor(pid)
        Process.exit(pid, :normal)
        receive do
          {:DOWN, ^ref, _, _, _} -> :ok
        after
          timeout -> exit(:timeout)
        end
        :ok
      end

      @doc false
      defmacro __before_compile__(_env) do
        :ok
      end

      defoverridable [__before_compile__: 1, send: 2, reply: 2, emote: 2]
    end
  end

  @doc false
  def start_link(module, opts) do
    GenServer.start_link(module, {self(), opts})
  end

  @type robot :: pid
  @type state :: term
  @type opts  :: any
  @type msg   :: Hedwig.Message.t

  @callback send(pid, msg) :: term
  @callback reply(pid, msg) :: term
  @callback emote(pid, msg) :: term
end


<|start_filename|>test/support/test_responder.ex<|end_filename|>
defmodule TestResponder do
  use Hedwig.Responder

  @usage """
  (this is a test) - did someone say test?
  """
  hear ~r/this is a test/i, msg do
    send msg, "did someone say test?"
  end

  @usage """
  (i love <subject>) - Relies with "then why don't you marry <subject>!?"
  """
  hear ~r/i love (\w+)/i, msg do
    reply msg, "then why don't you marry #{msg.matches[1]}!?"
  end

  @usage """
  (i like <subject>) - Emotes "likes <subject> too!"
  """
  hear ~r/i like (?<subject>\w+)/i, msg do
    emote msg, "likes #{msg.matches["subject"]} too!"
  end

  @usage """
  hedwig do you hear me? - Replies with "loud and clear!"
  """
  respond ~r/do you hear me\?/i, msg do
    reply msg, "loud and clear!"
  end

  @usage """
  a random example
  """
  hear ~r/randomness/i, msg do
    send msg, random(Enum.to_list(1..1000))
  end
end


<|start_filename|>test/mix/tasks/hedwig.gen.robot_test.exs<|end_filename|>
defmodule Mix.Tasks.Hedwig.Gen.RobotTest do
  use ExUnit.Case

  import ExUnit.CaptureIO
  import Hedwig.FileHelpers
  import Mix.Tasks.Hedwig.Gen.Robot, only: [run: 1]

  test "generates a new robot" do
    in_tmp fn _ ->
      capture_io("1", fn -> run ["--name", "alfred"] end)

      assert_file "lib/hedwig/robot.ex", """
      defmodule Hedwig.Robot do
        use Hedwig.Robot, otp_app: :hedwig
      """

      assert_file "lib/hedwig/robot.ex", """
      def handle_connect(%{name: name} = state) do
      """

      assert_file "lib/hedwig/robot.ex", """
      def handle_disconnect(_reason, state) do
      """

      assert_file "config/config.exs", """
      use Mix.Config

      config :hedwig, Hedwig.Robot,
        adapter: Hedwig.Adapters.Console,
        name: "alfred",
        aka: "/",
      """
    end
  end

  test "generates a new robot with existing config file" do
    in_tmp fn _ ->
      File.mkdir_p! "config"
      File.write! "config/config.exs", """
      # Hello
      use Mix.Config
      # World
      """

      capture_io("1", fn -> run ["--name", "alfred", "--robot", "Robot"] end)

      assert_file "config/config.exs", """
      # Hello
      use Mix.Config

      config :hedwig, Robot,
        adapter: Hedwig.Adapters.Console,
        name: "alfred",
        aka: "/",
        responders: [
          {Hedwig.Responders.Help, []},
          {Hedwig.Responders.Ping, []}
        ]

      # World
      """
    end
  end


  test "generates a new namespaced robot" do
    in_tmp fn _ ->
      capture_io("1", fn -> run ["--name", "alfred", "--robot", "MyApp.Robot"] end)
      assert_file "lib/my_app/robot.ex", "defmodule MyApp.Robot do"
    end
  end
end


<|start_filename|>test/hedwig_test.exs<|end_filename|>
defmodule HedwigTest do
  use Hedwig.RobotCase

  @tag start_robot: true
  test "list started robots", %{robot: pid} do
    assert [{_id, ^pid, _type, [Hedwig.Robot]}] = Hedwig.which_robots()
  end

  @tag start_robot: true, name: "codsworth"
  test "find a robot by name", %{robot: pid} do
    assert :undefined == :global.whereis_name("hedwig")
    assert ^pid = :global.whereis_name("codsworth")
  end

  @tag start_robot: true
  test "handle_in/2", %{robot: pid} do
    Hedwig.Robot.handle_in(pid, {:ping, self()})
    assert_receive :pong
  end

  @tag start_robot: true
  test "stop_robot/1", %{robot: pid} do
    Hedwig.stop_robot(pid)
    refute Process.alive?(pid)
  end
end


<|start_filename|>lib/hedwig/test/robot_case.ex<|end_filename|>
defmodule Hedwig.RobotCase do
  use ExUnit.CaseTemplate

  @moduledoc false
  @robot Hedwig.TestRobot
  @default_responders [{Hedwig.Responders.Help, []}, {TestResponder, []}]

  using do
    quote do
      import unquote(__MODULE__)
      @robot Hedwig.TestRobot
    end
  end

  setup tags do
    if tags[:start_robot] do
      robot = Map.get(tags, :robot, @robot)
      name = Map.get(tags, :name, "hedwig")
      responders = Map.get(tags, :responders, @default_responders)

      config = [name: name, aka: "/", responders: responders]

      Application.put_env(:hedwig, robot, config)
      {:ok, pid} = Hedwig.start_robot(robot, config)
      adapter = update_robot_adapter(pid)

      on_exit fn -> Hedwig.stop_robot(pid) end

      msg = %Hedwig.Message{robot: pid, text: "", user: "testuser"}

      {:ok, %{robot: pid, adapter: adapter, msg: msg}}
    else
      {:ok, tags}
    end
  end

  def update_robot_adapter(robot) do
    test_process = self()
    adapter = :sys.get_state(robot).adapter
    :sys.replace_state(adapter, fn state -> %{state | conn: test_process} end)

    adapter
  end
end


<|start_filename|>test/hedwig/adapters/console/writer_test.exs<|end_filename|>
defmodule Hedwig.Adapters.Console.WriterTest do
  use ExUnit.Case

  import ExUnit.CaptureIO
  alias Hedwig.Adapters.Console.Writer

  test "console connection prints a banner" do
    output = capture_and_normalize_io fn ->
      Writer.start_link("hedwig")
      Process.sleep(10)
    end

    assert output =~ "Hedwig Console - press Ctrl+C to exit."
    assert output =~ "The console adapter is useful for quickly verifying how your"
    assert output =~ "bot will respond based on the current installed responders"
  end

  test "puts/2" do
    output = capture_and_normalize_io fn ->
      {:ok, pid} = Writer.start_link("hedwig")

      msg = %Hedwig.Message{text: "hello"}
      Writer.puts(pid, msg)
      Process.sleep(10)
    end

    assert output =~ "hedwig> hello"
  end

  test "clear/1" do
    output = capture_io fn ->
      {:ok, pid} = Writer.start_link("hedwig")

      Writer.clear(pid)
      Process.sleep(10)
    end

    assert output =~ "\e[2J\e[H\e[0m"
  end

  defp capture_and_normalize_io(fun) do
    fun |> capture_io() |> strip_ansi()
  end

  defp strip_ansi(string), do: Regex.replace(~r/\e\[[^m]+m/, string, "")
end
