package net.roarsoftware.lastfm;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.roarsoftware.xml.DomElement;

/**
 * Provides nothing more than a namespace for the API methods starting with geo.
 *
 * @author Janni Kovacs
 */
public class Geo {

	private Geo() {
	}

	/**
	 * Get all events in a specific location by country or city name.<br/>
	 * This method returns <em>all</em> events by subsequently calling {@link #getEvents(String, String, int, String)}
	 * and concatenating the single results into one list.<br/>
	 * Pay attention if you use this method as it may produce a lot of network traffic and therefore
	 * may consume a long time.
	 *
	 * @param location Specifies a location to retrieve events for
	 * @param distance Find events within a specified distance
	 * @param apiKey A Last.fm API key.
	 * @return a list containing all events
	 */
	public static Collection<Event> getAllEvents(String location, String distance, String apiKey) {
		Collection<Event> events = null;
		int page = 1, total;
		do {
			PaginatedResult<Event> result = getEvents(location, distance, page, apiKey);
			total = result.getTotalPages();
			Collection<Event> pageResults = result.getPageResults();
			if (events == null) {
				// events is initialized here to initialize it with the right size and avoid array copying later on
				events = new ArrayList<Event>(total * pageResults.size());
			}
			for (Event artist : pageResults) {
				events.add(artist);
			}
			page++;
		} while (page <= total);
		return events;
	}

	/**
	 * Get all events in a specific location by country or city name.<br/>
	 * This method only returns the first page of a possibly paginated result. To retrieve all pages
	 * get the total number of pages via {@link net.roarsoftware.lastfm.PaginatedResult#getTotalPages()} and
	 * subsequently call {@link #getEvents(String, String, int, String)} with the successive page numbers.
	 *
	 * @param location Specifies a location to retrieve events for
	 * @param distance Find events within a specified distance
	 * @param apiKey A Last.fm API key.
	 * @return a {@link PaginatedResult} containing a list of events
	 */
	public static PaginatedResult<Event> getEvents(String location, String distance, String apiKey) {
		return getEvents(location, distance, 1, apiKey);
	}

	/**
	 * Get all events in a specific location by country or city name.<br/>
	 * This method only returns the specified page of a paginated result.
	 *
	 * @param location Specifies a location to retrieve events for
	 * @param distance Find events within a specified distance
	 * @param page A page number for pagination
	 * @param apiKey A Last.fm API key.
	 * @return a {@link PaginatedResult} containing a list of events
	 */
	public static PaginatedResult<Event> getEvents(String location, String distance, int page, String apiKey) {
		Map<String, String> params = new HashMap<String, String>();
		params.put("page", String.valueOf(page));
		if (location != null)
			params.put("location", location);
		if (distance != null)
			params.put("distance", distance);
		Result result = Caller.getInstance().call("geo.getEvents", apiKey, params);
		if (!result.isSuccessful())
			return new PaginatedResult<Event>(0, 0, Collections.<Event>emptyList());
		DomElement element = result.getContentElement();
		List<Event> events = new ArrayList<Event>();
		for (DomElement domElement : element.getChildren("event")) {
			events.add(Event.eventFromElement(domElement));
		}
		int currentPage = Integer.valueOf(element.getAttribute("page"));
		int totalPages = Integer.valueOf(element.getAttribute("totalpages"));
		return new PaginatedResult<Event>(page, totalPages, events);
	}

	/**
	 * Get all events in a specific location by country or city name.<br/>
	 * This method only returns the specified page of a paginated result.
	 *
	 * @param latitude Latitude
	 * @param longitude Longitude
	 * @param page A page number for pagination
	 * @param apiKey A Last.fm API key.
	 * @return a {@link PaginatedResult} containing a list of events
	 */
	public static PaginatedResult<Event> getEvents(double latitude, double longitude, int page, String apiKey) {
		Map<String, String> params = new HashMap<String, String>();
		params.put("page", String.valueOf(page));
		params.put("lat", String.valueOf(latitude));
		params.put("long", String.valueOf(longitude));
		Result result = Caller.getInstance().call("geo.getEvents", apiKey, params);
		if (!result.isSuccessful())
			return new PaginatedResult<Event>(0, 0, Collections.<Event>emptyList());
		DomElement element = result.getContentElement();
		List<Event> events = new ArrayList<Event>();
		for (DomElement domElement : element.getChildren("event")) {
			events.add(Event.eventFromElement(domElement));
		}
		int currentPage = Integer.valueOf(element.getAttribute("page"));
		int totalPages = Integer.valueOf(element.getAttribute("totalpages"));
		return new PaginatedResult<Event>(page, totalPages, events);
	}

	/**
	 * Get the most popular artists on Last.fm by country
	 *
	 * @param country A country name, as defined by the ISO 3166-1 country names standard
	 * @param apiKey A Last.fm API key.
	 * @return list of Artists
	 */
	public static Collection<Artist> getTopArtists(String country, String apiKey) {
		Result result = Caller.getInstance().call("geo.getTopArtists", apiKey, "country", country);
		if (!result.isSuccessful())
			return Collections.emptyList();
		List<Artist> artists = new ArrayList<Artist>();
		for (DomElement domElement : result.getContentElement().getChildren("artist")) {
			artists.add(Artist.artistFromElement(domElement));
		}
		return artists;
	}

	/**
	 * Get the most popular tracks on Last.fm by country
	 *
	 * @param country A country name, as defined by the ISO 3166-1 country names standard
	 * @param apiKey A Last.fm API key.
	 * @return a list of Tracks
	 */
	public static Collection<Track> getTopTracks(String country, String apiKey) {
		Result result = Caller.getInstance().call("geo.getTopTracks", apiKey, "country", country);
		if (!result.isSuccessful())
			return Collections.emptyList();
		List<Track> tracks = new ArrayList<Track>();
		for (DomElement domElement : result.getContentElement().getChildren("track")) {
			tracks.add(Track.trackFromElement(domElement));
		}
		return tracks;
	}

}