package com.pr0gramm.app.feed;

import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.pr0gramm.app.Settings;
import com.pr0gramm.app.Stats;
import com.pr0gramm.app.api.categories.ExtraCategoryApi;
import com.pr0gramm.app.api.categories.ExtraCategoryApiProvider;
import com.pr0gramm.app.api.pr0gramm.Api;
import com.pr0gramm.app.services.Track;
import com.pr0gramm.app.services.config.ConfigService;

import org.immutables.value.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Set;

import javax.inject.Inject;
import javax.inject.Singleton;

import rx.Observable;

/**
 * Performs the actual request to get the items for a feed.
 */
@Singleton
public class FeedService {
    private static final Logger logger = LoggerFactory.getLogger("FeedService");

    private final Api mainApi;
    private final ExtraCategoryApi categoryApi;
    private final Settings settings;
    private final ConfigService configService;

    @Inject
    public FeedService(Api mainApi, ExtraCategoryApiProvider categoryApi, Settings settings,
                       ConfigService configService) {

        this.mainApi = mainApi;
        this.categoryApi = categoryApi.get();
        this.settings = settings;
        this.configService = configService;
    }

    public Observable<Api.Feed> getFeedItems(FeedQuery query) {
        FeedFilter feedFilter = query.feedFilter();

        // filter by feed-type
        Integer promoted = (feedFilter.getFeedType() == FeedType.PROMOTED) ? 1 : null;
        Integer following = (feedFilter.getFeedType() == FeedType.PREMIUM) ? 1 : null;

        int flags = ContentType.combine(query.contentTypes());
        String tags = feedFilter.getTags().orNull();
        String user = feedFilter.getUsername().orNull();

        // FIXME this is quite hacky right now.
        String likes = feedFilter.getLikes().orNull();
        Boolean self = Strings.isNullOrEmpty(likes) ? null : true;

        FeedType feedType = feedFilter.getFeedType();

        // statistics
        Stats.get().incrementCounter("feed.loaded", "type:" + feedType.name().toLowerCase());

        switch (feedType) {
            case RANDOM:
                return categoryApi.random(tags, flags);

            case BESTOF:
                int benisScore = settings.bestOfBenisThreshold();
                return categoryApi.bestof(tags, user, flags, query.older().orNull(), benisScore);

            case CONTROVERSIAL:
                return categoryApi.controversial(tags, flags, query.older().orNull());

            case TEXT:
                return categoryApi.text(tags, flags, query.older().orNull());

            default:
                // prepare the call to the official api. The call is only made on subscription.
                Observable<Api.Feed> officialCall = mainApi.itemsGet(promoted, following,
                        query.older().orNull(), query.newer().orNull(), query.around().orNull(),
                        flags, tags, likes, self, user);

                if (likes == null && configService.config().searchUsingTagService()) {
                    return categoryApi
                            .general(promoted, tags, user, flags, query.older().orNull(), query.newer().orNull(), query.around().orNull())
                            .onErrorResumeNext(officialCall);

                } else if (!query.around().isPresent() && !query.newer().isPresent()) {
                    if (tags != null && tags.startsWith("?")) {
                        // track the advanced search
                        Track.advancedSearch(tags.substring(1));

                        logger.info("Using general search api, but falling back on old one in case of an error.");
                        return categoryApi
                                .general(promoted, tags.substring(1), user, flags, query.older().orNull(), query.newer().orNull(), query.around().orNull())
                                .onErrorResumeNext(officialCall);
                    }
                }

                return officialCall;
        }
    }

    public Observable<Api.Post> loadPostDetails(long id) {
        return mainApi.info(id);
    }

    @Value.Immutable
    public interface FeedQuery {
        FeedFilter feedFilter();

        Set<ContentType> contentTypes();

        Optional<Long> newer();

        Optional<Long> older();

        Optional<Long> around();
    }
}

@@ -25,16 +25,61 @@
 .feed;%0A%0A
+import android.support.annotation.Nullable;%0A%0A
 import c
@@ -2184,24 +2184,106 @@
 erCase());%0A%0A
+        // get extended tag query%0A        SearchQuery q = new SearchQuery(tags);%0A%0A
         swit
@@ -2365,16 +2365,18 @@
 .random(
+q.
 tags, fl
@@ -2515,16 +2515,18 @@
 .bestof(
+q.
 tags, us
@@ -2655,16 +2655,18 @@
 versial(
+q.
 tags, fl
@@ -2759,16 +2759,18 @@
 pi.text(
+q.
 tags, fl
@@ -3135,16 +3135,18 @@
  flags, 
+q.
 tags, li
@@ -3333,24 +3333,26 @@
 l(promoted, 
+q.
 tags, user, 
@@ -3349,32 +3349,68 @@
 gs, user, flags,
+%0A                                   
  query.older().o
@@ -3433,32 +3433,68 @@
 ewer().orNull(),
+%0A                                   
  query.around().
@@ -3495,32 +3495,33 @@
 und().orNull())%0A
+%0A
                 
@@ -3683,44 +3683,18 @@
 if (
-tags != null && tags.startsWith(%22?%22)
+q.advanced
 ) %7B%0A
@@ -3795,29 +3795,16 @@
 rch(tags
-.substring(1)
 );%0A%0A    
@@ -4016,21 +4016,8 @@
 tags
-.substring(1)
 , us
@@ -4594,11 +4594,467 @@
 );%0A    %7D
+%0A%0A    private static class SearchQuery %7B%0A        final boolean advanced;%0A%0A        @Nullable%0A        final String tags;%0A%0A        SearchQuery(@Nullable String tags) %7B%0A            if (tags == null %7C%7C !tags.trim().startsWith(%22?%22)) %7B%0A                this.advanced = false;%0A                this.tags = tags;%0A            %7D else %7B%0A                this.advanced = true;%0A                this.tags = tags.replaceFirst(%22%5C%5Cs*%5C%5C?%5C%5Cs*%22, %22%22);%0A            %7D%0A        %7D%0A    %7D
 %0A%7D%0A

Improve extended search in random/etc category