/*
 * 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$
 *  
 */
/*
 * EXTENSION
 * ISSUE: DVCORE-2521
 * FIXED BY: Roman Pedchenko
 * DATE: 2014-12-05
 * ADOPTED FROM: TEIID-2153, 2171, 2359, 2149, 2358, 2410, 2271, 2467, 2429, 2363, 2160, 2461, 2797, 2326, 1557
 * END EXTENSION
 */
// https://issues.jboss.org/browse/TEIID-2245
// https://issues.jboss.org/browse/TEIID-3630
package org.teiid.query.optimizer;

import java.util.Arrays;
import java.util.List;

import org.teiid.api.exception.query.QueryMetadataException;
import org.teiid.api.exception.query.QueryPlannerException;
import org.teiid.api.exception.query.QueryResolverException;
import org.teiid.common.buffer.BlockedException;
import org.teiid.common.buffer.TupleBatch;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.core.id.IDGenerator;
import org.teiid.dqp.internal.process.PreparedPlan;
import org.teiid.metadata.Procedure;
import org.teiid.metadata.FunctionMethod.Determinism;
import org.teiid.query.QueryPlugin;
import org.teiid.query.analysis.AnalysisRecord;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.metadata.TempCapabilitiesFinder;
import org.teiid.query.metadata.TempMetadataAdapter;
import org.teiid.query.metadata.TempMetadataID;
import org.teiid.query.metadata.TempMetadataStore;
import org.teiid.query.optimizer.capabilities.CapabilitiesFinder;
import org.teiid.query.optimizer.capabilities.SourceCapabilities.Capability;
import org.teiid.query.optimizer.relational.RelationalPlanner;
import org.teiid.query.optimizer.relational.rules.CapabilitiesUtil;
import org.teiid.query.optimizer.xml.XMLPlanner;
import org.teiid.query.processor.ProcessorPlan;
import org.teiid.query.resolver.QueryResolver;
import org.teiid.query.rewriter.QueryRewriter;
import org.teiid.query.sql.LanguageVisitor;
import org.teiid.query.sql.lang.Command;
import org.teiid.query.sql.lang.Insert;
import org.teiid.query.sql.lang.Query;
import org.teiid.query.sql.proc.CreateProcedureCommand;
import org.teiid.query.sql.symbol.Constant;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.ExpressionSymbol;
import org.teiid.query.sql.symbol.GroupSymbol;
import org.teiid.query.util.CommandContext;

import com.datavirtuality.dv.core.interfaces.DVCaller;


/**
 * <p>This Class produces a ProcessorPlan object (a plan for query execution) from a 
 * user's command and a source of metadata.</p>
 */
public class QueryOptimizer {
	
	private static final CommandPlanner XML_PLANNER = new XMLPlanner();
	private static final CommandPlanner PROCEDURE_PLANNER = new ProcedurePlanner();
    private static final CommandPlanner BATCHED_UPDATE_PLANNER = new BatchedUpdatePlanner();
    private static final CommandPlanner DDL_PLANNER = new DdlPlanner();

	// Can't construct	
	private QueryOptimizer() {}

