defmodule ExViva do
  @base_url "https://services.viva.sjofartsverket.se:8080"
  alias ExViva.{Decoders, HTTP}
  @moduledoc """
  This modules provides functions to fetch imformation from the Swedish Maritime Administration.
  The data from the API is not interpreted in any way, it is just normalized and parsed
  into appropriate Elixir datatypes.
  """

  @doc """
  Returns a list of %ExViva.Station{} structs.
  """
  def get_stations(opts \\ []) do
    get_stations_request()
    |> request(opts)
  end

  @doc """
  Get observations for a particaluar station.

  Station_id can be a number, a %ExViva.Station{} struct or anything that implements
  the Viva.StationIdentity protocol.
  """
  def get_station(station_id, opts \\ []) do
    get_station_request(station_id, opts)
    |> request(opts)
  end

  def get_stations_request() do
    HTTP.Request.get("/output/vivaoutputservice.svc/vivastation/")
    |> HTTP.Request.accept_json()
    |> HTTP.Request.add_response_handler(:normalize_headers)
    |> HTTP.Request.add_response_handler(Decoders.GetStationsResult)
  end

  def get_station_request(station_id, opts \\ []) do
    station_id = ExViva.StationIdentity.station_id(station_id)

    HTTP.Request.get("/output/vivaoutputservice.svc/vivastation/#{station_id}")
    |> HTTP.Request.accept_json()
    |> HTTP.Request.add_response_handler(:normalize_headers)
    |> use_decoder(Decoders.GetSingleStationResult, get_opt(opts, :decode, true))
  end

  def sample_all(opts \\ []) do
    with {:ok, stations} <- get_stations() do
      {success, errors} = stations
      |> Task.async_stream(&get_station(&1, opts), max_concurrency: get_opt(opts, :concurrency, System.schedulers_online))
      |> Stream.map(&elem(&1, 1))
      |> Enum.split_with(fn {:ok, _result} -> true end)


      {:ok, success |> Enum.flat_map(fn {:ok, result} -> result.samples end), errors}
    end
  end

  defp request(request, opts) do
    request
    |> HTTP.Request.put_header("User-Agent", "ExViva 0.0.1")
    |> HTTP.request(base_url: @base_url)
    |> unwrap(get_opt(opts, :unwrap, true))
  end

  defp get_opt(opts, key, default), do: Keyword.get(opts, key, default)

  defp use_decoder(request, decoder, true) do
    request
    |> HTTP.Request.add_response_handler(decoder)
  end
  defp use_decoder(request, _, _), do: request

  defp unwrap({:ok, {200, _headers, body}}, true) do
    {:ok, body}
  end
  defp unwrap(result, false), do: result
end