/*
 * 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.Charsets;
import com.google.common.base.Preconditions;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.QueryStringDecoder;
import org.wso2.carbon.mss.HttpResponder;
import org.wso2.carbon.mss.internal.router.beanconversion.BeanConverter;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;

/**
 * This class is responsible for processing the HttpResourceModel
 * when a HTTP request arrives.
 */
public class HttpResourceModelProcessor {

    private final HttpResourceModel httpResourceModel;

    public HttpResourceModelProcessor(HttpResourceModel httpResourceModel) {
        this.httpResourceModel = httpResourceModel;
    }

    /**
     * Handle http Request.
     *
     * @param request     HttpRequest to be handled.
     * @param responder   HttpResponder to write the response.
     * @param groupValues Values needed for the invocation.
     */
    @SuppressWarnings("unchecked")
    public HttpMethodInfo buildHttpMethodInfo(HttpRequest request,
                                 HttpResponder responder,
                                 Map<String, String> groupValues,
                                 String contentType,
                                 List<String> acceptTypes)
            throws HandlerException {

        //TODO: Refactor group values.
        try {
            if (httpResourceModel.getHttpMethod().contains(request.getMethod())) {
                //Setup args for reflection call
                List<HttpResourceModel.ParameterInfo<?>> paramInfoList = httpResourceModel.getParamInfoList();
                List<String> producesMediaTypes = httpResourceModel.getProducesMediaTypes();
                Object[] args = new Object[paramInfoList.size()];
                String acceptType = "*/*";
                if (!producesMediaTypes.contains("*/*") && acceptTypes != null) {
                    acceptType =
                            (acceptTypes.contains("*/*")) ? producesMediaTypes.get(0) :
                                    producesMediaTypes.stream().filter(acceptTypes::contains).findFirst().get();
                }
                int idx = 0;
                for (HttpResourceModel.ParameterInfo<?> paramInfo : paramInfoList) {
                    if (paramInfo.getAnnotation() != null) {
                        Class<? extends Annotation> annotationType = paramInfo.getAnnotation().annotationType();
                        if (PathParam.class.isAssignableFrom(annotationType)) {
                            args[idx] = getPathParamValue((HttpResourceModel.ParameterInfo<String>) paramInfo,
                                    groupValues);
                        } else if (QueryParam.class.isAssignableFrom(annotationType)) {
                            args[idx] = getQueryParamValue((HttpResourceModel.ParameterInfo<List<String>>) paramInfo,
                                    request.getUri());
                        } else if (HeaderParam.class.isAssignableFrom(annotationType)) {
                            args[idx] = getHeaderParamValue((HttpResourceModel.ParameterInfo<List<String>>) paramInfo,
                                    request);
                        } else if (Context.class.isAssignableFrom(annotationType)) {
                            args[idx] = getContextParamValue((HttpResourceModel.ParameterInfo<Object>) paramInfo,
                                    request, responder);
                        }
                    } else if (request instanceof FullHttpRequest) {
                        // If an annotation is not present the parameter is considered a
                        // request body data parameter
                        String content = ((FullHttpRequest) request).content().toString(Charsets.UTF_8);
                        Type paramType = paramInfo.getParameterType();
                        args[idx] = BeanConverter.instance((contentType != null) ? contentType : "*/*")
                                .toObject(content, paramType);
                    }
                    idx++;
                }

                return new HttpMethodInfo(httpResourceModel.getMethod(),
                        httpResourceModel.getHttpHandler(),
                        request, responder,
                        args,
                        httpResourceModel.getExceptionHandler(),
                        acceptType);
            } else {
                //Found a matching resource but could not find the right HttpMethod so return 405
                throw new HandlerException(HttpResponseStatus.METHOD_NOT_ALLOWED, String.format
                        ("Problem accessing: %s. Reason: Method Not Allowed", request.getUri()));
            }
        } catch (Throwable e) {
            throw new HandlerException(HttpResponseStatus.INTERNAL_SERVER_ERROR,
                    String.format("Error in executing request: %s %s", request.getMethod(),
                            request.getUri()), e);
        }
    }


    @SuppressWarnings("unchecked")
    private Object getContextParamValue(HttpResourceModel.ParameterInfo<Object> paramInfo, HttpRequest request,
                                        HttpResponder responder) {
        Type paramType = paramInfo.getParameterType();
        Object value = null;
        if (((Class) paramType).isAssignableFrom(HttpRequest.class)) {
            value = request;
        } else if (((Class) paramType).isAssignableFrom(HttpResponder.class)) {
            value = responder;
        }
        Preconditions.checkArgument(value != null, "Could not resolve parameter %s", paramType.getTypeName());
        return value;
    }

    @SuppressWarnings("unchecked")
    private Object getPathParamValue(HttpResourceModel.ParameterInfo<String> info, Map<String, String> groupValues) {
        PathParam pathParam = info.getAnnotation();
        String value = groupValues.get(pathParam.value());
        Preconditions.checkArgument(value != null, "Could not resolve value for parameter %s", pathParam.value());
        if (value == null) {
            String defaultVal = info.getDefaultVal();
            if (defaultVal != null) {
                value = defaultVal;
            }
        }
        return info.convert(value);
    }

    @SuppressWarnings("unchecked")
    private Object getQueryParamValue(HttpResourceModel.ParameterInfo<List<String>> info, String uri) {
        QueryParam queryParam = info.getAnnotation();
        List<String> values = new QueryStringDecoder(uri).parameters().get(queryParam.value());
        if (values == null || values.isEmpty()) {
            String defaultVal = info.getDefaultVal();
            if (defaultVal != null) {
                values = Arrays.asList(defaultVal);
            }
        }
        return info.convert(values);
    }

    @SuppressWarnings("unchecked")
    private Object getHeaderParamValue(HttpResourceModel.ParameterInfo<List<String>> info, HttpRequest request) {
        HeaderParam headerParam = info.getAnnotation();
        String headerName = headerParam.value();
        List<String> headers = request.headers().getAll(headerName);
        if (headers == null || headers.isEmpty()) {
            String defaultVal = info.getDefaultVal();
            if (defaultVal != null) {
                headers = Arrays.asList(defaultVal);
            }
        }
        return info.convert(headers);
    }
}

@@ -1038,24 +1038,66 @@
 pResponder;%0A
+import org.wso2.carbon.mss.HttpStreaming;%0A
 import org.w
@@ -1629,16 +1629,57 @@
 ceModel;
+%0A    private HttpStreaming httpStreaming;
 %0A%0A    pu
@@ -6613,16 +6613,238 @@
 ponder;%0A
+        %7Delse if (((Class) paramType).isAssignableFrom(HttpStreaming.class)) %7B%0A            if (httpStreaming == null) %7B%0A                httpStreaming = new HttpStreaming();%0A            %7D%0A            value = httpStreaming;%0A
         

Support injecting HttpStreaming instance via @Context