package ua.net.tokar.json.rainbowrest;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

public class RainbowRestBatchFilter extends RainbowRestOncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger( RainbowRestBatchFilter.class );

    private static final String BATCH_ENDPOINT_URI_PARAM_NAME = "batchEndpointUri";
    private static final String BATCH_ENDPOINT_METHOD = "POST";
    private static final String DEFAULT_BATCH_ENDPOINT_URI = "/batch";

    private String batchEndpointUri = DEFAULT_BATCH_ENDPOINT_URI;

    private ObjectMapper mapper = new ObjectMapper();


    public RainbowRestBatchFilter() {
    }

    public RainbowRestBatchFilter( String batchEndpointUri ) {
        if ( StringUtils.isNotEmpty( batchEndpointUri ) ) {
            this.batchEndpointUri = batchEndpointUri;
        }
    }

    public RainbowRestBatchFilter(
            ExecutorService executorService,
            int executionTimeoutSeconds,
            String batchEndpointUri
    ) {
        super( executorService, executionTimeoutSeconds );
        this.batchEndpointUri = batchEndpointUri;
    }

    public RainbowRestBatchFilter(
            HttpClient httpClient,
            String batchEndpointUri
    ) {
        super( httpClient );
        this.batchEndpointUri = batchEndpointUri;
    }


    public RainbowRestBatchFilter(
            ExecutorService executorService,
            int executionTimeoutSeconds,
            HttpClient httpClient,
            String batchEndpointUri
    ) {
        super( executorService, executionTimeoutSeconds, httpClient );
        this.batchEndpointUri = batchEndpointUri;
    }

    @Override
    public void init( FilterConfig filterConfig ) throws ServletException {
        super.init( filterConfig );

        String batchEndpointUriOverride = filterConfig.getInitParameter(
                BATCH_ENDPOINT_URI_PARAM_NAME
        );
        if ( StringUtils.isNotEmpty( batchEndpointUriOverride ) ) {
            batchEndpointUri = batchEndpointUriOverride;
        }
    }

    @Override
    protected void doFilterInternal(
            ServletRequest request,
            ServletResponse response,
            FilterChain filterChain
    ) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        if ( !httpServletRequest.getMethod().equalsIgnoreCase( BATCH_ENDPOINT_METHOD )
                || !httpServletRequest.getRequestURI().equalsIgnoreCase( batchEndpointUri ) ) {
            doFilter( request, response, filterChain );
        } else {
            Map<String, String> map = mapper.readValue(
                    request.getInputStream(),
                    new TypeReference<Map<String, String>>() {
                    }
            );

            final ObjectNode tree = mapper.createObjectNode();
            List<HttpHeader> headers = getHeaders( (HttpServletRequest) request );

            List<Callable<JsonNodeResult>> callables =
                    map.entrySet()
                       .stream()
                       .map( nameToUrl -> (Callable<JsonNodeResult>) ( () -> {
                           String relativeUrl = nameToUrl.getValue();
                           JsonNode jsonNode = null;
                           try {
                               jsonNode = mapper.readTree(
                                       getResponseViaInternalDispatching(
                                               buildUri(
                                                       request,
                                                       relativeUrl,
                                                       Collections.emptyList()
                                               ),
                                               headers
                                       )
                               );
                           } catch ( IOException e ) {
                               // TODO provide error message
                           } catch ( URISyntaxException e ) {
                               log.warn( "Cannot build URI for relativeUrl='{}'", relativeUrl );
                           }
                           return new JsonNodeResult( jsonNode, nameToUrl.getKey() );
                       } ) )
                       .collect( Collectors.toList() );

            executeInParallel( callables ).forEach( result -> tree.set(
                    result.getFieldName(),
                    result.getJsonNode()
            ) );
            response.setContentType( "application/json" );
            response.getWriter().write( tree.toString() );
        }
    }

    private static class JsonNodeResult {
        private JsonNode jsonNode;
        private String fieldName;

        public JsonNodeResult( JsonNode jsonNode, String fieldName ) {
            this.jsonNode = jsonNode;
            this.fieldName = fieldName;
        }

        public JsonNode getJsonNode() {
            return jsonNode;
        }

        public String getFieldName() {
            return fieldName;
        }
    }
}

@@ -411,24 +411,71 @@
 letRequest;%0A
+import javax.servlet.http.HttpServletResponse;%0A
 import java.
@@ -482,32 +482,32 @@
 io.IOException;%0A
-
 import java.net.
@@ -601,16 +601,42 @@
 il.Map;%0A
+import java.util.Objects;%0A
 import j
@@ -1207,16 +1207,50 @@
 INT_URI;
+%0A    private Integer maxBatchSize;
 %0A%0A    pr
@@ -2203,32 +2203,66 @@
 batchEndpointUri
+,%0A            Integer maxBatchSize
 %0A    ) %7B%0A       
@@ -2367,32 +2367,74 @@
 tchEndpointUri;%0A
+        this.maxBatchSize = maxBatchSize;%0A
     %7D%0A%0A    @Over
@@ -3562,24 +3562,428 @@
         );%0A%0A
+            if ( !isValidBatchSize( map ) ) %7B%0A                sendError(%0A                        response,%0A                        HttpServletResponse.SC_BAD_REQUEST,%0A                        String.format(%0A                                %22Incorrect batch size. Max size=%25d%22,%0A                                maxBatchSize%0A                        )%0A                );%0A                return;%0A            %7D%0A%0A
             
@@ -6253,32 +6253,32 @@
 turn fieldName;%0A
-
         %7D%0A    %7D%0A
@@ -6276,11 +6276,438 @@
  %7D%0A    %7D
+%0A%0A    private void sendError( ServletResponse response, int responseCode, String message )%0A            throws IOException %7B%0A        log.warn( message );%0A        ( (HttpServletResponse) response ).sendError(%0A                responseCode,%0A                message%0A        );%0A    %7D%0A%0A    private boolean isValidBatchSize( Map%3CString, String%3E map ) %7B%0A        return Objects.isNull( maxBatchSize ) %7C%7C map.size() %3C= maxBatchSize;%0A    %7D
 %0A%7D%0A

Add limitation of maximum batch size (#33)