/*
 * 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.processor.relational;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.teiid.client.plan.PlanNode;
import org.teiid.common.buffer.BlockedException;
import org.teiid.common.buffer.TupleBatch;
import org.teiid.common.buffer.TupleSource;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.query.QueryPlugin;
import org.teiid.query.analysis.AnalysisRecord;
import org.teiid.query.eval.Evaluator;
import org.teiid.query.optimizer.relational.RowBasedSecurityHelper;
import org.teiid.query.processor.RegisterRequestParameter;
import org.teiid.query.sql.LanguageObject;
import org.teiid.query.sql.lang.BatchedUpdateCommand;
import org.teiid.query.sql.lang.Command;
import org.teiid.query.sql.util.VariableContext;
import org.teiid.query.util.CommandContext;



/** 
 * Node that batches commands sent to the DataManager.
 * @since 4.2
 */
public class BatchedUpdateNode extends SubqueryAwareRelationalNode {
    
    private static final List<Integer> ZERO_COUNT_TUPLE = Arrays.asList(Integer.valueOf(0));

    /** The commands in this batch. */
    private List<Command> updateCommands;
    private List<VariableContext> contexts;
    private List<Boolean> shouldEvaluate;
    
    /** The model name within the scope of which these commands are being executed. */
    private String modelName;
    /** The tuple source containing the update counts after the batch has been executed. */
    private TupleSource tupleSource;
    
    /** Set containing the indexes of commands that weren't executed. */
    private boolean[] unexecutedCommands;

	private int commandCount;
    
    /**
     *  
     * @param nodeID
     * @param commands The Commands in this batch
     * @param modelName The name of the model. All the commands in this batch must update groups only within this model.
     * @since 4.2
     */
    public BatchedUpdateNode(int nodeID, List<Command> commands, List<VariableContext> contexts, List<Boolean> shouldEvaluate, String modelName) {
        super(nodeID);
        this.shouldEvaluate = shouldEvaluate;
        this.contexts = contexts;
        this.updateCommands = commands;
        this.modelName = modelName;
    }

    /** 
     * @see org.teiid.query.processor.relational.RelationalNode#open()
     * @since 4.2
     */
    public void open() throws TeiidComponentException, TeiidProcessingException {
        super.open();
        unexecutedCommands = new boolean[updateCommands.size()];
        List<Command> commandsToExecute = new ArrayList<Command>(updateCommands.size());
        // Find the commands to be executed
        for (int i = 0; i < updateCommands.size(); i++) {
            Command updateCommand = (Command)updateCommands.get(i).clone();
            CommandContext context = this.getContext();
            if (this.contexts != null && !this.contexts.isEmpty()) {
            	context = context.clone();
            	context.setVariableContext(this.contexts.get(i));
            }
            boolean needProcessing = false;
            if(shouldEvaluate != null && shouldEvaluate.get(i)) {
                updateCommand = (Command) updateCommand.clone();
                Evaluator eval = getEvaluator(Collections.emptyMap());
                eval.initialize(context, getDataManager());
                AccessNode.rewriteAndEvaluate(updateCommand, eval, context, context.getMetadata());
            }
            needProcessing = RelationalNodeUtil.shouldExecute(updateCommand, true);
            if (needProcessing) {
                commandsToExecute.add(updateCommand);
            } else {
                unexecutedCommands[i] = true;
            }
        }
        if (!commandsToExecute.isEmpty()) {
            BatchedUpdateCommand command = new BatchedUpdateCommand(commandsToExecute);
            RowBasedSecurityHelper.checkConstraints(command, getEvaluator(Collections.emptyMap()));
            tupleSource = getDataManager().registerRequest(getContext(), command, modelName, new RegisterRequestParameter(null, getID(), -1));
        }
    }
    
    /** 
     * @throws TeiidProcessingException 
     * @see org.teiid.query.processor.relational.RelationalNode#nextBatchDirect()
     * @since 4.2
     */
    public TupleBatch nextBatchDirect() throws BlockedException, TeiidComponentException, TeiidProcessingException {
        int numExpectedCounts = updateCommands.size();
        for (;commandCount < numExpectedCounts; commandCount++) {
            // If the command at this index was not executed
            if (tupleSource == null || unexecutedCommands[commandCount]) {
                addBatchRow(ZERO_COUNT_TUPLE);
            } else { // Otherwise, get the next count in the batch
                List<?> tuple = tupleSource.nextTuple();
                if (tuple != null) {
                    // Assumption: the number of returned tuples exactly equals the number of commands submitted
                    addBatchRow(Arrays.asList(new Object[] {tuple.get(0)}));
                } else {
                    // Should never happen since the number of expected results is known
                     throw new TeiidComponentException(QueryPlugin.Event.TEIID30192, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30192, commandCount, numExpectedCounts));
                }
            }
        }
        // This is the only tuple batch we need.
        terminateBatches();
        return pullBatch();
    }

    /** 
     * @see org.teiid.query.processor.relational.RelationalNode#close()
     * @since 4.2
     */
    public void closeDirect() {
    	super.closeDirect();
        if (tupleSource != null) {
        	tupleSource.closeSource();
        	tupleSource = null;
        }
    }
    
    /** 
     * @see org.teiid.query.processor.relational.RelationalNode#reset()
     * @since 4.2
     */
    public void reset() {
        super.reset();
        tupleSource = null;
        unexecutedCommands = null;
        commandCount = 0;
    }
    
    /** 
     * @see java.lang.Object#clone()
     * @since 4.2
     */
    public Object clone() {
        BatchedUpdateNode clonedNode = new BatchedUpdateNode(getID(), updateCommands, contexts, shouldEvaluate, modelName);
        super.copyTo(clonedNode);
        return clonedNode;
    }
    
    @Override
    protected Collection<? extends LanguageObject> getObjects() {
    	return null;
    }
    
    @Override
    public Boolean requiresTransaction(boolean transactionalReads) {
    	return null;
    }
    
    @Override
    public PlanNode getDescriptionProperties() {
    	PlanNode node = super.getDescriptionProperties();
    	AnalysisRecord.addLanaguageObjects(node, AnalysisRecord.PROP_SQL, this.updateCommands);
    	return node;
    }

}
