/*
 *  Copyright (c) WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. 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.wso2.carbon.mss.internal.router;

import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMultimap;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.LastHttpContent;
import org.wso2.carbon.mss.HttpResponder;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * HttpMethodInfo is a helper class having state information about the http handler method to be invoked, the handler
 * and arguments required for invocation by the Dispatcher. RequestRouter populates this class and stores in its
 * context as attachment.
 */
class HttpMethodInfo {

    private final Method method;
    private final Object handler;
    private final boolean isChunkedRequest;
    private final ByteBuf requestContent;
    private final HttpRequest request;
    private final HttpResponder responder;
    private final Object[] args;
    private final boolean isStreaming;
    private final ExceptionHandler exceptionHandler;
    private final String mediaType;

    private BodyConsumer bodyConsumer;

    HttpMethodInfo(Method method, Object handler, HttpRequest request, HttpResponder responder, Object[] args,
                   ExceptionHandler exceptionHandler, String mediaType) {
        this.method = method;
        this.handler = handler;
        this.isChunkedRequest = request instanceof HttpContent;
        if (request instanceof HttpContent) {
            requestContent = ((HttpContent) request).content();
        } else {
            this.requestContent = null;
        }
        this.isStreaming = BodyConsumer.class.isAssignableFrom(method.getReturnType());
        this.request = rewriteRequest(request, isStreaming);
        this.responder = responder;
        this.exceptionHandler = exceptionHandler;
        this.mediaType = mediaType;

        // The actual arguments list to invoke handler method
        this.args = args;
    }

    /**
     * Calls the httpHandler method.
     */
    void invoke() throws Exception {
        if (isStreaming) {
            // Casting guarantee to be succeeded.
            bodyConsumer = (BodyConsumer) method.invoke(handler, args);
            if (bodyConsumer != null) {
                if (requestContent.isReadable()) {
                    bodyConsumerChunk(requestContent);
                }
                if (!isChunkedRequest) {
                    bodyConsumerFinish();
                }
            }
        } else {
            // Actually <T> would be void
            bodyConsumer = null;
            try {
                Object returnVal = method.invoke(handler, args);
                //sending return value as output
                new HttpMethodResponseHandler()
                        .setResponder(responder)
                        .setEntity(returnVal)
                        .setMediaType(mediaType)
                        .send();
            } catch (InvocationTargetException e) {
                exceptionHandler.handle(e.getTargetException(), request, responder);
            }
        }
    }

    void chunk(HttpContent chunk) throws Exception {
        if (bodyConsumer == null) {
            // If the handler method doesn't want to handle chunk request, the bodyConsumer will be null.
            // It applies to case when the handler method inspects the request and decides to decline it.
            // Usually the handler also closes the connection after declining the request.
            // However, depending on the closing time and the request,
            // there may be some chunk of data already sent by the client.
            return;
        }
        if (chunk instanceof LastHttpContent) {  //TODO: azeez
            bodyConsumerFinish();
        } else {
            bodyConsumerChunk(chunk.content());
        }
    }

    /**
     * Calls the {@link org.wso2.carbon.mss.internal.router.BodyConsumer#chunk(io.netty.buffer.ByteBuf,
     * org.wso2.carbon.mss.HttpResponder)} method.
     *
     * If the chunk method calls throws exception,
     * the {@link org.wso2.carbon.mss.internal.router.BodyConsumer#handleError(Throwable)} will be called and
     * this method will throw {@link org.wso2.carbon.mss.internal.router.HandlerException}.
     */
    private void bodyConsumerChunk(ByteBuf buffer) throws HandlerException {
        try {
            bodyConsumer.chunk(buffer, responder);
        } catch (Throwable t) {
            throw bodyConsumerError(t);
        }
    }

    /**
     * Calls {@link org.wso2.carbon.mss.internal.router.BodyConsumer#finished(org.wso2.carbon.mss.HttpResponder)}
     * method. The current bodyConsumer will be set to {@code null} after the call.
     */
    private void bodyConsumerFinish() {
        BodyConsumer consumer = bodyConsumer;
        bodyConsumer = null;
        consumer.finished(responder);
    }

    /**
     * Calls {@link org.wso2.carbon.mss.internal.router.BodyConsumer#handleError(Throwable)} and
     * throws {@link org.wso2.carbon.mss.internal.router.HandlerException}. The current bodyConsumer will be set
     * to {@code null} after the call.
     */
    private HandlerException bodyConsumerError(Throwable cause) throws HandlerException {
        BodyConsumer consumer = bodyConsumer;
        bodyConsumer = null;
        consumer.handleError(cause);

        throw new HandlerException(HttpResponseStatus.INTERNAL_SERVER_ERROR, "", cause);
    }

    /**
     * Sends the error to responder.
     */
    void sendError(HttpResponseStatus status, Throwable ex) {
        String msg;

        if (ex instanceof InvocationTargetException) {
            msg = String.format("Exception Encountered while processing request : %s",
                    Objects.firstNonNull(ex.getCause(), ex).getMessage());
        } else {
            msg = String.format("Exception Encountered while processing request: %s", ex.getMessage());
        }

        // Send the status and message, followed by closing of the connection.
        responder.sendString(status, msg, ImmutableMultimap.of(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE));
    }

    /**
     * Returns true if the handler method's return type is BodyConsumer.
     */
    boolean isStreaming() {
        return isStreaming;
    }

    private HttpRequest rewriteRequest(HttpRequest request, boolean isStreaming) {
        if (!isStreaming) {
            return request;
        }

        //TODO: Azeez handle chunks
    /*boolean isChunked = request.headers().contains(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED, true)
    if (!isChunked || request.content().readable()) {
      request.setChunked(true);
      request.setContent(Unpooled.EMPTY_BUFFER);
    }*/
        return request;
    }
}

@@ -2554,42 +2554,13 @@
 = re
-writeRequest(request, isStreaming)
+quest
 ;%0A  
@@ -6276,1308 +6276,136 @@
   * 
-Sends the error to responder.%0A     */%0A    void sendError(HttpResponseStatus status, Throwable ex) %7B%0A        String msg;%0A%0A        if (ex instanceof InvocationTargetException) %7B%0A            msg = String.format(%22Exception Encountered while processing request : %25s%22,%0A                    Objects.firstNonNull(ex.getCause(), ex).getMessage());%0A        %7D else %7B%0A            msg = String.format(%22Exception Encountered while processing request: %25s%22, ex.getMessage());%0A        %7D%0A%0A        // Send the status and message, followed by closing of the connection.%0A        responder.sendString(status, msg, ImmutableMultimap.of(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE));%0A    %7D%0A%0A    /**%0A     * Returns true if the handler method's return type is BodyConsumer.%0A     */%0A    boolean isStreaming() %7B%0A        return isStreaming;%0A    %7D%0A%0A    private HttpRequest rewriteRequest(HttpRequest request, boolean isStreaming) %7B%0A        if (!isStreaming) %7B%0A            return request;%0A        %7D%0A%0A        //TODO: Azeez handle chunks%0A    /*boolean isChunked = request.headers().contains(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED, true)%0A    if (!isChunked %7C%7C request.content().readable()) %7B%0A      request.setChunked(true);%0A      request.setContent(Unpooled.EMPTY_BUFFER);%0A    %7D*/%0A        return request
+Returns true if the handler method's return type is BodyConsumer.%0A     */%0A    boolean isStreaming() %7B%0A        return isStreaming
 ;%0A  

Remove unused methods