import AOC

aoc 2015, 13 do
  import NimbleParsec

  def p1 do
    {guests, edges} = parse_input()

    permutations(guests)
    |> Enum.map(&happiness(edges, &1))
    |> Enum.max()
  end

  def p2 do
    {guests, edges} = parse_input()

    guests = guests ++ ["Zach"]

    permutations(guests)
    |> Enum.map(&happiness(edges, &1))
    |> Enum.max()
  end

  defparsec(
    :fact_parsec,
    ascii_string([?a..?z, ?A..?Z], min: 1)
    |> ignore(string(" would "))
    |> choice([
      string("gain"),
      string("lose")
    ])
    |> ignore(string(" "))
    |> integer(min: 1)
    |> ignore(string(" happiness units by sitting next to "))
    |> ascii_string([?a..?z, ?A..?Z], min: 1)
    |> ignore(string("."))
  )

  def parse_fact(str) do
    {:ok, [left, sign, amount, right], _, _, _, _} = fact_parsec(str)

    {{left, right}, edge_value(sign, amount)}
  end

  def edge_value("gain", v), do: v
  def edge_value("lose", v), do: 0 - v

  def sort({a, b} = p) when a > b, do: swap(p)
  def sort(p), do: p
  def swap({a, b}), do: {b, a}

  def parse_input() do
    edges =
      input_stream()
      |> Stream.map(&parse_fact/1)
      |> Enum.into(%{})

    guests =
      edges
      |> Map.keys()
      |> Enum.reduce(MapSet.new(), fn {a, b}, acc -> acc |> MapSet.put(a) |> MapSet.put(b) end)

    edges =
      for {pair, weight} <- edges, reduce: %{} do
        acc ->
          sorted = sort(pair)

          if Map.has_key?(acc, sorted) do
            acc
          else
            reverse = swap(pair)
            Map.put(acc, sorted, weight + Map.get(edges, reverse))
          end
      end

    guests = Enum.to_list(guests)

    {guests, edges}
  end

  def permutations([]), do: [[]]

  def permutations(xs) do
    for h <- xs, t <- permutations(xs -- [h]), do: [h | t]
  end

  def happiness(edges, [a | _] = xs) do
    happiness(edges, xs, a, 0)
  end

  def happiness(edges, [t], h, acc) do
    edge = Map.get(edges, sort({h, t}), 0)
    edge + acc
  end

  def happiness(edges, [a, b | rest], h, acc) do
    edge = Map.get(edges, sort({a, b}), 0)
    happiness(edges, [b | rest], h, acc + edge)
  end
end