/*
 * 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.translator.coherence.visitor;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.teiid.language.AggregateFunction;
import org.teiid.language.ColumnReference;
import org.teiid.language.Comparison;
import org.teiid.language.Comparison.Operator;
import org.teiid.language.DerivedColumn;
import org.teiid.language.Expression;
import org.teiid.language.Function;
import org.teiid.language.In;
import org.teiid.language.Like;
import org.teiid.language.Literal;
import org.teiid.language.NamedTable;
import org.teiid.language.ScalarSubquery;
import org.teiid.language.SearchedCase;
import org.teiid.language.Select;
import org.teiid.language.TableReference;
import org.teiid.language.visitor.HierarchyVisitor;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.metadata.Column;
import org.teiid.metadata.ForeignKey;
import org.teiid.metadata.RuntimeMetadata;
import org.teiid.metadata.Table;
import org.teiid.resource.adapter.coherence.CoherenceFilterUtil;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.coherence.CoherencePlugin;

import com.tangosol.util.Filter;

/**
 */
public class CoherenceVisitor extends HierarchyVisitor {

	private String tableName = null;
	protected String[] attributeNames = null;
	protected Class[] attributeTypes = null;
	private RuntimeMetadata metadata;
	private Filter filter = null;

    private TranslatorException exception;

    /**
     * 
     */
    public CoherenceVisitor(RuntimeMetadata metadata) {
        super();        
        this.metadata = metadata;
    }
    
    public Filter getFilter() {
		return filter;

    }
 
    public String getTableName() {
    	return tableName;
    }
    
    public String[] getAttributeNames() {
    	return attributeNames;
    }
    
    public Class[] getAttributeTypes() {
    	return attributeTypes;
    }    
    
    public TranslatorException getException() {
        return this.exception;
    }
    
    public Filter createFilter(String criteria)  throws TranslatorException {
        return CoherenceFilterUtil.createFilter(criteria);
        
    }

    
	public void visit(Select query) {
		super.visit(query);
		
		Iterator<DerivedColumn> selectSymbolItr = query.getDerivedColumns().iterator();
		attributeNames = new String[query.getDerivedColumns().size()];
		attributeTypes = new Class[query.getDerivedColumns().size()];
		
		int i=0;
		while(selectSymbolItr.hasNext()) {
			Column e = getElementFromSymbol(selectSymbolItr.next());
			
			attributeNames[i] = this.getNameFromElement(e);
			attributeTypes[i] = e.getJavaType();

			i++;
		}
		
		List<TableReference> tables = query.getFrom();
		TableReference t = tables.get(0);
		if(t instanceof NamedTable) {
			Table group = ((NamedTable)t).getMetadataObject();
			tableName = group.getName();
		}
		
	}
	
	
    public void visit(Comparison obj) {
		LogManager.logTrace(LogConstants.CTX_CONNECTOR, "Parsing Comparison criteria."); //$NON-NLS-1$
		try {
			Comparison.Operator op = ((Comparison) obj).getOperator();
	       
			Expression lhs = ((Comparison) obj).getLeftExpression();
			Expression rhs = ((Comparison) obj).getRightExpression();
	
			String lhsString = getExpressionString(lhs);
			String rhsString = getExpressionString(rhs);
			if(lhsString == null || rhsString == null) {
	            final String msg = CoherencePlugin.Util.getString("CoherenceVisitor.missingComparisonExpression"); //$NON-NLS-1$
				exception = new TranslatorException(msg); 
			}

			if (rhs instanceof Literal || lhs instanceof Literal) {
		        if(rhs instanceof Literal) {
		            Literal literal = (Literal) rhs;
		            addCompareCriteria(lhsString, literal.getValue(), op, literal.getType() );
		            
//		            filter = CoherenceFilterUtil.createCompareFilter(lhsString, literal.getValue(), op, literal.getType() );
		            
		        } else {
		            Literal literal = (Literal) lhs;
		            addCompareCriteria(rhsString, literal.getValue(), op, literal.getType() );
//		            filter = CoherenceFilterUtil.createCompareFilter(rhsString, literal.getValue(), op, literal.getType() );
		        	
		        }
			}
		}catch (TranslatorException t) {
			exception = t;
		}
    }
    
