   use Benchee.Formatter
 
   alias Benchee.{
-    Statistics, Suite, Benchmark.Scenario, Configuration, Conversion
+    Statistics,
+    Suite,
+    Benchmark.Scenario,
+    Configuration,
+    Conversion
   }
+
   alias Benchee.Conversion.{Count, Duration, Unit, DeviationPercent}
 
-  @type unit_per_statistic :: %{atom => Unit.t}
-
-  @default_label_width 4 # Length of column header
+  @type unit_per_statistic :: %{atom => Unit.t()}
+
+  # Length of column header
+  @default_label_width 4
   @ips_width 13
   @average_width 15
   @deviation_width 11

  ```

  """
  @spec format_scenarios([Scenario.t], map) :: [String.t, ...]
  def format_scenarios(scenarios, config) do
    sorted_scenarios = Statistics.sort(scenarios)
    %{unit_scaling: scaling_strategy} = config
    units = Conversion.units(sorted_scenarios, scaling_strategy)
    label_width = label_width(sorted_scenarios)

    [column_descriptors(label_width) |
      scenario_reports(sorted_scenarios, units, label_width)
      ++ comparison_report(sorted_scenarios, units, label_width, config)
      ++ extended_statistics_report(
          sorted_scenarios, units, label_width, config)]
  end

  @spec extended_statistics_report(
    [Scenario.t], unit_per_statistic, integer, map) :: [String.t]
  defp extended_statistics_report(_, _, _, %{extended_statistics: false}) do
    []
  end
  defp extended_statistics_report(scenarios, units, label_width, _config) do
    [
      descriptor("Extended statistics"),
      extended_column_descriptors(label_width) |
      extended_statistics(scenarios, units, label_width)
    ]
  end

  @spec extended_statistics([Scenario.t], unit_per_statistic, integer)
    :: [String.t]
  defp extended_statistics(scenarios, units, label_width) do
    Enum.map(scenarios, fn(scenario) ->
      format_scenario_extended(scenario, units, label_width)
    end)
  end

  @spec format_scenario_extended(Scenario.t, unit_per_statistic, integer)
    :: String.t
  defp format_scenario_extended(%Scenario{
                                  name: name,
                                  run_time_statistics: %Statistics{
                                    minimum:     minimum,
                                    maximum:     maximum,
                                    sample_size: sample_size,
                                    mode:        mode
                                  }
                                },
                                %{run_time: run_time_unit},
                                label_width) do
    "~*s~*ts~*ts~*ts~*ts\n"
    |> :io_lib.format([
      -label_width, name,
      @minimum_width, run_time_out(minimum, run_time_unit),
      @maximum_width, run_time_out(maximum, run_time_unit),
      @sample_size_width, Count.format(sample_size),
      @mode_width, mode_out(mode, run_time_unit)])
    |> to_string
  end

  @spec mode_out(Statistics.mode, Benchee.Conversion.Unit.t) :: String.t
  defp mode_out(modes, _run_time_unit) when is_nil(modes) do
    "None"
  end
  defp mode_out(modes, run_time_unit) when is_list(modes) do
    Enum.map_join(modes, ", ", fn(mode) -> run_time_out(mode, run_time_unit) end)
  end
  defp mode_out(mode, run_time_unit) when is_number(mode) do
    run_time_out(mode, run_time_unit)
  end

  @spec extended_column_descriptors(integer) :: String.t
  defp extended_column_descriptors(label_width) do
    "\n~*s~*s~*s~*s~*s\n"
    |> :io_lib.format([-label_width, "Name", @minimum_width, "minimum",
                       @maximum_width, "maximum", @sample_size_width, "sample size",
                       @mode_width, "mode"])
    |> to_string
  end

  @spec column_descriptors(integer) :: String.t
  defp column_descriptors(label_width) do
    "\n~*s~*s~*s~*s~*s~*s\n"
    |> :io_lib.format([-label_width, "Name", @ips_width, "ips",
                       @average_width, "average",
                       @deviation_width, "deviation", @median_width, "median",
                       @percentile_width, "99th %"])
    |> to_string
  end

  defp label_width(scenarios) do
    max_label_width =
      scenarios
      |> Enum.map(fn(scenario) -> String.length(scenario.name) end)
      |> Stream.concat([@default_label_width])
      |> Enum.max
    max_label_width + 1
  end

  @spec scenario_reports([Scenario.t], unit_per_statistic, integer)
    :: [String.t]
  defp scenario_reports(scenarios, units, label_width) do
    Enum.map(scenarios, fn(scenario) ->
      format_scenario(scenario, units, label_width)
    end)
  end

  @spec format_scenario(Scenario.t, unit_per_statistic, integer) :: String.t
  defp format_scenario(%Scenario{
                         name: name,
                         run_time_statistics: %Statistics{
                           average:       average,
                           ips:           ips,
                           std_dev_ratio: std_dev_ratio,
                           median:        median,
                           percentiles:   %{99 => percentile_99}
                         }
                       },
                       %{run_time: run_time_unit,
                         ips:      ips_unit,
                       }, label_width) do
    "~*s~*ts~*ts~*ts~*ts~*ts\n"
    |> :io_lib.format([
      -label_width, name,
      @ips_width, ips_out(ips, ips_unit),
      @average_width, run_time_out(average, run_time_unit),
      @deviation_width, deviation_out(std_dev_ratio),
      @median_width, run_time_out(median, run_time_unit),
      @percentile_width, run_time_out(percentile_99, run_time_unit)])
    |> to_string
  end


    DeviationPercent.format(std_dev_ratio)
  end

  @spec comparison_report([Scenario.t()], unit_per_statistic, integer, map) :: [String.t()]
  defp comparison_report(scenarios, units, label_width, config)

  defp comparison_report([_scenario], _, _, _) do
    # No need for a comparison when only one benchmark was run
    []
  end

  defp comparison_report(_, _, _, %{comparison: false}) do
    []
  end

  defp comparison_report([scenario | other_scenarios], units, label_width, _) do
    [
      descriptor("Comparison"),
      reference_report(scenario, units, label_width)
      | comparisons(scenario, units, label_width, other_scenarios)
    ]
  end

  defp comparison_report_for_memory(scenarios, units, label_width, config)

  defp comparison_report_for_memory([_scenario], _, _, _) do
    # No need for a comparison when only one benchmark was run
    []
  end

  defp comparison_report_for_memory(_, _, _, %{comparison: false}) do
    []
  end

  defp comparison_report_for_memory([scenario | other_scenarios], units, label_width, _) do
    [
      descriptor("Comparison"),
      reference_report_for_memory(scenario, units, label_width)
      | comparisons_for_memory(scenario, units, label_width, other_scenarios)
    ]
  end

  defp reference_report(
         %Scenario{
           name: name,
           run_time_statistics: %Statistics{ips: ips}
         },
         %{ips: ips_unit},
         label_width
       ) do
    "~*s~*s\n"
    |> :io_lib.format([-label_width, name, @ips_width, ips_out(ips, ips_unit)])
    |> to_string
  end

  defp reference_report_for_memory(
         %Scenario{
           name: name,
           memory_usage_statistics: %Statistics{median: median}
         },
         %{memory: memory_unit},
         label_width
       ) do
    "~*s~*s\n"
    |> :io_lib.format([-label_width, name, @median_width, run_time_out(median, memory_unit)])
    |> to_string
  end

  @spec comparisons(Scenario.t(), unit_per_statistic, integer, [Scenario.t()]) :: [String.t()]
  defp comparisons(
         %Scenario{run_time_statistics: reference_stats},
         units,
         label_width,
         scenarios_to_compare
       ) do
    Enum.map(scenarios_to_compare, fn scenario = %Scenario{run_time_statistics: job_stats} ->
      slower = reference_stats.ips / job_stats.ips
      format_comparison(scenario, units, label_width, slower)
    end)
  end

  defp comparisons_for_memory(
         %Scenario{memory_usage_statistics: reference_stats},
         units,
         label_width,
         scenarios_to_compare
       ) do
    Enum.map(scenarios_to_compare, fn scenario = %Scenario{memory_usage_statistics: job_stats} ->
      slower = if job_stats.median == 0 do
                 0.0
               else
                 reference_stats.median / job_stats.median
               end
      format_comparison_for_memory(scenario, units, label_width, slower)
    end)
  end

  defp format_comparison(
         %Scenario{name: name, run_time_statistics: %Statistics{ips: ips}},
         %{ips: ips_unit},
         label_width,
         slower
       ) do
    ips_format = ips_out(ips, ips_unit)

    "~*s~*s - ~.2fx slower\n"
    |> :io_lib.format([-label_width, name, @ips_width, ips_format, slower])
    |> to_string
  end

  defp format_comparison_for_memory(
         %Scenario{name: name, memory_usage_statistics: %Statistics{median: median}},
         %{memory: memory_unit},
         label_width,
         slower
       ) do
    median_format = run_time_out(median, memory_unit)

    "~*s~*s - ~.2fx more memory\n"
    |> :io_lib.format([-label_width, name, @median_width, median_format, slower])
    |> to_string
  end

  @spec descriptor(String.t()) :: String.t()
  defp descriptor(header_str) do
    "\n#{header_str}: \n"
  end
