/*
 * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
 * with the License. A copy of the License is located at
 *
 * http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file 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 com.amazonaws.serverless.proxy.struts2;

import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.*;
import com.amazonaws.serverless.proxy.internal.servlet.*;
import com.amazonaws.serverless.proxy.internal.testutils.Timer;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;
import com.amazonaws.services.lambda.runtime.Context;
import org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import java.util.EnumSet;
import java.util.concurrent.CountDownLatch;

/**
 * A Lambda handler to initialize the Struts2 filter and proxy the requests.
 *
 * @param <RequestType>  request type
 * @param <ResponseType> response type
 */
public class Struts2LambdaContainerHandler<RequestType, ResponseType> extends AwsLambdaServletContainerHandler<RequestType, ResponseType, HttpServletRequest, AwsHttpServletResponse> {

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

    public static final String HEADER_STRUTS_STATUS_CODE = "X-Struts-StatusCode";

    private static final String TIMER_STRUTS_2_CONTAINER_CONSTRUCTOR = "STRUTS2_CONTAINER_CONSTRUCTOR";
    private static final String TIMER_STRUTS_2_HANDLE_REQUEST = "STRUTS2_HANDLE_REQUEST";
    private static final String TIMER_STRUTS_2_COLD_START_INIT = "STRUTS2_COLD_START_INIT";
    private static final String STRUTS_FILTER_NAME = "Struts2Filter";

    private boolean initialized;

    public static Struts2LambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> getAwsProxyHandler() {
        return new Struts2LambdaContainerHandler(
                AwsProxyRequest.class,
                AwsProxyResponse.class,
                new AwsProxyHttpServletRequestReader(),
                new AwsProxyHttpServletResponseWriter(),
                new AwsProxySecurityContextWriter(),
                new AwsProxyExceptionHandler());
    }

    public static Struts2LambdaContainerHandler<HttpApiV2ProxyRequest, AwsProxyResponse> getHttpApiV2ProxyHandler() {
        return new Struts2LambdaContainerHandler(
                HttpApiV2ProxyRequest.class,
                AwsProxyResponse.class,
                new AwsHttpApiV2HttpServletRequestReader(),
                new AwsProxyHttpServletResponseWriter(),
                new AwsHttpApiV2SecurityContextWriter(),
                new AwsProxyExceptionHandler());
    }

    public Struts2LambdaContainerHandler(Class<RequestType> requestTypeClass,
                                         Class<ResponseType> responseTypeClass,
                                         RequestReader<RequestType, HttpServletRequest> requestReader,
                                         ResponseWriter<AwsHttpServletResponse, ResponseType> responseWriter,
                                         SecurityContextWriter<RequestType> securityContextWriter,
                                         ExceptionHandler<ResponseType> exceptionHandler) {

        super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler);
        Timer.start(TIMER_STRUTS_2_CONTAINER_CONSTRUCTOR);
        this.initialized = false;
        Timer.stop(TIMER_STRUTS_2_CONTAINER_CONSTRUCTOR);
    }

    @Override
    protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) {
        return new AwsHttpServletResponse(request, latch);
    }

    @Override
    protected void handleRequest(HttpServletRequest httpServletRequest,
                                 AwsHttpServletResponse httpServletResponse,
                                 Context lambdaContext) throws Exception {
        Timer.start(TIMER_STRUTS_2_HANDLE_REQUEST);
        if (!this.initialized) {
            initialize();
        }

        if (AwsHttpServletRequest.class.isAssignableFrom(httpServletRequest.getClass())) {
            ((AwsHttpServletRequest)httpServletRequest).setServletContext(this.getServletContext());
        }
        this.doFilter(httpServletRequest, httpServletResponse, null);
        String responseStatusCode = httpServletResponse.getHeader(HEADER_STRUTS_STATUS_CODE);
        if (responseStatusCode != null) {
            httpServletResponse.setStatus(Integer.parseInt(responseStatusCode));
        }
        Timer.stop(TIMER_STRUTS_2_HANDLE_REQUEST);
    }

    @Override
    public void initialize() throws ContainerInitializationException {
        log.info("Initialize Struts2 Lambda Application ...");
        Timer.start(TIMER_STRUTS_2_COLD_START_INIT);
        try {
            if (this.startupHandler != null) {
                this.startupHandler.onStartup(this.getServletContext());
            }
            StrutsPrepareAndExecuteFilter filter = new StrutsPrepareAndExecuteFilter();
            FilterRegistration.Dynamic filterRegistration = this.getServletContext()
                    .addFilter(STRUTS_FILTER_NAME, filter);
            filterRegistration.addMappingForUrlPatterns(
                    EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.INCLUDE, DispatcherType.FORWARD),
                    true, "/*");
        } catch (Exception e) {
            throw new ContainerInitializationException("Could not initialize Struts2", e);
        }

        this.initialized = true;
        Timer.stop(TIMER_STRUTS_2_COLD_START_INIT);
        log.info("... initialize of Struts2 Lambda Application completed!");
    }

    public Servlet getServlet() {
        return null;
    }
}