defmodule Processes do
  @moduledoc false
  use Koans

  @intro "Processes"

  koan "You are a process" do
    assert Process.alive?(self()) == ___
  end

  koan "You can ask a process to introduce itself" do
    information = Process.info(self())

    assert information[:status] == ___
  end

  koan "Processes are referenced by their process ID (pid)" do
    assert is_pid(self()) == ___
  end

  koan "New processes are spawned functions" do
    value =
      spawn(fn ->
        receive do
        end
      end)

    assert is_pid(value) == ___
  end

  koan "Processes die when their function exits" do
    fast_process = spawn(fn -> :timer.sleep(10) end)
    slow_process = spawn(fn -> :timer.sleep(1000) end)

    # All spawned functions are executed concurrently with the current process.
    # You check back on slow_process and fast_process 50ms later. Let's
    # see if they are still alive!
    :timer.sleep(50)

    assert Process.alive?(fast_process) == ___
    assert Process.alive?(slow_process) == ___
  end

  koan "Processes can send and receive messages" do
    send(self(), "hola!")

    receive do
      msg -> assert msg == ___
    end
  end

  koan "A process will wait forever for a message" do
    wait_forever = fn ->
      receive do
      end
    end

    pid = spawn(wait_forever)

    assert Process.alive?(pid) == ___
  end

  koan "Received messages are queued, first in first out" do
    send(self(), "hola!")
    send(self(), "como se llama?")

    first_message =
      receive do
        message -> message
      end

    second_message =
      receive do
        message -> message
      end

    assert first_message == ___
    assert second_message == ___
  end

  koan "A common pattern is to include the sender in the message, so that it can reply" do
    greeter = fn ->
      receive do
        {:hello, sender} -> send(sender, :how_are_you?)
      end
    end

    pid = spawn(greeter)

    send(pid, {:hello, self()})

    # ms
    timeout = 100

    failure_message =
      "Sorry, I didn't get the right message. Look at the message that is sent back very closely, and try again"

    assert_receive ___, timeout, failure_message
  end

  def yelling_echo_loop do
    receive do
      {caller, value} ->
        send(caller, String.upcase(value))
        yelling_echo_loop()
    end
  end

  koan "Use tail recursion to receive multiple messages" do
    pid = spawn_link(&yelling_echo_loop/0)

    send(pid, {self(), "o"})
    assert_receive ___

    send(pid, {self(), "hai"})
    assert_receive ___
  end

  def state(value) do
    receive do
      {caller, :get} ->
        send(caller, value)
        state(value)

      {caller, :set, new_value} ->
        state(new_value)
    end
  end

  koan "Processes can be used to hold state" do
    initial_state = "foo"

    pid =
      spawn(fn ->
        state(initial_state)
      end)

    send(pid, {self(), :get})
    assert_receive ___

    send(pid, {self(), :set, "bar"})
    send(pid, {self(), :get})
    assert_receive ___
  end

  koan "Waiting for a message can get boring" do
    parent = self()

    spawn(fn ->
      receive do
      after
        5 -> send(parent, {:waited_too_long, "I am impatient"})
      end
    end)

    assert_receive ___
  end

  koan "Trapping will allow you to react to someone terminating the process" do
    parent = self()

    pid =
      spawn(fn ->
        Process.flag(:trap_exit, true)
        send(parent, :ready)

        receive do
          {:EXIT, _pid, reason} -> send(parent, {:exited, reason})
        end
      end)

    receive do
      :ready -> true
    end

    Process.exit(pid, :random_reason)

    assert_receive ___
  end

  koan "Parent processes can trap exits for children they are linked to" do
    Process.flag(:trap_exit, true)
    spawn_link(fn -> Process.exit(self(), :normal) end)

    assert_receive {:EXIT, _pid, ___}
  end

  koan "If you monitor your children, you'll be automatically informed of their departure" do
    spawn_monitor(fn -> Process.exit(self(), :normal) end)

    assert_receive {:DOWN, _ref, :process, _pid, ___}
  end
end
