/*
 * Copyright (c) 2013, 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.identity.application.authentication.framework.model;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 * This class is used as a wrapper to the incoming http request to authentication framework.
 * On the arrival of a request to authentication endpoint. The request will be wrapped from this
 * wrapper with all the information in the AuthenticationRequestCache
 */
public class AuthenticationFrameworkWrapper extends HttpServletRequestWrapper {

    private static final Log log = LogFactory.getLog(AuthenticationFrameworkWrapper.class);
    // Map which keeps parameters from authentication request cache
    private final Map<String, String[]> modifiableParameters;
    // This map keeps headers which are appended from cache entry
    private final Map<String, String> modifiableHeaders;
    // This map will contain all the parameters including cache params and original req params
    private Map<String, String[]> allParameters = null;

    /**
     * Create a new request wrapper that will merge additional parameters into
     * the request object without prematurely reading parameters from the
     * original request.
     *
     * @param request           HttpServletRequest
     * @param additionalParams  All Query Params
     * @param additionalHeaders All Headers
     */
    public AuthenticationFrameworkWrapper(final HttpServletRequest request,
                                          final Map<String, String[]> additionalParams,
                                          final Map<String, String> additionalHeaders) {
        super(request);
        modifiableParameters = new TreeMap<String, String[]>();
        modifiableParameters.putAll(additionalParams);
        modifiableHeaders = new TreeMap<String, String>();
        modifiableHeaders.putAll(additionalHeaders);
    }

    @Override
    public String getParameter(final String name) {
        String[] strings = getParameterMap().get(name);
        if (strings != null) {
            return strings[0];
        }
        return super.getParameter(name);
    }

    @Override
    public String getHeader(String name) {
        String header = super.getHeader(name);
        return (header != null) ? header : modifiableHeaders.get(name);
    }

    /**
     * Will return header names which were in original request and will append
     * all the header names which were in authentication request cache entry
     */
    @Override
    public Enumeration<String> getHeaderNames() {
        List<String> list = new ArrayList<String>();
        for (Enumeration<String> headerNames = super.getHeaderNames(); headerNames.
                hasMoreElements(); ) {
            list.add(headerNames.nextElement());
        }
        for (String keys : modifiableHeaders.keySet()) {
            list.add(keys);
        }
        return Collections.enumeration(list);
    }

    /**
     * Will return params which were in original request and will append
     * all the params which were in authentication request cache entry
     */
    @Override
    public Map<String, String[]> getParameterMap() {
        if (allParameters == null) {
            allParameters = new TreeMap<String, String[]>();
            allParameters.putAll(super.getParameterMap());
            allParameters.putAll(modifiableParameters);
        }
        //Return an unmodifiable collection because we need to uphold the interface contract.
        return Collections.unmodifiableMap(allParameters);
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return Collections.enumeration(getParameterMap().keySet());
    }

    @Override
    public String[] getParameterValues(final String name) {
        return getParameterMap().get(name);
    }

    /**
     * Will construct the new query parameter with the params
     */
    @Override
    public String getQueryString() {

        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String[]> entry : getParameterMap().entrySet()) {
            if (sb.length() > 0) {
                sb.append('&');
            }
            try {
                sb.append(URLEncoder.encode(entry.getKey(), "UTF-8"))
                        .append('=')
                        .append(URLEncoder.encode(entry.getValue()[0], "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error("Error while encoding query string built using entry key : " + entry.
                        getKey() + " and value : " + entry.getValue()[0], e);
            }
        }
        return sb.toString();
    }

    /**
     * Adds a header to the wrapper object
     *
     * @param key    Key of the header
     * @param values Value of the header
     */
    public void addHeader(String key, String values) {
        modifiableHeaders.put(key, values);
    }
}
