defmodule Mix.Tasks.AdventOfCode.Solve do
  use Mix.Task

  @shortdoc "Runs solution code with problem input"

  @moduledoc """
  #{@shortdoc}.

  ## Options

  `--input`
    - name of a `.input` file in `priv/inputs/` without the `.input` extension or `-` to read from stdin

  ## Examples

      # Run Day 2, Part 1 solution program against the `2.1.input` file
      $ mix advent_of_code.solve 2.1

      # Run Day 1, Part 2 with a special input
      $ mix advent_of_code.solve 1.2 --input day-1-part-2-small # priv/inputs/day-1-part-2-small.input

      # Run Day 17, Part 1 with input from stdin
  #   $ mix advent_of_code.solve 17.1 --input -
  """

  @switches [input: :string]

  @impl Mix.Task
  def run(args) do
    case OptionParser.parse!(args, strict: @switches) do
      {opts, [selector]} ->
        case stream_for_input(selector, opts) do
          {:indeterminate, stream} ->
            ProgressBar.render_spinner(
              [text: "Processing indeterminate data...", done: "Done."],
              fn -> solve(selector, stream) end
            )
            |> IO.puts()

          {size, stream} ->
            stream =
              stream
              |> Stream.with_index(1)
              |> Stream.each(fn {_, i} -> ProgressBar.render(i, size) end)
              |> Stream.map(fn {l, _} -> l end)

            solve(selector, stream)
            |> IO.puts()
        end

      _ ->
        nil
    end
  end

  defp solve(selector, stream) do
    selector
    |> AdventOfCode.solver()
    |> AdventOfCode.PuzzleSolver.solve(stream)
  end

  defp stream_for_input(selector, opts) do
    input = Keyword.get(opts, :input, selector)

    if input == "-" do
      {:indeterminate, IO.stream()}
    else
      file = Path.join("priv/inputs", "#{input}.input")
      stream = File.stream!(file)
      {Enum.count(stream), stream}
    end
  end
end