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

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Clob;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.teiid.core.TeiidComponentException;
import org.teiid.core.types.ClobImpl;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.types.InputStreamFactory;
import org.teiid.core.types.Streamable;
import org.teiid.dqp.internal.datamgr.ConnectorManager;
import org.teiid.dqp.internal.datamgr.ConnectorWork;
import org.teiid.dqp.internal.datamgr.ConnectorWorkItem;
import org.teiid.dqp.message.AtomicRequestMessage;
import org.teiid.dqp.message.AtomicResultsMessage;
import org.teiid.query.optimizer.TestOptimizer;
import org.teiid.query.optimizer.capabilities.SourceCapabilities;
import org.teiid.query.processor.relational.RelationalNodeUtil;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.translator.CacheDirective;
import org.teiid.translator.DataNotAvailableException;
import org.teiid.translator.TranslatorException;


/**
 * This data service will automatically generate results when called with a query - basically
 * the same as the old loopback connector.
 */
@SuppressWarnings("nls")
public class AutoGenDataService extends ConnectorManager{

    // Number of rows that will be generated for each query
    private int rows = 10;
    private SourceCapabilities caps;
	public boolean throwExceptionOnExecute;
	public Integer dataNotAvailable;
	public boolean strict;
	public int sleep;
    private final AtomicInteger executeCount = new AtomicInteger();
    private final AtomicInteger closeCount = new AtomicInteger();
    private boolean useIntCounter;
	public boolean addWarning;
	public CacheDirective cacheDirective;
	public boolean dataAvailable;
	public boolean threadBound;

    public AutoGenDataService() {
    	super("FakeConnector","FakeConnector"); //$NON-NLS-1$ //$NON-NLS-2$
        caps = TestOptimizer.getTypicalCapabilities();
    }
    
    public void setUseIntCounter(boolean useIntCounter) {
		this.useIntCounter = useIntCounter;
	}
    
    public void setSleep(int sleep) {
		this.sleep = sleep;
	}
    
    public void setCaps(SourceCapabilities caps) {
		this.caps = caps;
	}

    public void setRows(int rows) {
        this.rows = rows;
    }
    
    public int getRows() {
        return this.rows;
    }

    @Override
    public ConnectorWork registerRequest(AtomicRequestMessage message)
    		throws TeiidComponentException {
        List projectedSymbols = (message.getCommand()).getProjectedSymbols(); 
        List[] results = createResults(projectedSymbols, rows, useIntCounter);
        if (RelationalNodeUtil.isUpdate(message.getCommand())) {
        	results = new List[] {Arrays.asList(1)};
        }
                
        final AtomicResultsMessage msg = ConnectorWorkItem.createResultsMessage(results);
        msg.setFinalRow(rows);
        return new ConnectorWork() {
        	
        	boolean returnedInitial;
        	
        	@Override
        	public boolean isDataAvailable() {
        		return dataAvailable;
        	}
        	
			@Override
			public AtomicResultsMessage more() throws TranslatorException {
				if (dataNotAvailable != null) {
					int delay = dataNotAvailable;
					dataNotAvailable = null;
					DataNotAvailableException dnae = new DataNotAvailableException(delay);
					dnae.setStrict(strict);
					throw dnae;
				}
				if (addWarning) {
					msg.setWarnings(Arrays.asList(new Exception()));
				}
				if (!returnedInitial) {
					returnedInitial = true;
					return msg;
				}
				throw new RuntimeException("Should not be called"); //$NON-NLS-1$
			}
			
			@Override
			public void execute() throws TranslatorException {
				executeCount.incrementAndGet();
				if (sleep > 0) {
					try {
						Thread.sleep(sleep);
					} catch (InterruptedException e) {
						throw new RuntimeException(e);
					}
				}
				if (throwExceptionOnExecute) {
		    		throw new TranslatorException("Connector Exception"); //$NON-NLS-1$
		    	}
			}
			
			@Override
			public void close() {
				closeCount.incrementAndGet();
			}
			
			@Override
			public void cancel() {
				
			}
			
			@Override
			public CacheDirective getCacheDirective() {
				return cacheDirective;
			}

			@Override
			public boolean isForkable() {
				return true;
			}
			
			@Override
			public boolean isThreadBound() {
				return threadBound;
			}
			
		};
    }
    
    public AtomicInteger getExecuteCount() {
		return executeCount;
	}
    
    public AtomicInteger getCloseCount() {
		return closeCount;
	}
    
    public static List[] createResults(List symbols, int rowCount, boolean useIntCounter) {
        List[] rows = new List[rowCount];

        for(int i=0; i<rowCount; i++) {        
            List row = new ArrayList();        
            Iterator iter = symbols.iterator();
            while(iter.hasNext()) {
                Expression symbol = (Expression) iter.next();
                Class type = symbol.getType();
                row.add( getValue(type, i, useIntCounter) );
            }
            rows[i] = row;
        }   
        
        return rows;
    }

    private static final String STRING_VAL = "ABCDEFG"; //$NON-NLS-1$
    private static final Integer INTEGER_VAL = new Integer(0);
    private static final Long LONG_VAL = new Long(0);
    private static final Float FLOAT_VAL = new Float(0.0);
    private static final Short SHORT_VAL = new Short((short)0);
    private static final Double DOUBLE_VAL = new Double(0.0);
    private static final Character CHAR_VAL = new Character('c');
    private static final Byte BYTE_VAL = new Byte((byte)0);
    public static final Clob CLOB_VAL = new ClobImpl(new InputStreamFactory() {
    	@Override
    	public InputStream getInputStream() throws IOException {
    		return new ByteArrayInputStream("hello world".getBytes(Streamable.CHARSET));
    	}
    }, -1);
    private static final Boolean BOOLEAN_VAL = Boolean.FALSE;
    private static final BigInteger BIG_INTEGER_VAL = new BigInteger("0"); //$NON-NLS-1$
    private static final BigDecimal BIG_DECIMAL_VAL = new BigDecimal("0"); //$NON-NLS-1$
    private static final java.sql.Date SQL_DATE_VAL = new java.sql.Date(0);
    private static final java.sql.Time TIME_VAL = new java.sql.Time(0);
    private static final java.sql.Timestamp TIMESTAMP_VAL = new java.sql.Timestamp(0);
    
    static Object getValue(Class<?> type, int row, boolean useIntCounter) {
        if(type.equals(DataTypeManager.DefaultDataClasses.STRING)) {
            return STRING_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.INTEGER)) {
            return useIntCounter?row:INTEGER_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.SHORT)) { 
            return SHORT_VAL;    
        } else if(type.equals(DataTypeManager.DefaultDataClasses.LONG)) {
            return LONG_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.FLOAT)) {
            return FLOAT_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.DOUBLE)) {
            return DOUBLE_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.CHAR)) {
            return CHAR_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.BYTE)) {
            return BYTE_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.BOOLEAN)) {
            return BOOLEAN_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.BIG_INTEGER)) {
            return BIG_INTEGER_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.BIG_DECIMAL)) {
            return BIG_DECIMAL_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.DATE)) {
            return SQL_DATE_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.TIME)) {
            return TIME_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.TIMESTAMP)) {
            return TIMESTAMP_VAL;
        } else if(type.equals(DataTypeManager.DefaultDataClasses.CLOB)) {
            return CLOB_VAL;
        } else {
            return null;
        }
    }

    @Override
	public SourceCapabilities getCapabilities(){
        return caps;
    }

}
