/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF 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.apache.axis2.jaxws.handler;

import org.apache.axis2.i18n.Messages;
import org.apache.axis2.jaxws.ExceptionFactory;
import org.apache.axis2.jaxws.core.MessageContext;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * The JAX-WS exposes transport properties whose value is Map<String, List<String>>.  The
 * String is the content-id and DataHandler is the data handler representing the TransportHeaders.
 * 
 * The JAX-WS MessageContext stores transport properties in an Map object located on the AXIS2
 * MessageContext.
 * 
 * This class, TransportHeadersAdapter, is an adapter between the Map<String, List<String>> 
 * interface needed by the properties and the actual implementation.  
 * All useful function is delegated through the MessageContext, 
 * so that we only have one copy of the information.  
 * 
 * To use this class, invoke the install method.  This will create an TransportHeadersAdapter 
 * (if necessary) and install it on the property JAX-WS standard TransportHeaders properties.  
 * (See BaseMessageContext.)
 */
public class TransportHeadersAdapter implements Map {

    private static final Log log = LogFactory.getLog(TransportHeadersAdapter.class);

    MessageContext mc; // MessageContext which provides the backing implementation
    String propertyName; // The name of the JAX-WS property

    /**
     * @param mc
     * @param propertyName
     */
    private TransportHeadersAdapter(MessageContext mc, String propertyName) {
        this.mc = mc;
        this.propertyName = propertyName;
    }

    /**
     * Add the TransportHeadersAdapter as the property for TransportHeaders
     * @param mc MessageContext
     */
    public static void install(MessageContext mc) {

        boolean isRequest = (mc.getMEPContext().getRequestMessageContext() == mc);

        // The property is either a request or response
        String propertyName =
                (isRequest) ? javax.xml.ws.handler.MessageContext.HTTP_REQUEST_HEADERS
                        : javax.xml.ws.handler.MessageContext.HTTP_RESPONSE_HEADERS;


        if (log.isDebugEnabled()) {
            log.debug("Installing TransportHeadersAdapter for " + propertyName);
        }

        // See if there is an existing map
        Object map = mc.getProperty(propertyName);

        // Reuse existing TransportHeadersAdapter
        if (map instanceof TransportHeadersAdapter) {
            if (log.isDebugEnabled()) {
                log.debug("An TransportHeadersAdapter is already installed.  " +
                                "Reusing the existing one.");
            }
            return;
        }

        // Create a new TransportHeadersAdapter and set it on the property 
        TransportHeadersAdapter tha = new TransportHeadersAdapter(mc, propertyName);

        if (map != null) {
            if (log.isDebugEnabled()) {
                log.debug("The TransportHeaders in the existing map (" + propertyName
                        + ") are copied to the TransportHeadersAdapter.");
            }
            // Copy the existing Map contents to this new adapter
            tha.putAll((Map) map);
        }
        mc.setProperty(propertyName, tha);
        
        // If this is a response, then also set the property for the response code
        if (!isRequest) {
            Object value = mc.getProperty(HTTPConstants.MC_HTTP_STATUS_CODE);
            mc.setProperty(javax.xml.ws.handler.MessageContext.HTTP_RESPONSE_CODE, value);
        }
    }

    /**
     * Get/Create the implementation map from the Axis2 properties
     * @param mc
     * @return Map
     */
    private static Map getDelegateMap(MessageContext mc) {
        // Get the axis2 Map
        Map map = (Map) mc.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
        if (map == null) {
            map = new HashMap();
            mc.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, map);
        }
        return map;
    }

    /**
     * Convert intput into a List
     * @param o
     * @return List
     */
    private static List<String> convertToList(Object o) {
        if (o == null) {
            return null;
        } else if (o instanceof List) {
            return (List) o;
        } else if (o instanceof String) {
            List l = new ArrayList();
            l.add(o);
            return l;
        } else {
            throw ExceptionFactory.makeWebServiceException(
            		Messages.getMessage("inputConvertionErr",o.getClass().toString()));
        }
    }

    private static String convertToString(Object o) {
        if (o == null) {
            return null;
        } else if (o instanceof String) {
            return (String) o;
        } else if (o instanceof List) {

            List l = (List) o;
            if (l.size() == 0) {
                return null;
            } else if (l.size() == 1) {
                return (String) l.get(0);
            }
        }
        throw ExceptionFactory.makeWebServiceException(
        		Messages.getMessage("inputConvertionErr1",o.getClass().toString()));
    }


    public int size() {
        return getDelegateMap(mc).size();
    }

    public boolean isEmpty() {
        return getDelegateMap(mc).isEmpty();
    }

    public boolean containsKey(Object key) {
        return getDelegateMap(mc).containsKey(key);
    }

    public boolean containsValue(Object value) {
        // TODO Should walk all of the values to do this correctly.
        String valueString = convertToString(value);
        return getDelegateMap(mc).containsValue(valueString);
    }

    public Object get(Object key) {
        Object value = convertToList(getDelegateMap(mc).get(key));
        if (log.isDebugEnabled()) {
            log.debug("get(" + key + ") returns value=" + value);
        }
        return value;
    }

    public Object put(Object key, Object value) {
        if (log.isDebugEnabled()) {
            log.debug("put(" + key + " , " + value + ")");
        }
        String valueString = convertToString(value);
        return convertToList(getDelegateMap(mc).put(key, valueString));
    }

    public Object remove(Object key) {
        return convertToList(getDelegateMap(mc).remove(key));
    }

    public void putAll(Map t) {
        for (Object key : t.keySet()) {
            Object value = t.get(key);
            if (log.isDebugEnabled()) {
                log.debug("put via putAll (" + key + " , " + value + ")");
            }
            put(key, value);
        }
    }

    public void clear() {
        getDelegateMap(mc).clear();
    }

    public Set keySet() {
        return getDelegateMap(mc).keySet();
    }

    public Collection values() {
        return copy().values();
    }

    public Set entrySet() {
        return copy().entrySet();
    }
    
    private Map copy() {
        Map tempMap = new HashMap<String, List<String>>();
        for (Object key : keySet()) {
            List<String> value = (List<String>)get(key);
            tempMap.put(key, value);
        }
        return tempMap;
    }
    
    public String toString() {
        return "TransportHeadersAdapter: " + getDelegateMap(mc).toString();        
    }
}
