/*
 * 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.dqp.internal.process;

import java.util.Collection;
import java.util.List;

import org.teiid.common.buffer.BlockedException;
import org.teiid.common.buffer.TupleBuffer;
import org.teiid.common.buffer.TupleSource;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.dqp.internal.process.SessionAwareCache.CacheID;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.metadata.FunctionMethod.Determinism;
import org.teiid.query.processor.RegisterRequestParameter;
import org.teiid.query.sql.symbol.GroupSymbol;
import org.teiid.translator.CacheDirective;
import org.teiid.translator.CacheDirective.Scope;

/**
 * A proxy {@link TupleSource} that caches a {@link DataTierTupleSource}
 */
final class CachingTupleSource extends
		TupleSourceCache.CopyOnReadTupleSource {
	private final DataTierManagerImpl dataTierManagerImpl;
	private final CacheID cid;
	private final RegisterRequestParameter parameterObject;
	private final CacheDirective cd;
	private final Collection<GroupSymbol> accessedGroups;
	final DataTierTupleSource dtts;
	final RequestWorkItem item;

	CachingTupleSource(DataTierManagerImpl dataTierManagerImpl, TupleBuffer tb, DataTierTupleSource ts, CacheID cid,
			RegisterRequestParameter parameterObject, CacheDirective cd,
			Collection<GroupSymbol> accessedGroups, RequestWorkItem item) {
		super(tb, ts);
		this.dataTierManagerImpl = dataTierManagerImpl;
		this.dtts = ts;
		this.cid = cid;
		this.parameterObject = parameterObject;
		this.cd = cd;
		this.accessedGroups = accessedGroups;
		this.item = item;
	}

	@Override
	public List<?> nextTuple() throws TeiidComponentException,
			TeiidProcessingException {
		if (dtts.scope == Scope.NONE || tb == null) {
			removeTupleBuffer();
			return ts.nextTuple();
		}
		//TODO: the cache directive object needs synchronized for consistency
		List<?> tuple = super.nextTuple();
		if (tuple == null && !dtts.errored) {
			synchronized (cd) {
				if (dtts.scope == Scope.NONE) {
					removeTupleBuffer();
					return tuple;
				}
				CachedResults cr = new CachedResults();
		        cr.setResults(tb, null);
		        if (!Boolean.FALSE.equals(cd.getUpdatable())) {
		        	if (accessedGroups != null) {
			        	for (GroupSymbol gs : accessedGroups) {
			        		cr.getAccessInfo().addAccessedObject(gs.getMetadataID());
			        	}
		        	}
		        } else {
		        	cr.getAccessInfo().setSensitiveToMetadataChanges(false);
		        }
		        if (parameterObject.limit > 0 && parameterObject.limit == rowNumber) {
		        	cr.setRowLimit(rowNumber);
		        }
		        tb.setPrefersMemory(Boolean.TRUE.equals(cd.getPrefersMemory()));
		    	Determinism determinismLevel = getDeterminismLevel(this.dtts.scope);
		        this.dataTierManagerImpl.requestMgr.getRsCache().put(cid, determinismLevel, cr, cd.getTtl()); 
		        tb = null;
			}
		}
		return tuple;
	}

	public static Determinism getDeterminismLevel(CacheDirective.Scope scope) {
		Determinism determinismLevel = Determinism.SESSION_DETERMINISTIC;
		if (scope != null) {
			switch (scope) {
			case VDB:
				determinismLevel = Determinism.VDB_DETERMINISTIC;
			case SESSION:
				determinismLevel = Determinism.SESSION_DETERMINISTIC;
			case USER:
				determinismLevel = Determinism.USER_DETERMINISTIC;
			}
		}
		return determinismLevel;
	}

	private void removeTupleBuffer() {
		if (tb != null) {
			tb.remove();
			tb = null;
		}
	}

	@Override
	public void closeSource() {
		try {
			if (tb != null && !dtts.errored) {
				boolean readAll = true;
				synchronized (cd) {
					readAll = !Boolean.FALSE.equals(cd.getReadAll()); 
				}
				if (readAll) {
					//TODO that this is blocking, so it could be made faster in non-transactional scenarios
					//we should also shut off any warnings, since the plan isn't consuming these tuples
					//the approach would probably be to do more read-ahead
					dtts.getAtomicRequestMessage().setSerial(true);
					while (dtts.scope != Scope.NONE) {
						if (item.isCanceled()) {
							LogManager.logDetail(LogConstants.CTX_DQP, dtts.getAtomicRequestMessage().getAtomicRequestID(), "Not using full results due to cancellation."); //$NON-NLS-1$
							break;
						}
						try {
							List<?> tuple = nextTuple();
							if (tuple == null) {
								break;
							}
						} catch (BlockedException e) {
							//this is possible if were were already waiting for an asynch result
							try {
								Thread.sleep(50); //TODO: we could synch/notify in the DataTierTupleSource
							} catch (InterruptedException e1) {
								break;
							} 
						} catch (TeiidException e) {
							LogManager.logDetail(LogConstants.CTX_DQP, e, dtts.getAtomicRequestMessage().getAtomicRequestID(), "Not using full results due to error."); //$NON-NLS-1$
							break;
						}
					}
				}
			}
		} finally {
			removeTupleBuffer();
			ts.closeSource();
		}
	}
}