package org.schabi.newpipe.extractor.services.soundcloud.extractors;

import com.grack.nanojson.JsonArray;
import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;

import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
import org.schabi.newpipe.extractor.utils.Utils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

import javax.annotation.Nonnull;

import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;

public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
    private static final int STREAMS_PER_REQUESTED_PAGE = 15;

    private String playlistId;
    private JsonObject playlist;

    public SoundcloudPlaylistExtractor(final StreamingService service,
                                       final ListLinkHandler linkHandler) {
        super(service, linkHandler);
    }

    @Override
    public void onFetchPage(@Nonnull final Downloader downloader) throws IOException,
            ExtractionException {

        playlistId = getLinkHandler().getId();
        final String apiUrl = SOUNDCLOUD_API_V2_URL + "playlists/" + playlistId + "?client_id="
                + SoundcloudParsingHelper.clientId() + "&representation=compact";

        final String response = downloader.get(apiUrl, getExtractorLocalization()).responseBody();
        try {
            playlist = JsonParser.object().from(response);
        } catch (final JsonParserException e) {
            throw new ParsingException("Could not parse json response", e);
        }
    }

    @Nonnull
    @Override
    public String getId() {
        return playlistId;
    }

    @Nonnull
    @Override
    public String getName() {
        return playlist.getString("title");
    }

    @Nonnull
    @Override
    public String getThumbnailUrl() {
        String artworkUrl = playlist.getString("artwork_url");

        if (artworkUrl == null) {
            // If the thumbnail is null, traverse the items list and get a valid one,
            // if it also fails, return null
            try {
                final InfoItemsPage<StreamInfoItem> infoItems = getInitialPage();

                for (final StreamInfoItem item : infoItems.getItems()) {
                    artworkUrl = item.getThumbnailUrl();
                    if (!isNullOrEmpty(artworkUrl)) {
                        break;
                    }
                }
            } catch (final Exception ignored) {
            }

            if (artworkUrl == null) {
                return EMPTY_STRING;
            }
        }

        return artworkUrl.replace("large.jpg", "crop.jpg");
    }

    @Override
    public String getUploaderUrl() {
        return SoundcloudParsingHelper.getUploaderUrl(playlist);
    }

    @Override
    public String getUploaderName() {
        return SoundcloudParsingHelper.getUploaderName(playlist);
    }

    @Override
    public String getUploaderAvatarUrl() {
        return SoundcloudParsingHelper.getAvatarUrl(playlist);
    }

    @Override
    public boolean isUploaderVerified() throws ParsingException {
        return playlist.getObject("user").getBoolean("verified");
    }

    @Override
    public long getStreamCount() {
        return playlist.getLong("track_count");
    }

    @Nonnull
    @Override
    public InfoItemsPage<StreamInfoItem> getInitialPage() {
        final StreamInfoItemsCollector streamInfoItemsCollector =
                new StreamInfoItemsCollector(getServiceId());
        final List<String> ids = new ArrayList<>();

        playlist.getArray("tracks")
                .stream()
                .filter(JsonObject.class::isInstance)
                .map(JsonObject.class::cast)
                .forEachOrdered(track -> {
                    // i.e. if full info is available
                    if (track.has("title")) {
                        streamInfoItemsCollector.commit(
                                new SoundcloudStreamInfoItemExtractor(track));
                    } else {
                        // %09d would be enough, but a 0 before the number does not create
                        // problems, so let's be sure
                        ids.add(String.valueOf(track.getLong("id")));
                    }
                });

        // extract using getPage if no tracks were collected and playlist is not empty
        if (streamInfoItemsCollector.getItems().isEmpty() && !ids.isEmpty()) {
            try {
                return getPage(new Page(ids));
            } catch (final IOException | ExtractionException e) {
                return new InfoItemsPage<>(streamInfoItemsCollector, null);
            }
        }

        return new InfoItemsPage<>(streamInfoItemsCollector, new Page(ids));
    }

    @Override
    public InfoItemsPage<StreamInfoItem> getPage(final Page page) throws IOException,
            ExtractionException {
        if (page == null || isNullOrEmpty(page.getIds())) {
            throw new IllegalArgumentException("Page doesn't contain IDs");
        }

        final List<String> currentIds;
        final List<String> nextIds;
        if (page.getIds().size() <= STREAMS_PER_REQUESTED_PAGE) {
            // Fetch every remaining stream, there are less than the max
            currentIds = page.getIds();
            nextIds = null;
        } else {
            currentIds = page.getIds().subList(0, STREAMS_PER_REQUESTED_PAGE);
            nextIds = page.getIds().subList(STREAMS_PER_REQUESTED_PAGE, page.getIds().size());
        }

        final String currentPageUrl = SOUNDCLOUD_API_V2_URL + "tracks?client_id="
                + SoundcloudParsingHelper.clientId() + "&ids=" + Utils.join(",", currentIds);

        final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
        final String response = NewPipe.getDownloader().get(currentPageUrl,
                getExtractorLocalization()).responseBody();

        try {
            final JsonArray tracks = JsonParser.array().from(response);
            // Response may not contain tracks in the same order as currentIds.
            // The streams are displayed in the order which is used in currentIds on SoundCloud.
            final HashMap<Long, JsonObject> idToTrack = new HashMap<>();
            for (final Object track : tracks) {
                if (track instanceof JsonObject) {
                    final JsonObject o = (JsonObject) track;
                    idToTrack.put(o.getLong("id"), o);
                }
            }
            for (final String strId : currentIds) {
                final long id = Long.parseLong(strId);
                try {
                    collector.commit(new SoundcloudStreamInfoItemExtractor(
                            Objects.requireNonNull(
                                    idToTrack.get(id),
                            "no track with id " + id + " in response"
                            )
                    ));
                } catch (final NullPointerException e) {
                    throw new ParsingException("Could not parse json response", e);
                }
            }
        } catch (final JsonParserException e) {
            throw new ParsingException("Could not parse json response", e);
        }

        return new InfoItemsPage<>(collector, new Page(nextIds));
    }
}
