defmodule AdventOfCode.Y2023.Day18 do
  @moduledoc """
  --- Day 18: Lavaduct Lagoon ---
  Problem Link: https://adventofcode.com/2023/day/18
  Difficulty: l
  Tags: geometry
  """
  alias AdventOfCode.Helpers.{InputReader, Transformers}

  import AdventOfCode.Algorithms.Geometry, only: [polygon_area: 1]

  @dir_map_1 %{"R" => :right, "D" => :down, "L" => :left, "U" => :up}
  @dir_map_2 %{"0" => :right, "1" => :down, "2" => :left, "3" => :up}

  def input, do: InputReader.read_from_file(2023, 18)

  def run(input \\ input()) do
    input = parse(input)
    {input |> skip_colour() |> lava_capacity(), input |> rectify() |> lava_capacity()}
  end

  def parse(data \\ input()) do
    for line <- Transformers.lines(data) do
      [_, dir, steps, colours] = Regex.run(~r{(.) (\d+) \((.+)\)}, line)
      {@dir_map_1[dir], String.to_integer(steps), colours}
    end
  end

  defp lava_capacity(input) do
    {perimeter, polygon} = get_geometry(input)
    trunc(polygon_area(polygon) + div(perimeter, 2) + 1)
  end

  defp move(rules), do: move(rules, {0, 0}, [{0, 0}])
  defp move([], _, points), do: Enum.reverse(points)

  defp move([{:up, steps} | rules], {x, y}, points),
    do: move(rules, {x - steps, y}, [{x - steps, y} | points])

  defp move([{:down, steps} | rules], {x, y}, points),
    do: move(rules, {x + steps, y}, [{x + steps, y} | points])

  defp move([{:left, steps} | rules], {x, y}, points),
    do: move(rules, {x, y - steps}, [{x, y - steps} | points])

  defp move([{:right, steps} | rules], {x, y}, points),
    do: move(rules, {x, y + steps}, [{x, y + steps} | points])

  defp get_geometry(instructions) do
    perimeter = Enum.reduce(instructions, 0, fn x, acc -> acc + elem(x, 1) end)

    {perimeter, move(instructions)}
  end

  defp process_colour(<<"#", steps::binary-size(5), dir::binary-1>>),
    do: {@dir_map_2[dir], elem(Integer.parse(steps, 16), 0)}

  defp skip_colour(instructions), do: Enum.map(instructions, fn {a, b, _} -> {a, b} end)
  defp rectify(instructions), do: Enum.map(instructions, &process_colour(elem(&1, 2)))
end