/*
 * Copyright (c) 2007-2009, Dennis M. Sosnoski All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 * disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 * following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of
 * JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.jibx.binding.model;

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

import org.jibx.util.InsertionOrderedSet;
import org.jibx.util.LazyList;

/**
 * External data for a binding definition. This tracks references to other bindings, along with the associated namespace
 * information.
 * 
 * @author Dennis M. Sosnoski
 */
public class BindingHolder
{
    /** Organizer managing this holder. */
    private final BindingOrganizer m_organizer;
    
    /** Namespace URI associated with this binding (<code>null</code> if no-namespace binding). */
    private final String m_namespace;
    
    /** Namespace used by default for elements flag. */
    private final boolean m_elementDefault;
    
    /** Set of namespaces referenced across bindings. */
    private final InsertionOrderedSet m_referencedNamespaces;
    
    /** Actual binding definition. */
    private BindingElement m_binding;
    
    /** Binding finalized flag. */
    private boolean m_finished;
    
    /** Name for file to be written from binding. */
    private String m_fileName;
    
    /** Binding name. */
    private String m_bindingName;
    
    /** List of mapping definitions in binding. */
    private final LazyList m_mappings;
    
    /**
     * Constructor.
     * 
     * @param uri (<code>null</code> if no-namespace binding)
     * @param dflt namespace is default for elements flag
     * @param dir directory managing this holder
     */
    public BindingHolder(String uri, boolean dflt, BindingOrganizer dir) {
        m_organizer = dir;
        m_namespace = uri;
        m_elementDefault = dflt;
        m_referencedNamespaces = new InsertionOrderedSet();
        if (uri != null) {
            m_referencedNamespaces.add(uri);
        }
        m_binding = new BindingElement();
        m_mappings = new LazyList();
    }
    
    /**
     * Get the binding organizer managing this holder.
     *
     * @return directory
     */
    public BindingOrganizer getOrganizer() {
        return m_organizer;
    }
    
    /**
     * Get namespace URI associated with this binding.
     * 
     * @return namespace (<code>null</code> if no-namespace)
     */
    public String getNamespace() {
        return m_namespace;
    }
    
    /**
     * Get default namespace URI for elements defined in this binding.
     * 
     * @return namespace (<code>null</code> if no-namespace)
     */
    public String getElementDefaultNamespace() {
        return m_elementDefault ? m_namespace : null;
    }
    
    /**
     * Get the binding element.
     * 
     * @return binding
     */
    public BindingElement getBinding() {
        return m_binding;
    }
    
    /**
     * Set the binding element. This method is provided so that the generated binding element can be replaced by one
     * which has been read in from a file after being written.
     * 
     * @param bind
     */
    public void setBinding(BindingElement bind) {
        m_binding = bind;
    }
    
    /**
     * Internal check method to verify that the binding is still modifiable.
     */
    private void checkModifiable() {
        if (m_finished) {
            throw new IllegalStateException("Internal error - attempt to modify binding after finalized");
        }
    }
    
    /**
     * Get the file name to be used for this file.
     * 
     * @return name (<code>null</code> if not set)
     */
    public String getFileName() {
        return m_fileName;
    }
    
    /**
     * Set the file name to be used for this file.
     * 
     * @param name
     */
    public void setFileName(String name) {
        m_fileName = name;
    }
    
    /**
     * Get binding name.
     *
     * @return name
     */
    public String getBindingName() {
        return m_bindingName;
    }

    /**
     * Set binding name.
     *
     * @param name
     */
    public void setBindingName(String name) {
        m_bindingName = name;
    }
    
    /**
     * Add a mapping definition to the binding.
     *
     * @param mapping
     */
    public void addMapping(MappingElementBase mapping) {
        checkModifiable();
        m_mappings.add(mapping);
    }
    
    /**
     * Get the number of mapping definitions present in this binding.
     *
     * @return count
     */
    public int getMappingCount() {
        return m_mappings.size();
    }
    
    /**
     * Add namespace required for this binding.
     * 
     * @param uri namespace for binding of referenced component
     */
    public void addNamespaceDependency(String uri) {
        if (uri != null && !"http://www.w3.org/XML/1998/namespace".equals(uri)) {
            checkModifiable();
            if (m_referencedNamespaces.add(uri)) {
                m_organizer.addNamespaceReference(uri);
            }
        }
    }
    
    /**
     * Finishes building the binding.
     *
     * @param formats format elements to be used in binding
     * @param includes include elements to be used in binding
     * @param dfltns binding namespace is the default namespace flag
     * @param nsset set of namespaces defined outside this binding
     * @param nsprefixmap map from namespace URI to prefix
     */
    /*default*/ void finish(Collection formats, Collection includes, boolean dfltns, Set nsset, Map nsprefixmap) {
        
        // make sure not already done
        checkModifiable();
        
        // add namespace element for the definitions in this binding
        List topchilds = m_binding.topChildren();
        if (m_namespace != null && (!nsset.contains(m_namespace) || m_elementDefault)) {
            NamespaceElement ns = new NamespaceElement();
            ns.setUri(m_namespace);
            if (m_elementDefault) {
                ns.setDefaultName("elements");
            }
            if (!dfltns) {
                String prefix = (String)nsprefixmap.get(m_namespace);
                if (prefix == null) {
                    prefix = "tns";
                }
                ns.setPrefix(prefix);
            }
            topchilds.add(ns);
        }
        
        // add all required namespace declarations to root element and namespace elements to binding
        ArrayList nss = m_referencedNamespaces.asList();
        for (int i = 0; i < nss.size(); i++) {
            String uri = (String)nss.get(i);
            String prefix = (String)nsprefixmap.get(uri);
            m_binding.addNamespaceDecl(prefix == null ? "tns" : prefix, uri);
            if (!nsset.contains(uri) && prefix != null) {
                NamespaceElement ns = new NamespaceElement();
                ns.setUri(uri);
                ns.setPrefix(prefix);
                topchilds.add(ns);
            }
        }
        
        // add formats, screening out any with no actual definitions to allow default handling of Java 5 enums
        for (Iterator iter = formats.iterator(); iter.hasNext();) {
            FormatElement format = (FormatElement)iter.next();
            if (format.getDefaultText() != null || format.getDeserializerName() != null ||
                format.getEnumValueName() != null || format.getSerializerName() != null) {
                topchilds.add(format);
            }
        }
        
        // follow formats with namespaces, includes, and accumulated mappings
        topchilds.addAll(includes);
        topchilds.addAll(m_mappings);
        m_finished = true;
    }
}