/**
 * Ozwillo Kernel
 * Copyright (C) 2015  The Ozwillo Kernel Authors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package oasis.web.applications;

import java.net.URI;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;

import com.google.common.base.Strings;
import com.google.common.collect.Streams;
import com.ibm.icu.util.ULocale;

import oasis.model.accounts.AccountRepository;
import oasis.model.applications.v2.CatalogEntry;
import oasis.model.applications.v2.CatalogEntryRepository;
import oasis.model.applications.v2.ImmutableCatalogEntryRepository;
import oasis.model.applications.v2.SimpleCatalogEntry;
import oasis.model.authn.AccessToken;
import oasis.web.authn.OAuth;
import oasis.web.authn.OAuthPrincipal;

public abstract class AbstractMarketSearchEndpoint {
  @Inject CatalogEntryRepository catalogEntryRepository;
  @Inject AccountRepository accountRepository;

  @Context SecurityContext context;

  @GET
  @OAuth
  @Produces(MediaType.APPLICATION_JSON)
  public Response get(
      @Nullable @QueryParam("hl") ULocale locale,
      @DefaultValue("0") @QueryParam("start") int start,
      @DefaultValue("25") @QueryParam("limit") int limit,
      @Nullable @QueryParam("q") String query,
      @Nullable @QueryParam("supported_locale") List<ULocale> supported_locale,
      @Nullable @QueryParam("geographical_areas") Set<URI> geographical_areas,
      @Nullable @QueryParam("restricted_areas") Set<URI> restricted_areas,
      @Nullable @QueryParam("target_audience") Set<CatalogEntry.TargetAudience> target_audience,
      @Nullable @QueryParam("payment_option") Set<CatalogEntry.PaymentOption> payment_option,
      @Nullable @QueryParam("category_id") Set<String> category_id,
      @Nullable @QueryParam("portal") String portal
  ) {
    return post(locale, start, limit, query, supported_locale, geographical_areas, restricted_areas, target_audience, payment_option, category_id, portal);
  }

  @POST
  @OAuth
  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  @Produces(MediaType.APPLICATION_JSON)
  public Response post(
      @Nullable @FormParam("hl") ULocale locale,
      @DefaultValue("0") @FormParam("start") int start,
      @DefaultValue("25") @FormParam("limit") int limit,
      @Nullable @FormParam("q") String query,
      @Nullable @FormParam("supported_locale") List<ULocale> supported_locale,
      @Nullable @FormParam("geographical_area") Set<URI> geographical_area,
      @Nullable @FormParam("restricted_area") Set<URI> restricted_area,
      @Nullable @FormParam("target_audience") Set<CatalogEntry.TargetAudience> target_audience,
      @Nullable @FormParam("payment_option") Set<CatalogEntry.PaymentOption> payment_option,
      @Nullable @FormParam("category_id") Set<String> category_id,
      @Nullable @FormParam("portal") String portal
  ) {
    final @Nullable AccessToken accessToken;

    if (context.getUserPrincipal() != null) {
      accessToken = ((OAuthPrincipal) context.getUserPrincipal()).getAccessToken();
      if (locale == null) {
        locale = accountRepository.getUserAccountById(accessToken.getAccountId()).getLocale();
      }
    } else {
      accessToken = null;
    }

    String trimmedQuery = query;
    if (query != null) {
      trimmedQuery = Strings.emptyToNull(query.trim());
    }

    ImmutableCatalogEntryRepository.SearchRequest.Builder requestBuilder = ImmutableCatalogEntryRepository.SearchRequest.builder()
        .displayLocale(locale)
        .start(start)
        .limit(limit)
        .query(trimmedQuery)
        .addAllSupported_locale(preProcess(supported_locale))
        .addAllGeographical_area(preProcess(geographical_area))
        .addAllRestricted_area(preProcess(restricted_area))
        .addAllTarget_audience(preProcess(target_audience))
        .addAllPayment_option(preProcess(payment_option))
        .addAllCategory_id(preProcessStr(category_id))
        .portal(getPortal(accessToken, portal));
    // TODO: add information about apps the user has already "bought" (XXX: limit to accessToken.isPortal! to avoid leaking data)
    Iterator<SimpleCatalogEntry> results = catalogEntryRepository.search(requestBuilder.build()).iterator();
    if (accessToken == null || !accessToken.isPortal()) {
      // hide 'portals' to non-portal clients
      // XXX: should this endpoint only be accessible to portals?
      results = Streams.stream(results)
          .peek(entry -> entry.setPortals(null))
          .iterator();
    }
    return Response.ok()
        .entity(new GenericEntity<Iterator<SimpleCatalogEntry>>(results) {})
        .build();
  }

  @Nullable protected abstract String getPortal(@Nullable AccessToken accessToken, @Nullable String portal);

  private Iterable<String> preProcessStr(@Nullable Collection<String> strings) {
    return preProcessToStream(strings)
        .filter(s -> !s.isEmpty())
        ::iterator;
  }

  private <T> Iterable<T> preProcess(@Nullable Collection<T> objects) {
    return preProcessToStream(objects)::iterator;
  }

  private <T> Stream<T> preProcessToStream(@Nullable Collection<T> objects) {
    if (objects == null) {
      return Stream.empty();
    }
    return objects.stream().filter(Objects::nonNull);
  }
}

@@ -920,16 +920,52 @@
 il.Set;%0A
+import java.util.stream.Collectors;%0A
 import j
@@ -5266,27 +5266,27 @@
 a)%0A    Itera
-tor
+ble
 %3CSimpleCatal
@@ -5357,27 +5357,16 @@
 build())
-.iterator()
 ;%0A    if
@@ -5632,17 +5632,18 @@
     
-.
+::
 iterator
 ();%0A
@@ -5638,18 +5638,16 @@
 iterator
-()
 ;%0A    %7D%0A
@@ -5710,19 +5710,19 @@
 ty%3CItera
-tor
+ble
 %3CSimpleC

Fix (again!) market search endpoint