/*
 * 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.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */

package org.teiid.query.processor.xml;

import static org.teiid.query.analysis.AnalysisRecord.*;

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

import org.teiid.client.plan.PlanNode;
import org.teiid.common.buffer.BlockedException;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.logging.LogManager;


/**
 * <p>This instruction holds a List of Criteria, and for each Criteria there is a
 * {@link Program} that will be executed.  Therefore, this ProcessorInstruction
 * implements an arbitrarily deep if-else if-....else block.</p>
 * 
 * <p>During processing, the Criteria are iterated through, in order.  When one 
 * evaluates to true, that Criteria's Program is placed on to the 
 * Program {@link ProcessorEnvironment#getProgramStack stack}, and the loop
 * is broken - this simulates the if-else if-... behavior.  An optional
 * "else" or "default" Program can be added via the 
 * {@link #addDefaultChoiceSubProgram addDefaultChoiceSubProgram} 
 * method - this is the optional "else"
 * block at the end of an if-else if-... construct.</p>
 */
public class IfInstruction extends ProcessorInstruction {

    private List thenBlocks;
    private DefaultCondition defaultCondition;

    // State if condition evaluation blocked
    private int conditionIndex = 0;

    /**
     * Constructor for IfInstruction.
     * @param endIf see {@link #getEndIf}
     */
    public IfInstruction() {
        super();
        this.thenBlocks = new ArrayList();
    }
    
    public void addCondition(Condition condition) { 
        thenBlocks.add(condition);
    }

    public void setDefaultCondition(DefaultCondition defaultCondition) { 
        this.defaultCondition = defaultCondition;
    }

    /**
     * Return the number of "if/else if" clauses and 
     * corresponding "then" sub programs in this If Instruction.
     * (Does not include the optional 
     * {@link #getElseProgram "else" sub program}).
     * @return number of "then" sub programs
     */
    int getThenCount(){
        return this.thenBlocks.size();
    }

    /**
     * Return the Criteria object representing the "if" or
     * "else if" condition, at the indicated "then" index.
     * Use {@link #getThenCount getThenCount} method to get the
     * number of then sub programs.
     * @param thenCount index into the ordered list of "then" sub programs
     * @return Criteria for the sub program, or null if it is an invalid index
     */
    Condition getThenCondition(int thenCount){
        if (thenCount >=0 && thenCount < this.thenBlocks.size()){
            return (Condition)this.thenBlocks.get(thenCount);
        }
        return null;
    }

    /**
     * Return the sub Program object representing a "then" clause
     * of this "if then else if..." block, at the indicated "then" index.
     * Use {@link #getThenCount getThenCount} method to get the
     * number of then sub programs.
     * @param thenCount index into the ordered list of "then" sub programs
     * @return the "thenCount"th "then" sub program, or null if it is an invalid index
     */
    Program getThenProgram(int thenCount){
        if (thenCount >=0 && thenCount < this.thenBlocks.size()){
            Condition condition = (Condition)this.thenBlocks.get(thenCount);
            return condition.getThenProgram();
        }
        return null;
    }    
    
    /**
     * Returns the optional "else" sub program, which has no criteria
     * but is executed if no other "if/else if" condition evaluates to
     * true.
     * @return else Program, may be null
     */
    Program getElseProgram(){
        return this.defaultCondition.getThenProgram();
    }
  
    /**
     * This instruction will evaluate it's criteria, one by one.  If any evaluate
     * to true, it will push the corresponding sub-program on to the top of the
     * program stack, and break from the loop.  Regardless if whether any criteria
     * evaluate to true, this instruction will increment the program counter of the
     * current program.
     * @see ProcessorInstruction#process(ProcessorEnvironment)
     */
    public XMLContext process(XMLProcessorEnvironment env, XMLContext context)
        throws BlockedException, TeiidComponentException, TeiidProcessingException{

        List thens = this.thenBlocks;
        if (this.defaultCondition != null){
	        //add default choice to the end of the List - this will be
	        //the "else" at the end of the "if-then-else-if..." structure,
	        //and its rsNames will be null

            //the original List must not be modified, so make a copy
            thens = new ArrayList(thenBlocks);
	        thens.add(defaultCondition);
        }
        
        Condition condition = null;
        boolean foundTrueCondition = false;

        for(; conditionIndex < thens.size(); conditionIndex++){            
            condition = (Condition)thens.get(conditionIndex);
                             
            // evaluate may block if criteria evaluation blocks
             if(condition.evaluate(env, context)) {
                 foundTrueCondition = true;
                 //break from the loop; only the first "then" Program
                 //whose criteria evaluates to true will be executed 
                 break;
             }
            
        }
        conditionIndex = 0;

        // This IF instruction should be processed exactly once, so the 
        // program containing the IF instruction needs to have it's
        // counter incremented.
        // NOTE: At this point, if any of the conditions (above) evaluated
        // to true, then a sub program will be pushed (below) onto the 
        // stack above this current program.  That's okay.  Still need to
        // increment program counter so this IF instruction is only
        // executed once
        env.incrementCurrentProgramCounter();

        if (foundTrueCondition) {

            //push the "then" Program onto the stack            
            Program thenProgram = condition.getThenProgram();
            
            env.pushProgram(thenProgram, condition.isProgramRecursive());
            LogManager.logTrace(org.teiid.logging.LogConstants.CTX_XML_PLAN, new Object[]{"IF: true condition", condition, "- then program:", thenProgram}); //$NON-NLS-1$ //$NON-NLS-2$
        }
        return context;
    }

    public String toString() {
        return "IF BLOCK:"; //$NON-NLS-1$
    }
    
    public PlanNode getDescriptionProperties() {
        PlanNode props = new PlanNode("CHOICE"); //$NON-NLS-1$

        for (int i = 0; i < this.thenBlocks.size(); i++) {
            Condition condition = (Condition) thenBlocks.get(i);
            PlanNode node = null;
            if(condition instanceof RecurseProgramCondition) {
                node = new PlanNode("RECURSIVE"); //$NON-NLS-1$
                node.addProperty(PROP_CONDITION, condition.toString());
            } else {
                node = condition.getThenProgram().getDescriptionProperties();
                node.addProperty(PROP_CONDITION, ((CriteriaCondition)condition).criteria.toString());
            }
            props.addProperty("Condition " + i, node); //$NON-NLS-1$
        }
        
        if (defaultCondition != null && defaultCondition.getThenProgram() != null){
            props.addProperty(PROP_DEFAULT_PROGRAM, defaultCondition.getThenProgram().getDescriptionProperties());
        }
                
        return props;
    }
}