lib/spotify/player.ex

defmodule Spotify.Player do
  @moduledoc """
  Provides functions for retrieving and manipulating user's current playback.

  https://developer.spotify.com/documentation/web-api/reference/player/
  """

  use Spotify.Responder
  import Spotify.Helpers

  alias Spotify.{
    Client,
    Context,
    CurrentlyPlaying,
    Device,
    Episode,
    History,
    Paging,
    Playback,
    Track
  }

  @doc """
  Add an item to the user's playback queue.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/add-to-queue/)

  **Method**: `POST`

  **Optional Params**: `device_id`
  """
  def enqueue(conn, uri, params \\ []) do
    url = params |> Keyword.put(:uri, uri) |> enqueue_url()
    conn |> Client.post(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.enqueue_url(device_id: "abc")
      "https://api.spotify.com/v1/me/player/queue?device_id=abc"
  """
  def enqueue_url(params \\ []) do
    "https://api.spotify.com/v1/me/player/queue" <> query_string(params)
  end

  @doc """
  Get the user's available devices.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/get-a-users-available-devices/)

  **Method**: `GET`
  """
  def get_devices(conn) do
    url = devices_url()
    conn |> Client.get(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.devices_url
      "https://api.spotify.com/v1/me/player/devices"
  """
  def devices_url do
    "https://api.spotify.com/v1/me/player/devices"
  end

  @doc """
  Get information about the user's playback currently playing context.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/get-information-about-the-users-current-playback/)

  **Method**: `GET`

  **Optional Params**: `market`, `additional_types`
  """
  def get_current_playback(conn, params \\ []) do
    url = player_url(params)
    conn |> Client.get(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.player_url(market: "US")
      "https://api.spotify.com/v1/me/player?market=US"
  """
  def player_url(params \\ []) do
    "https://api.spotify.com/v1/me/player" <> query_string(params)
  end

  @doc """
  Get the user's recently played tracks.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/get-recently-played/)

  **Method**: `GET`

  **Optional Params**: `limit`, `after`, `before`
  """
  def get_recently_played(conn, params \\ []) do
    url = recently_played_url(params)
    conn |> Client.get(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.recently_played_url(limit: 50)
      "https://api.spotify.com/v1/me/player/recently-played?limit=50"
  """
  def recently_played_url(params \\ []) do
    "https://api.spotify.com/v1/me/player/recently-played" <> query_string(params)
  end

  @doc """
  Get the user's currently playing tracks.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/get-the-users-currently-playing-track/)

  **Method**: `GET`

  **Optional Params**: `market`, `additional_types`
  """
  def get_currently_playing(conn, params \\ []) do
    url = currently_playing_url(params)
    conn |> Client.get(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.currently_playing_url(market: "US")
      "https://api.spotify.com/v1/me/player/currently-playing?market=US"
  """
  def currently_playing_url(params \\ []) do
    "https://api.spotify.com/v1/me/player/currently-playing" <> query_string(params)
  end

  @doc """
  Pause the user's playback.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/pause-a-users-playback/)

  **Method**: `PUT`

  **Optional Params**: `device_id`
  """
  def pause(conn, params \\ []) do
    url = pause_url(params)
    conn |> Client.put(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.pause_url(device_id: "abc")
      "https://api.spotify.com/v1/me/player/pause?device_id=abc"
  """
  def pause_url(params \\ []) do
    "https://api.spotify.com/v1/me/player/pause" <> query_string(params)
  end

  @doc """
  Seek to position in currently playing track.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/seek-to-position-in-currently-playing-track/)

  **Method**: `PUT`

  **Optional Params**: `device_id`
  """
  def seek(conn, position_ms, params \\ []) do
    url = params |> Keyword.put(:position_ms, position_ms) |> seek_url()
    conn |> Client.put(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.seek_url(device_id: "abc")
      "https://api.spotify.com/v1/me/player/seek?device_id=abc"
  """
  def seek_url(params \\ []) do
    "https://api.spotify.com/v1/me/player/seek" <> query_string(params)
  end

  @doc """
  Set repeat mode for the user's playback.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/set-repeat-mode-on-users-playback/)

  **Method**: `PUT`

  **Optional Params**: `device_id`
  """
  def set_repeat(conn, state, params \\ []) when state in [:track, :context, :off] do
    url = params |> Keyword.put(:state, state) |> repeat_url()
    conn |> Client.put(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.repeat_url(device_id: "abc")
      "https://api.spotify.com/v1/me/player/repeat?device_id=abc"
  """
  def repeat_url(params \\ []) do
    "https://api.spotify.com/v1/me/player/repeat" <> query_string(params)
  end

  @doc """
  Set volume for the user's playback.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/set-volume-for-users-playback/)

  **Method**: `PUT`

  **Optional Params**: `device_id`
  """
  def set_volume(conn, volume_percent, params \\ []) when volume_percent in 0..100 do
    url = params |> Keyword.put(:volume_percent, volume_percent) |> volume_url()
    conn |> Client.put(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.volume_url(device_id: "abc")
      "https://api.spotify.com/v1/me/player/volume?device_id=abc"
  """
  def volume_url(params \\ []) do
    "https://api.spotify.com/v1/me/player/volume" <> query_string(params)
  end

  @doc """
  Skip the user's playback to next track.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/skip-users-playback-to-next-track/)

  **Method**: `POST`

  **Optional Params**: `device_id`
  """
  def skip_to_next(conn, params \\ []) do
    url = next_url(params)
    conn |> Client.post(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.next_url(device_id: "abc")
      "https://api.spotify.com/v1/me/player/next?device_id=abc"
  """
  def next_url(params \\ []) do
    "https://api.spotify.com/v1/me/player/next" <> query_string(params)
  end

  @doc """
  Skip the user's playback to previous track.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/skip-users-playback-to-previous-track/)

  **Method**: `POST`

  **Optional Params**: `device_id`
  """
  def skip_to_previous(conn, params \\ []) do
    url = previous_url(params)
    conn |> Client.post(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.previous_url(device_id: "abc")
      "https://api.spotify.com/v1/me/player/previous?device_id=abc"
  """
  def previous_url(params \\ []) do
    "https://api.spotify.com/v1/me/player/previous" <> query_string(params)
  end

  @doc """
  Start/resume the user's playback.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/start-a-users-playback/)

  **Method**: `PUT`

  **Optional Params**: `device_id`, `context_uri`, `uris`, `offset`, `position_ms`
  """
  def play(conn, params \\ []) do
    {query_params, body_params} = Keyword.split(params, [:device_id])

    url = play_url(query_params)
    body = body_params |> Enum.into(%{}) |> Poison.encode!()

    conn |> Client.put(url, body) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.play_url(device_id: "abc")
      "https://api.spotify.com/v1/me/player/play?device_id=abc"
  """
  def play_url(params \\ []) do
    "https://api.spotify.com/v1/me/player/play" <> query_string(params)
  end

  @doc """
  Toggle shuffle for the user's playback.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/toggle-shuffle-for-users-playback/)

  **Method**: `PUT`

  **Optional Params**: `device_id`
  """
  def set_shuffle(conn, state, params \\ []) when state |> is_boolean() do
    url = params |> Keyword.put(:state, state) |> shuffle_url()
    conn |> Client.put(url) |> handle_response()
  end

  @doc """
      iex> Spotify.Player.shuffle_url(device_id: "abc")
      "https://api.spotify.com/v1/me/player/shuffle?device_id=abc"
  """
  def shuffle_url(params \\ []) do
    "https://api.spotify.com/v1/me/player/shuffle" <> query_string(params)
  end

  @doc """
  Transfer the user's playback.
  [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/player/transfer-a-users-playback/)

  **Method**: `PUT`

  **Optional Params**: `play`
  """
  def transfer_playback(conn, device_ids, params \\ []) do
    url = player_url()
    body = params |> Keyword.put(:device_ids, device_ids) |> Enum.into(%{}) |> Poison.encode!()

    conn |> Client.put(url, body) |> handle_response()
  end

  def build_response(%{"devices" => devices}) do
    Enum.map(devices, &to_struct(Device, &1))
  end

  def build_response(body = %{"items" => _}) do
    build_paged_histories(body)
  end

  def build_response(body = %{"device" => _}) do
    body =
      body
      |> build_item()
      |> build_device()
      |> build_context()

    to_struct(Playback, body)
  end

  def build_response(body) do
    body =
      body
      |> build_item()
      |> build_context()

    to_struct(CurrentlyPlaying, body)
  end

  defp build_paged_histories(body) do
    %Paging{
      href: body["href"],
      items: body["items"] |> build_histories(),
      limit: body["limit"],
      next: body["next"],
      offset: body["offset"],
      previous: body["previous"],
      total: body["total"]
    }
  end

  defp build_histories(histories) do
    Enum.map(histories, fn history ->
      %History{
        track: to_struct(Track, history["track"]),
        played_at: history["played_at"],
        context: to_struct(Context, history["context"])
      }
    end)
  end

  defp build_item(body = %{"item" => nil}), do: body

  defp build_item(body = %{"currently_playing_type" => "track"}) do
    Map.update!(body, "item", &to_struct(Track, &1))
  end

  defp build_item(body = %{"currently_playing_type" => "episode"}) do
    Map.update!(body, "item", &to_struct(Episode, &1))
  end

  defp build_device(body = %{"device" => _}) do
    Map.update!(body, "device", &to_struct(Device, &1))
  end

  defp build_device(body), do: body

  defp build_context(body = %{"context" => nil}), do: body

  defp build_context(body = %{"context" => _}) do
    Map.update!(body, "context", &to_struct(Context, &1))
  end
end