/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

package org.teiid.query.mapping.xml;

import java.util.ArrayList;
import java.util.List;

import org.teiid.core.TeiidRuntimeException;
import org.teiid.query.QueryPlugin;



/** 
 * This is base class to define all nodes except the attribute. However, this quite not
 * enough to define a Element node. Specially designed for sequence, choice and all node 
 * types
 */
public abstract class MappingBaseNode extends MappingNode {
    // An ID on the recursive parent as to who the recursive child node is?  
    String recursionId;
    
    protected MappingBaseNode() {
        // implicit constructor
    }
    
    public MappingBaseNode setMinOccurrs(int cardinality) {
        setProperty(MappingNodeConstants.Properties.CARDINALITY_MIN_BOUND, new Integer(cardinality));
        return this;
    }

    public MappingBaseNode setMaxOccurrs(int cardinality) {
        setProperty(MappingNodeConstants.Properties.CARDINALITY_MAX_BOUND, new Integer(cardinality));
        return this;
    }    
         
    public MappingBaseNode setSource(String source) {
        if (source != null) {
            setProperty(MappingNodeConstants.Properties.RESULT_SET_NAME, source);
        }
        else {
            removeProperty(MappingNodeConstants.Properties.RESULT_SET_NAME);
        }
        return this;
    }
         
    public String getSource() {
        return (String)getProperty(MappingNodeConstants.Properties.RESULT_SET_NAME);
    }     
    
    public MappingElement addChildElement(MappingElement elem) {
        if (elem.isRecursive()) {
            MappingRecursiveElement recursiveElement = (MappingRecursiveElement)elem;
            MappingBaseNode recursiveRoot = getRecursiveRootNode(recursiveElement);
            recursiveRoot.setRootRecursiveNode(true, recursiveElement.getMappingClass());
            addChild(elem);
        }
        else {
            addChild(elem);
        }
        return elem;
    }
    
    private MappingBaseNode getRecursiveRootNode(MappingRecursiveElement elem) {
        if (hasSource(elem.getMappingClass())) {
            return this;
        }
        MappingBaseNode parent = this.getParentNode();
        if (parent != null) {
            return parent.getRecursiveRootNode(elem);
        }
         throw new TeiidRuntimeException(QueryPlugin.Event.TEIID30457, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30457, elem));
    }
    
    /**
     * Any node with its parent node of Source Node, is like property on node itself, 
     * as all the source nodes will have atmost one child. 
     * @param source
     * @return
     */
    private boolean hasSource(String source) {
        return source.equals(getSource());
    }
    
    public MappingChoiceNode addChoiceNode(MappingChoiceNode elem) {
        addChild(elem);
        return elem;
    }
    
    public MappingSequenceNode addSequenceNode(MappingSequenceNode elem) {
        addChild(elem);
        return elem;
    }
    
    public MappingAllNode addAllNode(MappingAllNode elem) {
        addChild(elem);
        return elem;
    }     
    
    public MappingSourceNode addSourceNode(MappingSourceNode elem) {
        addChild(elem);
        return elem;
    }     
    
    public MappingCriteriaNode addCriteriaNode(MappingCriteriaNode node) {
        addChild(node);
        return node;        
    }
    
    public MappingBaseNode getParentNode() {
        if (getParent() instanceof MappingBaseNode) {
            return (MappingBaseNode)getParent();
        }
        return null;
    }
    
    public String getName() {
        // if we decide to give the choice/seq/all nodes names then
        // we need to change the logic in the "getFullName()"
        return null;
    }
    
    public void removeChildNode(MappingBaseNode toRemove) {
        getChildren().remove(toRemove);
    }
        
    public int getMinOccurence() {
        Integer occur = (Integer)getProperty(MappingNodeConstants.Properties.CARDINALITY_MIN_BOUND);
        if (occur != null) {
            return occur.intValue();
        }
        return 1;
    }
    
    public int getMaxOccurence() {
        Integer occur = (Integer)getProperty(MappingNodeConstants.Properties.CARDINALITY_MAX_BOUND);
        if (occur != null) {
            return occur.intValue();
        }
        return 1;
    }
                    
    /**
     * specify the element is a recursive root
     * @param root
     */
    void setRootRecursiveNode(boolean root, String recursionId) {
        setProperty(MappingNodeConstants.Properties.IS_RECURSIVE_ROOT, Boolean.valueOf(root));
        this.recursionId = recursionId;
    }
        
    public String getRecursionId() {
        return this.recursionId;
    }
    
    public boolean isRootRecursiveNode() {
        return Boolean.TRUE.equals(getProperty(MappingNodeConstants.Properties.IS_RECURSIVE_ROOT));        
    }
    
    /**
     * Get the document node of this node.
     * @return
     */
    public MappingDocument getDocument() {
        if (isDocumentNode()) {
            return (MappingDocument)this;
        }
        return getParentNode().getDocument();
    }
    
    public boolean isDocumentNode() {
        return false;
    }
    
    /**
     * A tag root node is the first visual node (Element to be specific) in the document tree
     * which is the root element in the output xml document. 
     * @return true if 
     */
    public boolean isTagRoot() {
        return false;
    }
    
    public List<String> getStagingTables() {
        return (List<String>)getProperty(MappingNodeConstants.Properties.TEMP_GROUP_NAMES);
    }
    
    public void setStagingTables(List<String> tables) {
        if (tables != null) {
            setProperty(MappingNodeConstants.Properties.TEMP_GROUP_NAMES, tables);
        }
        else {
            removeProperty(MappingNodeConstants.Properties.TEMP_GROUP_NAMES);
        }
    }
    
    public void addStagingTable(String tablename) {
        if (tablename == null) {
            return;
        }
        List<String> tables = getStagingTables();
        if (tables == null || tables.isEmpty()) {
            tables = new ArrayList<String>();
        }
        tables.add(tablename);
        setProperty(MappingNodeConstants.Properties.TEMP_GROUP_NAMES, tables);
    }  
    
    @Override
    public MappingNode clone() {
    	MappingBaseNode clone = (MappingBaseNode) super.clone();
    	List<String> staging = getStagingTables();
    	if (getStagingTables() != null && staging != MappingNodeConstants.Defaults.DEFAULT_VALUES.get(MappingNodeConstants.Properties.TEMP_GROUP_NAMES)) {
    		clone.setStagingTables(new ArrayList<String>(staging));
    	}
    	return clone;
    }
    
}
