defmodule Spotify.Album do
  @moduledoc """
    Functions for retrieving information about albums.

    Some endpoints return collections. Spotify wraps the collection in a paging object,
    this API does the same. A single piece of data will not be wrapped, however
    in some instances an objet key can contain a collection wrapped in a paging
    object, for example, requesting several albums will not give you a paging
    object for the albums, but the tracks will be wrapped in one.

    There are two functions for each endpoint, one that actually makes the request,
    and one that provides the endpoint:

        Spotify.Playist.create_playlist(conn, "foo", "bar") # makes the POST request.
        Spotify.Playist.create_playlist_url("foo", "bar") # provides the url for the request.

  https://developer.spotify.com/web-api/get-several-albums/
  """

  use Spotify.Responder
  import Spotify.Helpers

  alias Spotify.{
    Album,
    Client,
    Paging,
    Track
  }

  defstruct ~w[
    album_type
    artists
    available_markets
    copyrights
    external_ids
    external_urls
    genres
    href
    id
    images
    name
    popularity
    release_date
    release_date_precision
    tracks
    type
    label
  ]a

  @doc """
  Get Spotify catalog information for a single album.
  [Spotify Documentation](https://developer.spotify.com/web-api/get-album/)

  **Method**: GET

  **Optional Params**: `market`

      Spotify.Album.get_album(conn, "4")
      # => { :ok, %Spotify.Album{...} }
  """
  def get_album(conn, id, params \\ []) do
    url = get_album_url(id, params)
    conn |> Client.get(url) |> handle_response
  end

  @doc """
  Get Spotify catalog information for a single album.

      iex> Spotify.Album.get_album_url("4")
      "https://api.spotify.com/v1/albums/4"
  """
  def get_album_url(id, params \\ []) do
    "https://api.spotify.com/v1/albums/#{id}" <> query_string(params)
  end

  @doc """
  Get Spotify catalog information for multiple albums identified by their Spotify IDs.
  [Spotify Documentation](https://developer.spotify.com/web-api/get-several-albums/)

  **Method**: GET

  **Optional Params**: `market`

      Spotify.Album.get_albums(conn, ids: "1,4")
      # => { :ok, %Spotify.Album{...} }
  """
  def get_albums(conn, params) do
    url = get_albums_url(params)
    conn |> Client.get(url) |> handle_response
  end

  @doc """
  Get Spotify catalog information for multiple albums identified by their Spotify IDs.

      iex> Spotify.Album.get_albums_url(ids: "1,3")
      "https://api.spotify.com/v1/albums?ids=1%2C3"
  """
  def get_albums_url(params) do
    "https://api.spotify.com/v1/albums" <> query_string(params)
  end

  @doc """
  Get Spotify catalog information about an album’s tracks.
  [Spotify Documentation](https://developer.spotify.com/web-api/get-albums-tracks/)

  **Method**: `GET`
  **Optional Params**: `limit`, `offset`, `market`

      Spotify.Album.get_album_tracks("1")
      # => { :ok, %Paging{items: [%Spotify.Tracks{}, ...] } }
  """
  def get_album_tracks(conn, id, params \\ []) do
    url = get_album_tracks_url(id, params)
    conn |> Client.get(url) |> handle_response
  end

  @doc """
  Get Spotify catalog information about an album’s tracks.

      iex> Spotify.Album.get_album_tracks_url("4")
      "https://api.spotify.com/v1/albums/4/tracks"
  """
  def get_album_tracks_url(id, params \\ []) do
    "https://api.spotify.com/v1/albums/#{id}/tracks" <> query_string(params)
  end

  @doc """
  Get Spotify catalog information about an artist’s albums. Optional parameters can be specified in the query string to filter and sort the response.

  [Spotify Documentation](https://developer.spotify.com/web-api/get-artists-albums/)

  **Method**: `GET`

      Spotify.Album.get_arists_albums(conn, "4")
      # => { :ok, %Paging{items: [%Album{}, ..]} }
  """
  def get_artists_albums(conn, id) do
    url = get_artists_albums_url(id)
    conn |> Client.get(url) |> handle_response
  end

  @doc """
  Get Spotify catalog information about an artist’s albums. Optional parameters can be specified in the query string to filter and sort the response.

      iex> Spotify.Album.get_artists_albums_url("4")
      "https://api.spotify.com/v1/artists/4/albums"
  """
  def get_artists_albums_url(id) do
    "https://api.spotify.com/v1/artists/#{id}/albums"
  end

  @doc """
  Get a list of new album releases featured in Spotify
  [Spotify Documentation](https://developer.spotify.com/web-api/get-list-new-releases/)

  **Method**: `GET`
  **Optional Params**: `country`, `limit`, `offset`

      Spotify.Album.new_releases(conn, country: "US")
      # => { :ok, %Paging{items: [%Album{}, ..]} }
  """
  def new_releases(conn, params \\ []) do
    url = new_releases_url(params)
    conn |> Client.get(url) |> handle_response
  end

  @doc """
  Get a list of new album releases featured in Spotify

      iex> Spotify.Album.new_releases_url(country: "US")
      "https://api.spotify.com/v1/browse/new-releases?country=US"
  """
  def new_releases_url(params) do
    "https://api.spotify.com/v1/browse/new-releases" <> query_string(params)
  end

  @doc """
  Save one or more albums to the current user’s “Your Music” library.
  [Spotify Documentation](https://developer.spotify.com/web-api/save-albums-user/)

  **Method**: `PUT`
  **Required Params**: `ids`

      Spotify.Album.save_albums(conn, ids: "1,4")
      # => :ok
  """

  def save_albums(conn, params) do
    url = save_albums_url(params)
    conn |> Client.put(url) |> handle_response
  end

  @doc """
  Save one or more albums to the current user’s “Your Music” library.
  [Spotify Documentation](https://developer.spotify.com/web-api/get-users-saved-albums/)

      iex> Spotify.Album.save_albums_url(ids: "1,4")
      "https://api.spotify.com/v1/me/albums?ids=1%2C4"
  """
  def save_albums_url(params) do
    "https://api.spotify.com/v1/me/albums" <> query_string(params)
  end

  @doc """
  Get a list of the albums saved in the current Spotify user’s “Your Music” library.
  [Spotify Documentation](https://developer.spotify.com/web-api/get-users-saved-albums/)

  **Method**: `GET`
  **Optional Params**: `limit`, `offset`, `market`

      Spotify.Album.my_albums(conn, limit: 5)
      # => { :ok, %Paging{items: [%Album{}, ...]} }
  """
  def my_albums(conn, params) do
    url = my_albums_url(params)
    conn |> Client.get(url) |> handle_response
  end

  @doc """
  Get a list of the albums saved in the current Spotify user’s “Your Music” library.

      iex> Spotify.Album.my_albums_url(limit: 5)
      "https://api.spotify.com/v1/me/albums?limit=5"
  """
  def my_albums_url(params) do
    "https://api.spotify.com/v1/me/albums" <> query_string(params)
  end

  @doc """
  Remove one or more albums from the current user’s “Your Music” library.
  [Spotify Documentation](https://developer.spotify.com/web-api/remove-albums-user/)

  **Method**: `DELETE`

      Spotify.Album.remove_albums(conn, ids: "1,4")
      # => :ok
  """
  def remove_albums(conn, params) do
    url = remove_albums_url(params)
    conn |> Client.delete(url) |> handle_response
  end

  @doc """
  Remove one or more albums from the current user’s “Your Music” library.

    iex> Spotify.Album.remove_albums_url(ids: "1,4")
    "https://api.spotify.com/v1/me/albums?ids=1%2C4"
  """
  def remove_albums_url(params) do
    "https://api.spotify.com/v1/me/albums" <> query_string(params)
  end

  @doc """
  Check if one or more albums is already saved in the current Spotify user’s “Your Music” library.
  [Spotify Documentation](https://developer.spotify.com/web-api/check-users-saved-albums/)

  **Method**: `GET`

      Spotify.Album.check_albums(ids: "1,4")
      # => [true, false]  (Album 1 is in the user's library, 4 is not)
  """
  def check_albums(conn, params) do
    url = check_albums_url(params)
    conn |> Client.get(url) |> handle_response
  end

  @doc """
  Check if one or more albums is already saved in the current Spotify user’s “Your Music” library.

      iex> Spotify.Album.check_albums_url(ids: "1,4")
      "https://api.spotify.com/v1/me/albums/contains?ids=1%2C4"
  """
  def check_albums_url(params) do
    "https://api.spotify.com/v1/me/albums/contains" <> query_string(params)
  end

  @doc """
  Implement the callback required by the Responder behavior
  """
  def build_response(body) do
    case body do
      %{"albums" => albums} -> build_albums(albums)
      %{"items" => items} -> infer_type_and_build(items)
      %{"album_type" => _} -> build_album(body)
    end
  end

  @doc false
  def infer_type_and_build(items) do
    case List.first(items) do
      %{"track_number" => _} -> build_tracks(items)
      %{"album_type" => _} -> build_albums(items)
      %{"album" => _} -> build_albums(items)
    end
  end

  @doc false
  def build_tracks(tracks) do
    paging = %Paging{items: tracks}
    tracks = Track.build_tracks(tracks)
    Map.put(paging, :items, tracks)
  end

  @doc false
  def build_album(album) do
    album = to_struct(Album, album)
    paging = to_struct(Paging, album.tracks)
    tracks = Enum.map(paging.items, &to_struct(Track, &1))
    paging = Map.put(paging, :items, tracks)
    Map.put(album, :tracks, paging)
  end

  @doc false
  def build_albums(albums) when is_list(albums), do: Enum.map(albums, &build_album/1)

  def build_albums(albums) when is_map(albums) do
    paging = to_struct(Paging, albums)
    new_releases = Enum.map(paging.items, &to_struct(Album, &1))
    Map.put(paging, :items, new_releases)
  end
end
