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

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.teiid.common.buffer.BufferManager;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.logging.LogManager;
import org.teiid.logging.MessageLevel;
import org.teiid.query.mapping.xml.ResultSetInfo;
import org.teiid.query.processor.ProcessorDataManager;
import org.teiid.query.processor.ProcessorPlan;
import org.teiid.query.sql.symbol.GroupSymbol;
import org.teiid.query.util.CommandContext;



public class XMLProcessorEnvironment {

    /** XML result documents should be in String form */
    public static final String STRING_RESULT = "String"; //$NON-NLS-1$

    /* Stack <ProgramState> */
    private LinkedList<ProgramState> programStack = new LinkedList<ProgramState>();
    
    private Set<String> loadedStagingTables = Collections.synchronizedSet(new HashSet<String>());

    private DocumentInProgress documentInProgress;
    
    private String xmlFormat;
    
    private GroupSymbol documentGroup;
    
    private ProcessorDataManager dataMgr;
    private BufferManager bufferMgr;
    private CommandContext commandContext;
    
    protected XMLProcessorEnvironment(){
    }
    
    public XMLProcessorEnvironment(Program mainProgram, GroupSymbol docGroup){
        pushProgram(mainProgram);
        this.documentGroup = docGroup;
    }
    
    /**
     * @see ProcessorEnvironment#start()
     */
    public void initialize(CommandContext context, ProcessorDataManager dataMgr, BufferManager bufferMgr) {
        this.dataMgr = dataMgr;
        this.bufferMgr = bufferMgr;
        this.commandContext = context;
    }
                     
    /**
     * An object to hold state about a Program.  Programs are
     * immutable, therefore their program counter needs to be held
     * in this wrapper object.
     */
    private static class ProgramState {
        private Program program;
        private int programCounter = 0;
        private int lookaheadCounter;
        private int recursionCount = NOT_RECURSIVE;
        
        private static final int NOT_RECURSIVE = 0;
        
        public String toString() {
            return program.toString() + ", counter " + programCounter + ", recursionCount " + (recursionCount == NOT_RECURSIVE? "not recursive": "" + recursionCount); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
        }
    }
    
    public Program getCurrentProgram() {
        // jh case 5266
        if ( this.programStack.size() > 0 ) {
            ProgramState programState = this.programStack.getFirst();
            return programState.program;
        }
        return null;
    }
    
    /**
     * Indicates if there is a recursive program anywhere in the
     * current program stack (not just at the top). 
     * @return whether there is a recursive program anywhere in the
     * program stack
     */
    public boolean isRecursiveProgramInStack() {
        Iterator<ProgramState> stackedPrograms = this.programStack.iterator();
        // Always at least one program in the stack
        ProgramState programState = stackedPrograms.next();
        while (programState.recursionCount == ProgramState.NOT_RECURSIVE && stackedPrograms.hasNext()) {
            programState = stackedPrograms.next();
        }
        return (programState.recursionCount > ProgramState.NOT_RECURSIVE);
    }