    public void addCompareCriteria(String columnname, Object value, Operator op, Class<?> type ) throws TranslatorException {
        filter = CoherenceFilterUtil.createCompareFilter(columnname, value, op, type);
    }
    
    public void visit(Like obj) {
    	
		LogManager.logTrace(LogConstants.CTX_CONNECTOR, "Parsing LIKE criteria."); //$NON-NLS-1$
//		isNegated = ((Like) criteria).isNegated();
		// Convert LIKE to Equals, where any "%" symbol is replaced with "*".
		try {
			Comparison.Operator op = Operator.EQ;
			Expression lhs = ((Like) obj).getLeftExpression();
			Expression rhs = ((Like) obj).getRightExpression();
		
			String lhsString = getExpressionString(lhs);
			String rhsString = getExpressionString(rhs);
//			rhsString = rhsString.replace("%", "*"); //$NON-NLS-1$ //$NON-NLS-2$
			filter = CoherenceFilterUtil.createFilter(lhsString + " LIKE \'" + rhsString + "\'");		
		}catch (TranslatorException t) {
			exception = t;
		}
    }

    
    public void visit(In obj) {
		LogManager.logTrace(LogConstants.CTX_CONNECTOR, "Parsing IN criteria."); //$NON-NLS-1$
//		isNegated = ((In) criteria).isNegated();
		try {
			Expression lhs = ((In)obj).getLeftExpression();
			String lhsString = getExpressionString(lhs);
			
			List<Expression> rhsList = ((In)obj).getRightExpressions();
	
			Class type = null;
			List parms = new ArrayList(rhsList.size());
	        Iterator iter = rhsList.iterator();
	        while(iter.hasNext()) {
	            Expression expr = (Expression) iter.next();
	            type = addParmFromExpression(expr, parms);
	            
	        }
	        
	        addInCriteria(lhsString, parms, type);
//	        filter = CoherenceFilterUtil.createInFilter(lhsString, parms, type);
		}catch (TranslatorException t) {
			exception = t;
		}
	        
    }
    
    public void addInCriteria(String columnname, List<Object> parms, Class<?> type ) throws TranslatorException {
        filter = CoherenceFilterUtil.createInFilter(columnname, parms, type);
    }
    
    private Class addParmFromExpression(Expression expr, List parms ) {
    	Class type = null;
        if(expr instanceof Literal) {
        	Long longparm = null;
            Literal literal = (Literal) expr;
            
            parms.add(literal);
            
            type = literal.getType();
  
        } else {
            this.exception = new TranslatorException("CoherenceVisitor.Unsupported_expression" + expr); //$NON-NLS-1$
        }
        
        return type;
         
    }
	/** 
	 * Method to get name from the supplied Element
	 * @param e the supplied Element
	 * @return the name
	 */
    // GHH 20080326 - found that code to fall back on Name if NameInSource
	// was null wasn't working properly, so replaced with tried and true
	// code from another custom connector.
	public String getNameFromElement(Column e) {
		String attributeName = null;
		Object p = e.getParent();
		if (p instanceof Table) {
			Table t = (Table)p;
			if (t.getForeignKeys() != null && !t.getForeignKeys().isEmpty()) {
				ForeignKey fk = (ForeignKey)  t.getForeignKeys().get(0);
				String fk_nis = fk.getNameInSource();
				attributeName = fk_nis + "." + e.getNameInSource();
			}
		}
		if (attributeName == null) {
			attributeName = e.getNameInSource();
		}
		if (attributeName == null || attributeName.equals("")) { //$NON-NLS-1$
			attributeName = e.getName();
			// If name in source is not set, then fall back to the column name.
		}
		return attributeName;
	}

	public String getNameFromTable(Table e) {
		String tableName = e.getNameInSource();
		if (tableName == null || tableName.equals("")) { //$NON-NLS-1$
			tableName = e.getName();
			// If name in source is not set, then fall back to the column name.
		}
		return tableName;
	}