	public static ProcessorPlan optimizePlan(Command command, QueryMetadataInterface metadata, IDGenerator idGenerator, CapabilitiesFinder capFinder, AnalysisRecord analysisRecord, CommandContext context)
		throws QueryMetadataException, TeiidComponentException, QueryPlannerException {

		if (analysisRecord == null) {
			analysisRecord = new AnalysisRecord(false, false);
		}
		
		if (context == null) {
			context = new CommandContext();
		}
		
		if (!(capFinder instanceof TempCapabilitiesFinder)) {
			capFinder = new TempCapabilitiesFinder(capFinder);
		}
		
        boolean debug = analysisRecord.recordDebug();

		if (!(metadata instanceof TempMetadataAdapter)) {
			metadata = new TempMetadataAdapter(metadata, new TempMetadataStore());
		}

		if (context.getMetadata() == null) {
			context.setMetadata(metadata);
		}
                
        // Create an ID generator that can be used for all plans to generate unique data node IDs
        if(idGenerator == null) {
            idGenerator = new IDGenerator();
        }
        
		if(debug) {
			analysisRecord.println("\n----------------------------------------------------------------------------"); //$NON-NLS-1$
            analysisRecord.println("OPTIMIZE: \n" + command); //$NON-NLS-1$
		}

		if (command instanceof Insert) {
			Insert insert = (Insert)command;
			if (insert.isUpsert()) {
				//if not supported or there are trigger actions, then rewrite as a procedure
				//we do this here since we're changing the command type.
				//TODO: we could push this back into the rewrite, but it will need to be capabilities aware
				GroupSymbol group = insert.getGroup();
				Object modelId = metadata.getModelID(group.getMetadataID());
				boolean supportsUpsert = CapabilitiesUtil.supports(Capability.UPSERT, modelId, metadata, capFinder);
				if (!supportsUpsert) {
					try {
						command = QueryRewriter.rewriteAsUpsertProcedure(insert, metadata, context);
					} catch (TeiidProcessingException e) {
						throw new QueryPlannerException(e);
					}
					if(debug) {
						analysisRecord.println("\n----------------------------------------------------------------------------"); //$NON-NLS-1$
						analysisRecord.println("OPTIMIZE UPSERT PROCEDURE: \n" + command); //$NON-NLS-1$
					}
				}
			}
		}

		ProcessorPlan result = null;

		// EXTENSION
		// ISSUE: DVCORE-570
		// FIXED BY: Roman Pedchenko
		// DATE: 2013.07.23
    	result = (ProcessorPlan) DVCaller.call(18, command, metadata);
        if (result == null)
	 	// END EXTENSION
		switch (command.getType()) {
		case Command.TYPE_UPDATE_PROCEDURE:
			CreateProcedureCommand cupc = (CreateProcedureCommand)command;
			// 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
			// if (cupc.getUserCommand() == null) {
			if (cupc.getUpdateType() != Command.TYPE_UNKNOWN || cupc.getVirtualGroup() == null) {
			// END EXTENSION
				//row update procedure or anon block
				result = planProcedure(command, metadata, idGenerator, capFinder, analysisRecord, context);
			} else {
// 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
//     			StoredProcedure c = (StoredProcedure)cupc.getUserCommand();
// END EXTENSION
				Object pid = cupc.getVirtualGroup().getMetadataID();
				// 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
				// if (c != null) {
				// 	pid = c.getProcedureID();
				if (pid instanceof TempMetadataID) {
					TempMetadataID tid = (TempMetadataID)pid;
					if (tid.getOriginalMetadataID() != null) {
						pid = tid.getOriginalMetadataID();
					}
				// END EXTENSION
				}
				String fullName = metadata.getFullName(pid);
				fullName = "procedure cache:" + fullName; //$NON-NLS-1$
				PreparedPlan pp = context.getPlan(fullName);
				boolean isDvOption = context.getDvOptions() != null && !context.getDvOptions().isEmpty(); 
				if (pp == null || isDvOption) {
					Determinism determinismLevel = context.resetDeterminismLevel();
					try {
						CommandContext clone = CommandContext.newChild(context);
						clone.setDataLineageSkip(true);
						ProcessorPlan plan = planProcedure(command, metadata, idGenerator, capFinder, analysisRecord, clone);
						//note that this is not a full prepared plan.  It is not usable by user queries.
						if (pid instanceof Procedure) {
							clone.accessedPlanningObject(pid);
						}
						pp = new PreparedPlan();
						pp.setPlan(plan, clone);
						if (!isDvOption)
							context.putPlan(fullName, pp, context.getDeterminismLevel());
						context.addDvExt2(clone.getDvExt2());
						context.addUsedMattableIds(clone.getUsedMattableIds());
					} finally {
						context.setDeterminismLevel(determinismLevel);
					}
				}
				result = pp.getPlan().clone();
				for (Object id : pp.getAccessInfo().getObjectsAccessed()) {
					context.accessedPlanningObject(id);
				}
			}
// 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
//             // propagate procedure parameters to the plan to allow runtime type checking
//             ProcedureContainer container = (ProcedureContainer)cupc.getUserCommand();
//             ProcedurePlan plan = (ProcedurePlan)result;
//             if (container != null) {
//             	if (container instanceof StoredProcedure) {
//             		plan.setRequiresTransaction(container.getUpdateCount() > 0);
//             		StoredProcedure sp = (StoredProcedure)container;
//             		if (sp.returnParameters()) {
//             			List<ElementSymbol> outParams = new LinkedList<ElementSymbol>();
//             			for (SPParameter param : sp.getParameters()) {
//     						if (param.getParameterType() == SPParameter.RETURN_VALUE) {
//     							outParams.add(param.getParameterSymbol());
//     						}
//     					}
//             			for (SPParameter param : sp.getParameters()) {
//     						if (param.getParameterType() == SPParameter.INOUT || 
//     								param.getParameterType() == SPParameter.OUT) {
//     							outParams.add(param.getParameterSymbol());
//     						}
//     					}
//             			if (outParams.size() > 0) {
//             				plan.setOutParams(outParams);
//             			}
//             		}
//             		plan.setParams(sp.getProcedureParameters());
//             	}
//                 plan.setMetadata(metadata);
//             }
// END EXTENSION
	        break;
		case Command.TYPE_BATCHED_UPDATE:
			result = BATCHED_UPDATE_PLANNER.optimize(command, idGenerator, metadata, capFinder, analysisRecord, context);
			break;
		case Command.TYPE_ALTER_PROC:
		case Command.TYPE_ALTER_TRIGGER:
		case Command.TYPE_ALTER_FUNCTION:	
		case Command.TYPE_ALTER_VIEW:
			result = DDL_PLANNER.optimize(command, idGenerator, metadata, capFinder, analysisRecord, context);
			break;
		// EXTENSION
		// ISSUE: DVCORE-316, DVCORE-913
		// FIXED BY: Roman Pedchenko, Dmitry Pogorelov
		// DATE: 2013.05.12, 2014.09.30, 2014.10.08
		case Command.TYPE_SET:
			result = new SetPlan((SetCommand) command);
			break;
		// END EXTENSION
		default:
			try {
				if (command.getType() == Command.TYPE_QUERY && command instanceof Query && QueryResolver.isXMLQuery((Query)command, metadata)) {
					result = XML_PLANNER.optimize(command, idGenerator, metadata, capFinder, analysisRecord, context);
				} else {
					RelationalPlanner planner = new RelationalPlanner();
					planner.initialize(command, idGenerator, metadata, capFinder, analysisRecord, context);
					result = planner.optimize(command);
					DVCaller.call(211, context, analysisRecord);
				}
			} catch (QueryResolverException e) {
				 throw new TeiidRuntimeException(QueryPlugin.Event.TEIID30245, e);
			}
        }

		if (command.getOption() != null)
			result.setReplace(command.getOption().isReplace());

		if(debug) {
            analysisRecord.println("\n----------------------------------------------------------------------------"); //$NON-NLS-1$
            analysisRecord.println("OPTIMIZATION COMPLETE:"); //$NON-NLS-1$
            analysisRecord.println("PROCESSOR PLAN:\n" + result); //$NON-NLS-1$
			analysisRecord.println("============================================================================");		 //$NON-NLS-1$
		}			

		return result;
	}

