/*
 * 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.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.SortedMap;
import java.util.TreeSet;

import org.teiid.adminapi.impl.VDBMetaData;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.metadata.AbstractMetadataRecord;
import org.teiid.query.eval.Evaluator;
import org.teiid.query.metadata.CompositeMetadataStore;
import org.teiid.query.metadata.TransformationMetadata;
import org.teiid.query.processor.relational.RelationalNode;
import org.teiid.query.sql.lang.Criteria;
import org.teiid.query.sql.lang.OrderBy;
import org.teiid.query.sql.lang.Query;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.Function;
import org.teiid.query.tempdata.BaseIndexInfo;
import org.teiid.query.tempdata.SearchableTable;
import org.teiid.query.util.CommandContext;

abstract class RecordTable<T extends AbstractMetadataRecord> implements SearchableTable {
	
	public interface SimpleIterator<T> {
		public T next() throws TeiidProcessingException, TeiidComponentException;
	}
	
	private static final SimpleIterator<?> empty = new SimpleIterator<Object>() {
		
		@Override
		public Object next() throws TeiidProcessingException, TeiidComponentException {
			return null;
		}
	};
	
	@SuppressWarnings("unchecked")
	public static <T> SimpleIterator<T> emptyIterator() {
		return (SimpleIterator<T>) empty;
	}
	
	public static class SimpleIteratorWrapper<T> implements SimpleIterator<T> {
		private Iterator<? extends T> iter;
		
		public SimpleIteratorWrapper(Iterator<? extends T> iter) {
			this.iter = iter;
		}
		
		public void setIterator(Iterator<? extends T> iter) {
			this.iter = iter;
		}
		
		@Override
		public T next() throws TeiidProcessingException,
				TeiidComponentException {
			while (iter.hasNext()) {
				T result = iter.next();
				if (result != null && isValid(result)) {
					return result;
				}
			}
			return null;
		}

		protected boolean isValid(T result) {
			return true;
		}
		
	}
	
	public static abstract class ExpandingSimpleIterator<P, T> implements SimpleIterator<T> {
		private SimpleIterator<T> childIter;
		private SimpleIterator<P> parentIter;
		private P currentParent;
		
		public ExpandingSimpleIterator(SimpleIterator<P> parentIter) {
			this.parentIter = parentIter;
		}

		@Override
		public T next() throws TeiidProcessingException,
				TeiidComponentException {
			while (true) {
				if (childIter == null) {
					currentParent = parentIter.next();
					if (currentParent == null) {
						return null;
					}
					childIter = getChildIterator(currentParent);
				}
				T t = childIter.next();
				if (t != null) {
					return t;
				}
				childIter = null;
			}
		}
		
		public P getCurrentParent() {
			return currentParent;
		}
		
		protected abstract SimpleIterator<T> getChildIterator(P parent);
	}
	
	private Map<Expression, Integer> columnMap;
	private Expression[] pkColumns;
	protected Evaluator eval;
	
	public RecordTable(int[] pkColumnIndexs, List<ElementSymbol> columns) {
		this.columnMap = RelationalNode.createLookupMap(columns);
		this.pkColumns = new ElementSymbol[pkColumnIndexs.length];
		for (int i = 0; i < pkColumnIndexs.length; i++) {
			pkColumns[i] = columns.get(pkColumnIndexs[i]);
		}
		eval = new Evaluator(columnMap, null, null);
	}
	
	@Override
	public int getPkLength() {
		return pkColumns.length;
	}

	@Override
	public Map<Expression, Integer> getColumnMap() {
		return columnMap;
	}
	
	@Override
	public Boolean matchesPkColumn(int pkIndex, Expression ex) {
		if (ex instanceof Function) {
			Function f = (Function)ex;
			ex = f.getArg(0);
		}
		return (pkColumns[pkIndex].equals(ex));
	}
	
	@Override
	public boolean supportsOrdering(int pkIndex, Expression ex) {
		return !(ex instanceof ElementSymbol);
	}
	
	public abstract SimpleIterator<T> processQuery(final VDBMetaData vdb, CompositeMetadataStore metadataStore, BaseIndexInfo<?> ii, TransformationMetadata metadata, CommandContext commandContext);
	
	public SimpleIterator<T> processQuery(final VDBMetaData vdb, NavigableMap<String, ?> map, BaseIndexInfo<?> ii, final CommandContext commandContext) {
		final Criteria crit = ii.getCoveredCriteria();
		final ArrayList<Object> rowBuffer = new ArrayList<Object>(1);
		if (!ii.getValueSet().isEmpty()) {
			final List<List<Object>> vals = ii.getValueSet();
			final SortedMap<String, ?> fMap = map;
			return new SimpleIterator<T>() {
				int i = 0;
				TreeSet<String> seen = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
				@Override
				public T next() throws TeiidProcessingException, TeiidComponentException {
					while (i < vals.size()) {
						String key = (String)vals.get(i++).get(0);
						if (!seen.add(key)) {
							//filter to only a single match 
							continue;
						}
						T s = extractRecord(fMap.get(key));
						if (isValid(s, vdb, rowBuffer, crit, commandContext)) {
							return s;
						}
					}
					return null;
				}
			};
		}
		try {
			if (ii.getLower() != null) {
				if (ii.getUpper() != null) {
					map = map.subMap((String) ii.getLower().get(0), true, (String) ii.getUpper().get(0), true);
				} else {
					map = map.tailMap((String) ii.getLower().get(0), true);
				}
			} else if (ii.getUpper() != null) {
				map = map.headMap((String) ii.getUpper().get(0), true);
			}
			final Iterator<?> iter = map.values().iterator();
			return new SimpleIterator<T>() {
				@Override
				public T next() throws TeiidProcessingException, TeiidComponentException {
					while (iter.hasNext()) {
						T s = extractRecord(iter.next());
						if (isValid(s, vdb, rowBuffer, crit, commandContext)) {
							return s;
						}
					}
					return null;
				}
			};
		} catch (IllegalArgumentException e) {
			//this is a map bound issue or lower is greater than upper
			return emptyIterator();
		}
	}
	
	protected T extractRecord(Object val) {
		return (T) val;
	}

	public BaseIndexInfo<RecordTable<?>> planQuery(Query query, Criteria condition, CommandContext context) {
		BaseIndexInfo<RecordTable<?>> info = new BaseIndexInfo<RecordTable<?>>(this, Collections.EMPTY_LIST, condition, null, false);
		if (!info.getValueSet().isEmpty()) {
			info.sortValueSet(OrderBy.ASC, context.getBufferManager().getOptions().getDefaultNullOrder());
		}
		return info;
	}
	
	/**
	 * 
	 * @param s
	 * @param vdb
	 * @param rowBuffer
	 * @param condition
	 * @param commandContext
	 * @return
	 * @throws TeiidProcessingException
	 * @throws TeiidComponentException
	 */
	protected boolean isValid(T s, VDBMetaData vdb, List<Object> rowBuffer, Criteria condition, CommandContext commandContext) throws TeiidProcessingException, TeiidComponentException {
		if (s == null) {
			return false;
		}
		if (!commandContext.getDQPWorkContext().isAdmin() && !commandContext.getAuthorizationValidator().isAccessible(s, commandContext)) {
			return false;
		}
		if (condition != null) {
			rowBuffer.clear();
			fillRow(s, rowBuffer);
			return eval.evaluate(condition, rowBuffer);
		}
		return true;
	}

	protected void fillRow(T s, List<Object> rowBuffer) {
		rowBuffer.add(s.getName());
	}
	
}