    /**
     * Helper method for getting runtime {@link org.teiid.connector.metadata.runtime.Element} from a
     * {@link org.teiid.language.DerivedColumn}.
     * @param symbol Input ISelectSymbol
     * @return Element returned metadata runtime Element
     */
    private Column getElementFromSymbol(DerivedColumn symbol) {
        ColumnReference expr = (ColumnReference) symbol.getExpression();
        return expr.getMetadataObject();
    }
	    
	// GHH 20080326 - found that code to fall back on Name if NameInSource
	// was null wasn't working properly, so replaced with tried and true
	// code from another custom connector.
	private String getExpressionString(Expression e) throws TranslatorException {
		String expressionName = null;
		// GHH 20080326 - changed around the IElement handling here
		// - the rest of this method is unchanged
		if(e instanceof ColumnReference) {
			Column mdIDElement = ((ColumnReference)e).getMetadataObject();
			expressionName = mdIDElement.getNameInSource();
			if(expressionName == null || expressionName.equals("")) {  //$NON-NLS-1$
				expressionName = mdIDElement.getName();
			}
		} else if(e instanceof Literal) {
//			try {
//				if(((Literal)e).getType().equals(Class.forName(Timestamp.class.getName()))) {
//					LogManager.logTrace(LogConstants.CTX_CONNECTOR, "Found an expression that uses timestamp; converting to LDAP string format."); //$NON-NLS-1$
//					Timestamp ts = (Timestamp)((Literal)e).getValue();
//					Date dt = new Date(ts.getTime());
//					//TODO: Fetch format if provided.
//					SimpleDateFormat sdf = new SimpleDateFormat(LDAPConnectorConstants.ldapTimestampFormat);
//					expressionName = sdf.format(dt);
//					LogManager.logTrace(LogConstants.CTX_CONNECTOR, "Timestamp to stsring is: " + expressionName); //$NON-NLS-1$
//				}
//				else {
//					expressionName = ((Literal)e).getValue().toString();
//				}
				
				expressionName = ((Literal)e).getValue().toString();
//			} catch (ClassNotFoundException cce) {
//	            final String msg = LDAPPlugin.Util.getString("IQueryToLdapSearchParser.timestampClassNotFoundError"); //$NON-NLS-1$
//				throw new TranslatorException(cce, msg); 
//			}
//				
		} else {
			if(e instanceof AggregateFunction) {
				LogManager.logError(LogConstants.CTX_CONNECTOR, "Received IAggregate, but it is not supported. Check capabilities."); //$NON-NLS-1$
			} else if(e instanceof Function) {
				LogManager.logError(LogConstants.CTX_CONNECTOR, "Received IFunction, but it is not supported. Check capabilties."); //$NON-NLS-1$
			} else if(e instanceof ScalarSubquery) {
				LogManager.logError(LogConstants.CTX_CONNECTOR, "Received IScalarSubquery, but it is not supported. Check capabilties."); //$NON-NLS-1$
			} else if (e instanceof SearchedCase) {
				LogManager.logError(LogConstants.CTX_CONNECTOR, "Received ISearchedCaseExpression, but it is not supported. Check capabilties."); //$NON-NLS-1$
			}
            final String msg = CoherencePlugin.Util.getString("CoherenceVisitory.unsupportedElementError" , e.toString()); //$NON-NLS-1$
			throw new TranslatorException(msg); 
		}
		expressionName = escapeReservedChars(expressionName);
		return expressionName;
	}
	
	private String escapeReservedChars(String expr) {
		StringBuffer sb = new StringBuffer();
        for (int i = 0; i < expr.length(); i++) {
            char curChar = expr.charAt(i);
            switch (curChar) {
                case '\\':
                    sb.append("\\5c"); //$NON-NLS-1$
                    break;
                case '*':
                    sb.append("\\2a"); //$NON-NLS-1$
                    break;
                case '(':
                    sb.append("\\28"); //$NON-NLS-1$
                    break;
                case ')':
                    sb.append("\\29"); //$NON-NLS-1$
                    break;
                case '\u0000': 
                    sb.append("\\00"); //$NON-NLS-1$
                    break;
                default:
                    sb.append(curChar);
            }
        }
        return sb.toString();
	}	
	
}