	private static ProcessorPlan planProcedure(Command command,
			QueryMetadataInterface metadata, IDGenerator idGenerator,
			CapabilitiesFinder capFinder, AnalysisRecord analysisRecord,
			CommandContext context) throws TeiidComponentException,
			QueryPlannerException, QueryMetadataException {
		ProcessorPlan result;
		try {
			command = QueryRewriter.rewrite(command, metadata, context);
		} catch (TeiidProcessingException e) {
			 throw new QueryPlannerException(e);
		}
		result = PROCEDURE_PLANNER.optimize(command, idGenerator, metadata, capFinder, analysisRecord, context);
		return result;
	}

	// EXTENSION
	// ISSUE: DVCORE-316,DVCORE-437,DVCORE-526, DVCORE-913
	// FIXED BY: Roman Pedchenko, Dmitry Pogorelov
	// DATE: 2013.05.12,2013.06.16,2013.07.08,2014.09.30,2014.10.08
	public static class SetCommand extends Command {

		private String name;
		private String value;

		public SetCommand() { }

		public SetCommand(String name, String value) {
			this.name = name;
			this.value = value;
		}

		public boolean isShow() {
			return value == null;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getValue() {
			return value;
		}

		public void setValue(String value) {
			this.value = value;
		}

		@Override
		public void acceptVisitor(LanguageVisitor visitor) {
		}

		@Override
		public int getType() {
			return TYPE_SET;
		}

		@Override
		public Object clone() {
			return new SetCommand(name, value);
		}

		@Override
		public List<Expression> getProjectedSymbols() {
			if (!isShow())
				return Command.getUpdateCommandSymbol();
            return Arrays.asList((Expression) new ExpressionSymbol("value", new Constant("")));
		}

		@Override
		public boolean areResultsCachable() {
			return false;
		}

		@Override
		public boolean returnsResultSet() {
			return isShow();
		}
	}

	public static class SetPlan extends ProcessorPlan {
		private SetCommand command;
		private boolean done;

		public SetPlan(SetCommand command) {
			this.command = command;
		}

		public SetCommand getSetCommand() {
			return command;
		}

		@Override
		public List getOutputElements() {
			return command.getProjectedSymbols();
		}

		@Override
		public void open() throws TeiidComponentException, TeiidProcessingException {
		}

		@Override
		public TupleBatch nextBatch() throws BlockedException, TeiidComponentException, TeiidProcessingException {
			if (done)
				return createFinalBatch();
			done = true;
			finalizeRun();

			String name = command.getName();
			if (!command.isShow()) {
				String value = command.getValue();
				getContext().getDvOptions().put(name.toUpperCase(), value.toUpperCase());
				return new TupleBatch(1, new List[] { Arrays.asList(0) } );
			}
			return new TupleBatch(1, new List[] { Arrays.asList(getContext().getDvOptions().get(name.toUpperCase())) } );
		}

		/**
		 * @return
		 */
		protected TupleBatch createFinalBatch() {
			TupleBatch emptyTerminationBatch = new TupleBatch(1, new List[0]);
            emptyTerminationBatch.setTerminationFlag(true);
			return emptyTerminationBatch;
		}

		@Override
		public void close() throws TeiidComponentException {
			done = false;
		}

		@Override
		public ProcessorPlan clone() {
			return new SetPlan((SetCommand) command.clone());
		}
	}
	// END EXTENSION
}
