package com.faforever.api.config.elide;

import com.faforever.api.data.DataController;
import com.faforever.api.data.checks.IsAuthenticated;
import com.faforever.api.data.checks.IsClanLeader;
import com.faforever.api.data.checks.IsClanMembershipDeletable;
import com.faforever.api.data.checks.IsLoginOwner;
import com.faforever.api.data.checks.IsReviewOwner;
import com.faforever.api.data.checks.permission.HasBanRead;
import com.faforever.api.data.checks.permission.HasBanUpdate;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.elide.Elide;
import com.yahoo.elide.ElideSettingsBuilder;
import com.yahoo.elide.audit.Slf4jLogger;
import com.yahoo.elide.core.EntityDictionary;
import com.yahoo.elide.core.filter.dialect.RSQLFilterDialect;
import com.yahoo.elide.datastores.hibernate5.HibernateStore;
import com.yahoo.elide.datastores.hibernate5.HibernateStore.Builder;
import com.yahoo.elide.jsonapi.JsonApiMapper;
import com.yahoo.elide.security.checks.Check;
import org.hibernate.SessionFactory;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.AbstractCacheResolver;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerMapping;

import javax.persistence.EntityManagerFactory;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;

@Configuration
public class ElideConfig {

  @Bean
  public Elide elide(EntityManagerFactory entityManagerFactory, ObjectMapper objectMapper, EntityDictionary entityDictionary) {
    RSQLFilterDialect rsqlFilterDialect = new RSQLFilterDialect(entityDictionary);

    HibernateStore hibernateStore = new Builder(entityManagerFactory.unwrap(SessionFactory.class)).build();

    return new Elide(new ElideSettingsBuilder(hibernateStore)
        .withJsonApiMapper(new JsonApiMapper(entityDictionary, objectMapper))
        .withAuditLogger(new Slf4jLogger())
        .withEntityDictionary(entityDictionary)
        .withJoinFilterDialect(rsqlFilterDialect)
        .withSubqueryFilterDialect(rsqlFilterDialect)
        .build());
  }

  @Bean
  public EntityDictionary entityDictionary() {
    ConcurrentHashMap<String, Class<? extends Check>> checks = new ConcurrentHashMap<>();
    checks.put(IsAuthenticated.EXPRESSION, IsAuthenticated.Inline.class);
    checks.put(IsLoginOwner.EXPRESSION, IsLoginOwner.Inline.class);
    checks.put(IsReviewOwner.EXPRESSION, IsReviewOwner.Inline.class);
    checks.put(IsClanLeader.EXPRESSION, IsClanLeader.Inline.class);
    checks.put(IsClanMembershipDeletable.EXPRESSION, IsClanMembershipDeletable.Inline.class);
    checks.put(HasBanRead.EXPRESSION, HasBanRead.Inline.class);
    checks.put(HasBanUpdate.EXPRESSION, HasBanUpdate.Inline.class);

    return new EntityDictionary(checks);
  }

  /**
   * Returns a cache resolver that resolves cache names by JSON API type names. For instance, the type "map" will be
   * resolved to a cache named "map".
   */
  @Bean
  public CacheResolver elideCacheResolver(CacheManager cacheManager) {
    return new AbstractCacheResolver(cacheManager) {
      @Override
      protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
        String jsonApiPath = getDataApiPath((HttpServletRequest) context.getArgs()[1]);
        String type = jsonApiPath.split("/")[0];

        if (!cacheManager.getCacheNames().contains(type)) {
          return Collections.singletonList("default");
        }

        return Collections.singletonList(type);
      }
    };
  }

  private String getDataApiPath(HttpServletRequest request) {
    return ((String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE))
        .replace(DataController.PATH_PREFIX, "");
  }
}

@@ -973,16 +973,161 @@
 .Check;%0A
+import com.yahoo.elide.utils.coerce.CoerceUtil;%0Aimport org.apache.commons.beanutils.ConvertUtils;%0Aimport org.apache.commons.beanutils.Converter;%0A
 import o
@@ -1151,24 +1151,24 @@
 ionFactory;%0A
-
 import org.s
@@ -1670,16 +1670,75 @@
 equest;%0A
+import java.time.Instant;%0Aimport java.time.OffsetDateTime;%0A
 import j
@@ -2204,24 +2204,61 @@
 ).build();%0A%0A
+    registerAdditionalConverters();%0A%0A
     return n
@@ -2309,26 +2309,24 @@
 tore)%0A      
-  
 .withJsonApi
@@ -2385,26 +2385,24 @@
 per))%0A      
-  
 .withAuditLo
@@ -2425,18 +2425,16 @@
 gger())%0A
-  
       .w
@@ -2473,26 +2473,24 @@
 nary)%0A      
-  
 .withJoinFil
@@ -2515,26 +2515,24 @@
 terDialect)%0A
-  
       .withS
@@ -2581,18 +2581,16 @@
     
-  
 .build()
 );%0A 
@@ -2585,16 +2585,674 @@
 .build()
+);%0A  %7D%0A%0A  /**%0A   * See https://github.com/yahoo/elide/issues/428.%0A   */%0A  private void registerAdditionalConverters() %7B%0A    CoerceUtil.coerce(%22%22, String.class);%0A    ConvertUtils.register(new Converter() %7B%0A      @Override%0A      @SuppressWarnings(%22unchecked%22)%0A      public %3CT%3E T convert(Class%3CT%3E type, Object value) %7B%0A        return (T) OffsetDateTime.parse(String.valueOf(value));%0A      %7D%0A    %7D, OffsetDateTime.class);%0A    ConvertUtils.register(new Converter() %7B%0A      @Override%0A      @SuppressWarnings(%22unchecked%22)%0A      public %3CT%3E T convert(Class%3CT%3E type, Object value) %7B%0A        return (T) Instant.parse(String.valueOf(value));%0A      %7D%0A    %7D, Instant.class
 );%0A  %7D%0A%0A
@@ -4850,18 +4850,16 @@
 IBUTE))%0A
-  
       .r

Allow querying for Instant and OffsetDateTime