/*
 *
 *  Copyright 2016 the original author or authors.
 *
 *  Licensed 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 springfox.documentation.spring.data.rest;

import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceType;
import org.springframework.data.rest.webmvc.BasePathAwareHandlerMapping;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
import org.springframework.data.rest.webmvc.RestMediaTypes;
import org.springframework.data.rest.webmvc.support.JpaHelper;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import springfox.documentation.RequestHandler;
import springfox.documentation.spi.service.RequestHandlerProvider;
import springfox.documentation.spring.web.WebMvcRequestHandler;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.google.common.base.Predicates.*;
import static com.google.common.collect.Lists.*;
import static org.springframework.http.MediaType.*;
import static springfox.documentation.spring.data.rest.EntityServices.*;

@Component
class EntityServicesProvider implements RequestHandlerProvider {
  private final ResourceMappings mappings;
  private final Repositories repositories;
  private final RepositoryRestHandlerMapping restMappings;
  private final BasePathAwareHandlerMapping basePathAwareMappings;
  private final TypeResolver typeResolver;

  @Autowired
  EntityServicesProvider(
      ResourceMappings mappings,
      JpaHelper jpaHelper,
      RepositoryRestConfiguration repositoryConfiguration,
      ApplicationContext applicationContext,
      Repositories repositories,
      TypeResolver typeResolver) {
    this.mappings = mappings;
    this.repositories = repositories;
    this.typeResolver = typeResolver;
    this.restMappings = new RepositoryRestHandlerMapping(mappings, repositoryConfiguration);
    restMappings.setJpaHelper(jpaHelper);
    restMappings.setApplicationContext(applicationContext);
    restMappings.afterPropertiesSet();

    basePathAwareMappings = new BasePathAwareHandlerMapping(repositoryConfiguration);
    basePathAwareMappings.setApplicationContext(applicationContext);
    basePathAwareMappings.afterPropertiesSet();
  }


  @Override
  public List<RequestHandler> requestHandlers() {
    ArrayList<RequestHandler> requestHandlers = newArrayList();
    FluentIterable<Map.Entry<RequestMappingInfo, HandlerMethod>> entries = FluentIterable.from(allEntries());
    for (Map.Entry<RequestMappingInfo, HandlerMethod> each : entries.filter(not(restDataServices()))) {
      requestHandlers.add(new WebMvcRequestHandler(each.getKey(), each.getValue()));
    }
    for (Map.Entry<RequestMappingInfo, HandlerMethod> each : entries.filter(repositories())) {
      requestHandlers.add(new WebMvcRequestHandler(each.getKey(), each.getValue()));
    }
    for (Map.Entry<RequestMappingInfo, HandlerMethod> each : entries.filter(entityServices())) {
      EntityRequestTemplate entityRequestTemplate
          = new EntityRequestTemplate(typeResolver, repositories, mappings, each.getKey(), each.getValue());
      requestHandlers.addAll(entityRequestTemplate.operations());
    }
    List<RequestHandler> searchHandlers = newArrayList();
    for (Map.Entry<RequestMappingInfo, HandlerMethod> each : entries.filter(entitySearchServices())) {
      EntitySearchRequestTemplate entityRequestHandlers
          = new EntitySearchRequestTemplate(typeResolver, mappings, each.getKey(), each.getValue());
      searchHandlers.addAll(entityRequestHandlers.operations());
    }
    requestHandlers.addAll(maybeCombine(searchHandlers, compactHandlers()));

    List<RequestHandler> metadataHandlers = newArrayList();
    for (Map.Entry<RequestMappingInfo, HandlerMethod> each : basePathAwareMappings.getHandlerMethods().entrySet()) {
      if (entitySchemaService().apply(each)
          || alpsProfileServices().apply(each)) {
        metadataHandlers.addAll(
            new EntitySchemaTemplate(
                typeResolver,
                mappings,
                each.getKey(),
                each.getValue()).operations());
      } else {
        requestHandlers.add(new WebMvcRequestHandler(each.getKey(), each.getValue()));
      }
    }
    requestHandlers.addAll(maybeCombine(metadataHandlers, supportsAlps()));
    requestHandlers.addAll(FluentIterable.from(metadataHandlers).filter(optionMethods()).toList());
    return requestHandlers;
  }

  private Predicate<RequestHandler> optionMethods() {
    return new Predicate<RequestHandler>() {
      @Override
      public boolean apply(RequestHandler input) {
        return input.supportedMethods().contains(RequestMethod.OPTIONS);
      }
    };
  }

  private Collection<RequestHandler> maybeCombine(
      List<RequestHandler> metadataHandlers,
      Predicate <RequestHandler> selector) {
    List<RequestHandler> combined = newArrayList();
    Iterable<RequestHandler> selected = FluentIterable.from(metadataHandlers)
        .filter(and(selector, getHandler()));
    FluentIterable<RequestHandler> selectedCompliment = FluentIterable.from(metadataHandlers)
        .filter(and(not(selector), getHandler()));
    for (RequestHandler each : selected) {
      Optional<RequestHandler> found = selectedCompliment.firstMatch(samePathMapping(each.getPatternsCondition()));
      combined.add(combine(each, found));
    }
    combined.addAll(FluentIterable.from(metadataHandlers)
        .filter(EntitySearchRequestHandler.class)
        .filter(collectionHandlers()).toList());
    return combined;
  }


  private Predicate<EntitySearchRequestHandler> collectionHandlers() {
    return new Predicate<EntitySearchRequestHandler>() {
      @Override
      public boolean apply(EntitySearchRequestHandler input) {
        return input.resourceType() == ResourceType.COLLECTION;
      }
    };
  }

  private RequestHandler combine(RequestHandler source, Optional<RequestHandler> other) {
    if (other.isPresent()) {
      return other.get().combine(source);
    }
    return source;
  }

  private Predicate<RequestHandler> samePathMapping(final PatternsRequestCondition pathMappings) {
    return new Predicate<RequestHandler>() {
      @Override
      public boolean apply(RequestHandler input) {
        return input.getPatternsCondition().equals(pathMappings);
      }
    };
  }

  private Predicate<RequestHandler> compactHandlers() {
    return new Predicate<RequestHandler>() {
      @Override
      public boolean apply(RequestHandler input) {
        return input.key().getProducibleMediaTypes().contains(valueOf("application/x-spring-data-compact+json"));
      }
    };
  }

  private Predicate<RequestHandler> supportsAlps() {
    return new Predicate<RequestHandler>() {
      @Override
      public boolean apply(RequestHandler input) {
        return input.key().getSupportedMediaTypes().contains(RestMediaTypes.ALPS_JSON);
      }
    };
  }

  private Predicate<RequestHandler> getHandler() {
    return new Predicate<RequestHandler>() {
      @Override
      public boolean apply(RequestHandler input) {
        return input.key().getSupportedMethods().contains(RequestMethod.GET);
      }
    };
  }

  private Set<Map.Entry<RequestMappingInfo, HandlerMethod>> allEntries() {
    return restMappings.getHandlerMethods().entrySet();
  }
}

@@ -3157,24 +3157,38 @@
 onfiguration
+, repositories
 );%0A    restM

Fix for https://github.com/springfox/springfox/issues/1653