/*
 * 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$
 *  
 */
// https://issues.jboss.org/browse/TEIID-4509

package org.teiid.query.processor.proc;

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

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;
import org.teiid.query.processor.relational.SubqueryAwareRelationalNode;
import org.teiid.query.sql.lang.Criteria;
import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor;


/**
 * <p>This instruction an holds an if block and an else block and a criteria that determines
 * which block will be executed. These blocks are {@link Program} objects that could contain
 * nested if-else block.  Therefore, this <code>ProgramInstruction</code>
 * implements an arbitrarily deep if-else if-....else block.</p>
 *
 * <p>During processing, the Criteria is evaluated and if it evaluates to true,
 * the "if" block is executed else the "else" block if there is one is executed. These
 * programs are placed on the {@link ProgramEnvironment#getProgramStack stack}.</p>
 */
public class IfInstruction extends ProgramInstruction {

    // the "if" block
    private Program ifProgram;

    // optional "else" block
    private Program elseProgram;

    // criteria on the "if" block
    private Criteria condition;

    /**
     * Constructor for IfInstruction.
     * @param condition The <code>Criteria</code> used to determine which block to execute
     * @param ifProgram The <code>Program</code> representing the "if" block
     * @param elseProgram The <code>Program</code> representing the "else" block
     */
    public IfInstruction(Criteria condition, Program ifProgram, Program elseProgram) {
        this.condition = condition;
        this.ifProgram = ifProgram;
        this.elseProgram = elseProgram;
    }

    /**
     * Constructor for IfInstruction.
     * @param condition The <code>Criteria</code> used to determine which block to execute
     * @param ifProgram The <code>Program</code> representing the "if" block
     */
    public IfInstruction(Criteria condition, Program ifProgram) {
		this(condition, ifProgram, null);
    }

    /**
     * This instruction will evaluate it's criteria, if it evaluates
     * 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.
     * @throws TeiidProcessingException 
     * @see ProgramInstruction#process(ProcedurePlan)
     */
    public void process(ProcedurePlan procEnv)
        throws BlockedException, TeiidComponentException, TeiidProcessingException {

    	boolean evalValue = procEnv.evaluateCriteria(condition);

        if(evalValue) {
	        LogManager.logTrace(org.teiid.logging.LogConstants.CTX_DQP, new Object[]{"IFInstruction: "+ //$NON-NLS-1$
		        	" The criteria on the if block evaluated to true, processing the if block"}); //$NON-NLS-1$

            //push the "if" Program onto the stack
            procEnv.push(ifProgram);
        } else if(elseProgram != null) {
	        LogManager.logTrace(org.teiid.logging.LogConstants.CTX_DQP, new Object[]{"IFInstruction: "+ //$NON-NLS-1$
		        	" The criteria on the if block evaluated to false, processing the else block"}); //$NON-NLS-1$            
            //push the "else" Program onto the stack
            procEnv.push(elseProgram);
        }

    }

    public Program getIfProgram(){ //Defect 13291 - made public to support changes to ProcedurePlan
        return this.ifProgram;
    }

    public Program getElseProgram(){ //Defect 13291 - made public to support changes to ProcedurePlan
        return this.elseProgram;
    }
    
    /**
     * Returns a deep clone
     */
    public IfInstruction clone(){
    	// EXTENSION
    	// ISSUE: DVCORE-2441
    	// FIXED BY: Roman Pedchenko
    	// DATE: 2014.10.23
    	// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
    	// Program cloneIf = (Program) this.ifProgram.clone();
    	Program cloneIf = this.ifProgram.clone();
    	// END EXTENSION
    	Program cloneElse = null;
    	if(elseProgram != null) {
    		// EXTENSION
    		// ISSUE: DVCORE-2441
    		// FIXED BY: Roman Pedchenko
    		// DATE: 2014.10.23
    		// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
    		// cloneElse = (Program) this.elseProgram.clone();
    		cloneElse = this.elseProgram.clone();
    		// END EXTENSION
    	}
        IfInstruction clone = new IfInstruction(this.condition, cloneIf, cloneElse);
        return clone;
    }

    public String toString() {
    	// EXTENSION
    	// ISSUE: DVCORE-2441
    	// FIXED BY: Roman Pedchenko
    	// DATE: 2014.10.23
    	// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
    	// return "IF INSTRUCTION:"; //$NON-NLS-1$
    	StringBuffer sb = new StringBuffer("IF INSTRUCTION: "); //$NON-NLS-1$
    	sb.append(condition);
    	sb.append("\n").append(ifProgram); //$NON-NLS-1$
    	if (elseProgram!=null) {
    		sb.append("\nELSE\n"); //$NON-NLS-1$
    		sb.append(elseProgram);
    	}
    	return sb.toString();
    	// END EXTENSION
    }

    public PlanNode getDescriptionProperties() {
    	PlanNode props = new PlanNode("IF"); //$NON-NLS-1$
        props.addProperty(PROP_CRITERIA, this.condition.toString());
        props.addProperty(PROP_THEN, this.ifProgram.getDescriptionProperties());
        if(elseProgram != null) {
        	props.addProperty(PROP_ELSE, this.elseProgram.getDescriptionProperties());
        }
        return props;
    }
    
// EXTENSION
// ISSUE: DVCORE-2441
// FIXED BY: Roman Pedchenko
// DATE: 2014.10.23
// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
//     @Override
//     public void getChildPlans(List<ProcessorPlan> plans) {
//     	ifProgram.getChildPlans(plans);
//     	if (elseProgram != null) {
//     		elseProgram.getChildPlans(plans);
//     	}
//     }
// END EXTENSION

	@Override
	public Boolean requiresTransaction(boolean transactionalReads) {
		Boolean conditionRequires = SubqueryAwareRelationalNode.requiresTransaction(transactionalReads, ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(condition));
		if (conditionRequires != null && conditionRequires) {
			return true;
		}
		Boolean requires = ifProgram.requiresTransaction(transactionalReads);
		if (requires != null && requires) {
			return true;
		}
		if (elseProgram != null) {
			Boolean requiresElse = elseProgram.requiresTransaction(transactionalReads);
			if (requiresElse != null && requiresElse) {
				return true;
			}
			if (requiresElse == null) {
				return conditionRequires == null ? true : null;
			}
		}
		if (requires == null) {
			return conditionRequires == null ? true : null;
		}
		return conditionRequires;
	}

}