    public void incrementCurrentProgramCounter() {
        ProgramState programState = this.programStack.getFirst();
        programState.programCounter++;
        
        // Always leave one Program in the Program stack, even if it is finished
        while (this.programStack.size() > 1 &&
               programState.programCounter >= programState.program.getProcessorInstructions().size()) {
            this.programStack.removeFirst();
            if(LogManager.isMessageToBeRecorded(org.teiid.logging.LogConstants.CTX_XML_PLAN, MessageLevel.TRACE)) {
                LogManager.logTrace(org.teiid.logging.LogConstants.CTX_XML_PLAN, new Object[]{"Processor Environment popped program w/ recursion count " + programState.recursionCount, "; " + this.programStack.size(), " programs left."}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            }
            programState = this.programStack.getFirst();
        }        
    }
    
    public void pushProgram(Program program) {
        pushProgram(program, false);
    }
    
    public void pushProgram(Program program, boolean isRecursive) {
        ProgramState programState = new ProgramState();
        programState.program = program;
        if (isRecursive) {
            
            ProgramState previousState = getProgramState(program);
            if (previousState != null) {
                programState.recursionCount = previousState.recursionCount + 1;
            } else {
                programState.recursionCount = ProgramState.NOT_RECURSIVE + 1;
            }
            LogManager.logTrace(org.teiid.logging.LogConstants.CTX_XML_PLAN, new Object[]{"Pushed recursive program w/ recursion count " + programState.recursionCount}); //$NON-NLS-1$
            
        } else {
            LogManager.logTrace(org.teiid.logging.LogConstants.CTX_XML_PLAN, new Object[]{"Pushed non-recursive program w/ recursion count " + programState.recursionCount}); //$NON-NLS-1$
        }
        this.programStack.addFirst(programState);
    }

    public ProcessorInstruction getCurrentInstruction(XMLContext context) throws TeiidComponentException, TeiidProcessingException {
        ProgramState programState = this.programStack.getFirst();
        
        //Case 5266: account for an empty program on to the stack; 
        //this is needed to handle an empty sequence or an excluded Choice properly.
        if (programState != null && programState.program.getProcessorInstructions().isEmpty()) {
            incrementCurrentProgramCounter();
            
            programState = this.programStack.getFirst();
        }
        
        if (programState == null) {
            return null;
        }
        
        //start all siblings
        List<ProcessorInstruction> instrs = programState.program.getProcessorInstructions();
        if (programState.programCounter >= programState.lookaheadCounter && instrs.size() > programState.programCounter + 1) {
        	for (programState.lookaheadCounter = programState.programCounter; programState.lookaheadCounter < instrs.size(); programState.lookaheadCounter++) {
        		ProcessorInstruction pi = instrs.get(programState.lookaheadCounter);
        		boolean staging = false;
        		if (pi instanceof ExecStagingTableInstruction) {
        			staging = true;
        			ExecStagingTableInstruction esti = (ExecStagingTableInstruction)pi;
        			if (!esti.info.isAutoStaged()) {
        				//need to load staging tables prior to source queries
        				break;
        			}
        		}
        		if (pi instanceof ExecSqlInstruction) {
        			ExecSqlInstruction esi = (ExecSqlInstruction)pi;
        			if (!staging && esi.info.isAutoStaged() && esi.info.getTempTable() == null) {
        				continue; //derived load
        			}
        			PlanExecutor pe = esi.getPlanExecutor(this, context);
        			pe.execute(context.getReferenceValues(), true);
        		}
        	}
        }
        return programState.program.getInstructionAt(programState.programCounter);
    }
    
    public int getProgramRecursionCount(Program program){
        
        ProgramState programState = getProgramState(program);
        if (programState == null) {
            return ProgramState.NOT_RECURSIVE;
        }
        return programState.recursionCount;
    }
    
    private ProgramState getProgramState(Program program) {
        ProgramState result = null;
        Iterator<ProgramState> stackedPrograms = this.programStack.iterator();
        while (stackedPrograms.hasNext()) {
            ProgramState programState = stackedPrograms.next();
            Program stackedProgram = programState.program;
            if (stackedProgram == program) {
                result = programState;
                break;
            }
        }
            
        return result;
    }

    public PlanExecutor createResultExecutor(ResultSetInfo info) {
    
        // cloning the plan inside the resultset is not possible
        // because of the dependencies.
        ResultSetInfo clone = (ResultSetInfo)info.clone();
        ProcessorPlan plan = clone.getPlan();
        plan = plan.clone();
        clone.setPlan(plan);
        
        return new RelationalPlanExecutor(clone, this.commandContext, this.dataMgr, this.bufferMgr);
    }
    
    public DocumentInProgress getDocumentInProgress() {
        return this.documentInProgress;
    }

    public void setDocumentInProgress(DocumentInProgress documentInProgress) {
        this.documentInProgress = documentInProgress;
    }

    public String getXMLFormat() {
        return this.xmlFormat;
    }

    public void setXMLFormat(String xmlFormat) {
        this.xmlFormat = xmlFormat;
    }

    public ProcessorDataManager getDataManager() {
        return this.dataMgr;
    }

    public CommandContext getProcessorContext() {
        return this.commandContext;
    }
    
    public XMLProcessorEnvironment clone() {
        XMLProcessorEnvironment clone = new XMLProcessorEnvironment();
        copyIntoClone(clone);
        return clone;
    }
 
    /** 
     * Utility method to copy cloned state into newly-instantiated
     * (empty) clone.  Clone will appear as if it were reset.
     * @param clone new but empty
     */
    protected void copyIntoClone(XMLProcessorEnvironment clone) {
        // Programs - just get the one at the bottom of the stack
        ProgramState initialProgramState = this.programStack.getLast();
        ProgramState newState = new ProgramState();
        newState.program = initialProgramState.program;
        clone.programStack.addFirst(newState);
               
        // XML results form and format
        clone.setXMLFormat(this.getXMLFormat());
    }

    public GroupSymbol getDocumentGroup() {
        return this.documentGroup;
    }

    boolean isStagingTableLoaded(String tableName) {
        return this.loadedStagingTables.contains(tableName);
    }
    
    void markStagingTableAsLoaded(String tableName) {
        this.loadedStagingTables.add(tableName);
    }
    
    public BufferManager getBufferManager() {
		return bufferMgr;
	}
    
}
