/**
 * 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.cxf.javascript;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;

import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.feature.Feature;
import org.apache.cxf.service.model.SchemaInfo;
import org.apache.cxf.service.model.ServiceInfo;
import org.apache.ws.commons.schema.XmlSchemaComplexType;
import org.apache.ws.commons.schema.utils.NamespacePrefixList;

/**
 * Generate JavaScript names for QNames.
 * This might belong on the CXF bus.
 */
public class BasicNameManager implements NameManager {

    private Map<String, String> nsPrefixMap;

    /**
     * For unit testing, we allow a very meaningless version.
     * Real uses use the factory methods.
     */
    BasicNameManager() {
    }

    /**
     * Factory that just takes a service. Used in tools and unit tests.
     * @param service
     * @return
     */
    public static BasicNameManager newNameManager(ServiceInfo service) {
        BasicNameManager nameManager = new BasicNameManager();
        nameManager.initialize(service, null);
        return nameManager;
    }

    /**
     *
     * @param service
     * @param endpoint
     * @return
     */
    public static BasicNameManager newNameManager(ServiceInfo service, Endpoint endpoint) {
        BasicNameManager nameManager = new BasicNameManager();
        nameManager.initialize(service, endpoint);
        return nameManager;
    }

    private void initialize(ServiceInfo service, Endpoint endpoint) {
        nsPrefixMap = new HashMap<>();
        if (endpoint != null) {
            JavascriptOptionsFeature options = getOptions(endpoint);
            if (options.getNamespacePrefixMap() != null) {
                nsPrefixMap.putAll(options.getNamespacePrefixMap());
            }
        }

        Set<String> poorPrefixURIs = new HashSet<>();
        for (SchemaInfo schemaInfo : service.getSchemas()) {
            NamespacePrefixList schemaPrefixList = schemaInfo.getSchema().getNamespaceContext();
            for (String declaredPrefix : schemaPrefixList.getDeclaredPrefixes()) {
                String uri = schemaPrefixList.getNamespaceURI(declaredPrefix);

                if (!nsPrefixMap.containsKey(uri)) { // first schema to define a prefix wins.
                    if (declaredPrefix.startsWith("ns") || "tns".equals(declaredPrefix)) {
                        poorPrefixURIs.add(uri);
                    } else {
                        nsPrefixMap.put(uri, declaredPrefix.toUpperCase());
                    }
                }
            }
        }

        for (String uri : poorPrefixURIs) {
            defineFallbackPrefix(uri);
        }
    }

    private JavascriptOptionsFeature getOptions(Endpoint endpoint) {
        if (endpoint != null) {
            for (Feature feature : endpoint.getActiveFeatures()) {
                if (feature instanceof JavascriptOptionsFeature) {
                    return (JavascriptOptionsFeature) feature;
                }
            }
        }
        return new JavascriptOptionsFeature(); // save work and return a default set of options.
    }

    /**
     * Take a URI and turn it into a JavaScript name prefix.
     * @param uri input URI.
     * @return output prefix.
     */
    protected String transformURI(String uri) {
        return uri.replaceAll("http:/*", "").replace("uri:", "").replaceAll("[\\.:/-]", "_");
    }

    private String defineFallbackPrefix(String uri) {
        // this needs more work later. We are bound to annoy someone somehow in this area.
        String jsPrefix = transformURI(uri);
        nsPrefixMap.put(uri, jsPrefix);
        return jsPrefix;
    }

    /** {@inheritDoc}*/
    public String getJavascriptName(XmlSchemaComplexType schemaType) {
        QName typeQName = schemaType.getQName();
        return getJavascriptName(typeQName);
    }

    public String getJavascriptName(QName qname) {
        String nsprefix = nsPrefixMap.get(qname.getNamespaceURI());
        // nsprefix will be null if there is no prefix.
        if (nsprefix == null) {
            nsprefix = defineFallbackPrefix(qname.getNamespaceURI());
        }
        return nsprefix
               + "_"
               + qname.getLocalPart();
    }
}
