defmodule RPGTest do
  use ExUnit.Case

  alias RPG.{Edible, Character, LoafOfBread, ManaPotion, Poison, EmptyBottle}

  defmodule NewItem do
    defstruct []
  end

  describe "Edible" do
    @tag task_id: 1
    test "is a protocol" do
      assert Edible.__protocol__(:functions) == [eat: 2]
    end

    @tag task_id: 1
    test "cannot eat a completely new item" do
      assert_raise Protocol.UndefinedError, fn ->
        Edible.eat(%NewItem{}, %Character{})
      end
    end
  end

  describe "LoafOfBread" do
    @tag task_id: 2
    test "implements the Edible protocol" do
      {:consolidated, modules} = Edible.__protocol__(:impls)
      assert LoafOfBread in modules
    end

    @tag task_id: 2
    test "eating it increases health" do
      character = %Character{health: 50}
      {_byproduct, %Character{} = character} = Edible.eat(%LoafOfBread{}, character)
      assert character.health == 55
    end

    @tag task_id: 2
    test "eating it has no byproduct" do
      character = %Character{}
      {byproduct, %Character{}} = Edible.eat(%LoafOfBread{}, character)
      assert byproduct == nil
    end

    @tag task_id: 2
    test "eating it does not affect mana" do
      character = %Character{mana: 77}
      {_byproduct, %Character{} = character} = Edible.eat(%LoafOfBread{}, character)
      assert character.mana == 77
    end
  end

  describe "ManaPotion" do
    @tag task_id: 3
    test "implements the Edible protocol" do
      {:consolidated, modules} = Edible.__protocol__(:impls)
      assert ManaPotion in modules
    end

    @tag task_id: 3
    test "eating it increases mana" do
      character = %Character{mana: 10}
      {_byproduct, %Character{} = character} = Edible.eat(%ManaPotion{strength: 6}, character)
      assert character.mana == 16
      {_byproduct, %Character{} = character} = Edible.eat(%ManaPotion{strength: 9}, character)
      assert character.mana == 25
    end

    @tag task_id: 3
    test "eating it produces an empty bottle" do
      character = %Character{}
      {byproduct, %Character{}} = Edible.eat(%ManaPotion{}, character)
      assert byproduct == %EmptyBottle{}
    end

    @tag task_id: 3
    test "eating it does not affect health" do
      character = %Character{health: 4}
      {_byproduct, %Character{} = character} = Edible.eat(%ManaPotion{strength: 6}, character)
      assert character.health == 4
    end
  end

  describe "Poison" do
    @tag task_id: 4
    test "implements the Edible protocol" do
      {:consolidated, modules} = Edible.__protocol__(:impls)
      assert Poison in modules
    end

    @tag task_id: 4
    test "eating it brings health down to 0" do
      character = %Character{health: 120}
      {_byproduct, %Character{} = character} = Edible.eat(%Poison{}, character)
      assert character.health == 0
    end

    @tag task_id: 4
    test "eating it produces an empty bottle" do
      character = %Character{}
      {byproduct, %Character{}} = Edible.eat(%Poison{}, character)
      assert byproduct == %EmptyBottle{}
    end

    @tag task_id: 4
    test "eating it does not affect mana" do
      character = %Character{mana: 99}
      {_byproduct, %Character{} = character} = Edible.eat(%Poison{}, character)
      assert character.mana == 99
    end
  end

  @tag task_id: 4
  test "eating many things one after another" do
    items = [
      %LoafOfBread{},
      %ManaPotion{strength: 10},
      %ManaPotion{strength: 2},
      %LoafOfBread{}
    ]

    character = %Character{health: 100, mana: 100}

    character =
      Enum.reduce(items, character, fn item, character ->
        {_, character} = Edible.eat(item, character)
        character
      end)

    assert character.health == 110
    assert character.mana == 112
  end
end