/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.waveprotocol.box.server.waveserver;

import com.google.common.base.Function;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import org.waveprotocol.box.server.util.WaveletDataUtil;
import org.waveprotocol.wave.model.id.IdConstants;
import org.waveprotocol.wave.model.id.IdUtil;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.ParticipantIdUtil;
import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
import org.waveprotocol.wave.model.wave.data.ReadableWaveletData;
import org.waveprotocol.wave.model.wave.data.WaveViewData;
import org.waveprotocol.wave.model.wave.data.impl.WaveViewDataImpl;
import org.waveprotocol.wave.util.logging.Log;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;

/**
 * Base implementation of search provider.
 *
 * @author yurize@apache.org (Yuri Zelikov)
 */
public abstract class AbstractSearchProviderImpl implements SearchProvider {
  private static final Log LOG = Log.get(AbstractSearchProviderImpl.class);

  protected final WaveDigester digester;
  protected final ParticipantId sharedDomainParticipantId;
  private final WaveMap waveMap;

  public AbstractSearchProviderImpl(final String waveDomain, WaveDigester digester, WaveMap waveMap) {
    this.digester = digester;
    sharedDomainParticipantId = ParticipantIdUtil.makeUnsafeSharedDomainParticipantId(waveDomain);
    this.waveMap = waveMap;
  }

  protected List<WaveViewData> computeSearchResult(final ParticipantId user, int startAt,
      int numResults, List<WaveViewData> results) {
    int searchResultSize = results.size();
    // Check if we have enough results to return.
    if (searchResultSize < startAt) {
      return Collections.emptyList();
    } else {
      int endAt = Math.min(startAt + numResults, searchResultSize);
      return Lists.newArrayList(results.subList(startAt, endAt));
    }
  }

  // TODO (yurize) : Refactor this method. It does two things: filtering and
  // building waves.
  protected LinkedHashMap<WaveId, WaveViewData> filterWavesViewBySearchCriteria(
      Function<ReadableWaveletData, Boolean> matchesFunction,
      LinkedHashMultimap<WaveId, WaveletId> currentUserWavesView) {
    // Must use a map with stable ordering, since indices are meaningful.
    LinkedHashMap<WaveId, WaveViewData> results = Maps.newLinkedHashMap();

    // Loop over the user waves view.
    for (WaveId waveId : currentUserWavesView.keySet()) {
      Set<WaveletId> waveletIds = currentUserWavesView.get(waveId);
      WaveViewData view = buildWaveViewData(waveId, waveletIds, matchesFunction, waveMap);
      Iterable<? extends ObservableWaveletData> wavelets = view.getWavelets();
      boolean hasConversation = false;
      for (ObservableWaveletData waveletData : wavelets) {
        if (IdUtil.isConversationalId(waveletData.getWaveletId())) {
          hasConversation = true;
          break;
        }
      }
      if ((view != null) && hasConversation) {
        results.put(waveId, view);
      }
    }
    return results;
  }

  public static WaveViewData buildWaveViewData(WaveId waveId, Set<WaveletId> waveletIds,
      Function<ReadableWaveletData, Boolean> matchesFunction, WaveMap waveMap) {

    WaveViewData view = WaveViewDataImpl.create(waveId); // Copy of the wave built up for search hits.
    for (WaveletId waveletId : waveletIds) {
      WaveletContainer waveletContainer = null;
      WaveletName waveletname = WaveletName.of(waveId, waveletId);

      // TODO (Yuri Z.) This loop collects all the wavelets that match the
      // query, so the view is determined by the query. Instead we should
      // look at the user's wave view and determine if the view matches the
      // query.
      try {
        waveletContainer = waveMap.getWavelet(waveletname);
        if ((waveletContainer == null) || IdUtil.isUserDataWavelet(waveletId)
            || !waveletContainer.applyFunction(matchesFunction)) {
          continue;
        }
        // Just keep adding all the relevant wavelets in this wave.
        view.addWavelet(waveletContainer.copyWaveletData());
      } catch (WaveletStateException e) {
        LOG.warning("Failed to access wavelet " + waveletContainer.getWaveletName(), e);
      }
    }
    return view;
  }

  /**
   * Verifies whether the wavelet matches the filter criteria.
   *
   * @param wavelet the wavelet.
   * @param user the logged in user.
   * @param sharedDomainParticipantId the shared domain participant id.
   * @param isAllQuery true if the search results should include shared for this
   *        domain waves.
   */
  protected boolean isWaveletMatchesCriteria(ReadableWaveletData wavelet, ParticipantId user,
      ParticipantId sharedDomainParticipantId, boolean isAllQuery)
          throws WaveletStateException {
    // If it is user data wavelet for the user - return true.
    if (IdUtil.isUserDataWavelet(wavelet.getWaveletId()) && wavelet.getCreator().equals(user)) {
      return true;
    }
    // The wavelet should have logged in user as participant for 'in:inbox'
    // query.
    if (!isAllQuery && !wavelet.getParticipants().contains(user)) {
      return false;
    }
    // Or if it is an 'all' query - then either logged in user or shared domain
    // participant should be present in the wave.
    if (isAllQuery
        && !WaveletDataUtil.checkAccessPermission(wavelet, user, sharedDomainParticipantId)) {
      return false;
    }
    // If not returned 'false' above - then logged in user is either
    // explicit or implicit participant and therefore has access permission.
    return true;
  }
  
  /**
   * Ensures that each wave in the current waves view has the user data wavelet by always adding
   *  it to the view.
   */
  protected void ensureWavesHaveUserDataWavelet(
      LinkedHashMultimap<WaveId, WaveletId> currentUserWavesView, ParticipantId user) {
    WaveletId udw =
        WaveletId.of(user.getDomain(),
            IdUtil.join(IdConstants.USER_DATA_WAVELET_PREFIX, user.getAddress()));
    Set<WaveId> waveIds = currentUserWavesView.keySet();
    for (WaveId waveId : waveIds) {
      Set<WaveletId> waveletIds = currentUserWavesView.get(waveId);
      waveletIds.add(udw);
    }
  }
}

@@ -4557,24 +4557,25 @@
 aveletId);%0A%0A
+%0A
       // TOD
@@ -4920,59 +4920,8 @@
 ) %7C%7C
- IdUtil.isUserDataWavelet(waveletId)%0A            %7C%7C
  !wa

Revert "Fixes tests"