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

import static org.teiid.language.SQLConstants.Reserved.*;

import org.teiid.core.TeiidRuntimeException;
import org.teiid.language.*;
import org.teiid.language.Comparison.Operator;
import org.teiid.language.Join.JoinType;
import org.teiid.language.SQLConstants.Tokens;
import org.teiid.metadata.ColumnSet;
import org.teiid.metadata.Procedure;
import org.teiid.translator.jdbc.JDBCPlugin;
import org.teiid.translator.jdbc.SQLConversionVisitor;

public class PISQLConversionVisitor extends SQLConversionVisitor {

	private static final String APPLY = "APPLY";
    PIExecutionFactory executionFactory;
	
	public PISQLConversionVisitor(PIExecutionFactory hef) {
		super(hef);
		this.executionFactory = hef;
	}
	
    @Override
	public void visit(Join obj) {
        TableReference leftItem = obj.getLeftItem();
        TableReference rightItem = obj.getRightItem();
        JoinType joinType = obj.getJoinType();

        boolean ignoreOnClause = false;
        Condition condition = obj.getCondition();
        
        if(useParensForJoins() && leftItem instanceof Join) {
            buffer.append(Tokens.LPAREN);
            append(leftItem);
            buffer.append(Tokens.RPAREN);
        } else {
            append(leftItem);
        }
        buffer.append(Tokens.SPACE);
        
        switch(joinType) {
            case CROSS_JOIN:
                if (hasLateralJoin(rightItem)) {
                    if (!isTVF(rightItem)) {
                        throw new TeiidRuntimeException(JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID11025, rightItem));
                    }
                    buffer.append(CROSS)
                        .append(Tokens.SPACE)
                        .append(APPLY)
                        .append(Tokens.SPACE);
                    ignoreOnClause = true;
                } else {
                    // "a cross join b" is the same as "a inner join b on (1 = 1)"
                    buffer.append(INNER)
                        .append(Tokens.SPACE)
                        .append(JOIN)
                        .append(Tokens.SPACE);
                    Literal e1 = LanguageFactory.INSTANCE.createLiteral(new Integer(1), Integer.class);
                    Comparison criteria = new Comparison(e1, e1, Operator.EQ);
                    condition = criteria;
                }
                break;
            case FULL_OUTER_JOIN:
                // should not get here as capabilities are turned off.
                throw new TeiidRuntimeException(JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID11024, "FULL OUTER")); 
            case INNER_JOIN:
                buffer.append(INNER)
                    .append(Tokens.SPACE)
                    .append(JOIN)
                    .append(Tokens.SPACE);
                break;
            case LEFT_OUTER_JOIN:
                if (!hasLateralJoin(leftItem) && !hasLateralJoin(rightItem)) {
                    buffer.append(LEFT)
                      .append(Tokens.SPACE)
                      .append(OUTER)                 
                      .append(Tokens.SPACE)
                      .append(JOIN)
                      .append(Tokens.SPACE);
                } else if (hasLateralJoin(leftItem)) {
                    throw new TeiidRuntimeException(JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID11024, "RIGHT OUTER APPLY"));
                } else if (hasLateralJoin(rightItem)) {     
                    if (!isTVF(rightItem)) {
                        throw new TeiidRuntimeException(JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID11025, rightItem));
                    }  
                    buffer.append(OUTER)
                    .append(Tokens.SPACE)
                    .append(APPLY)                 
                    .append(Tokens.SPACE);
                    ignoreOnClause = true;
                }
                break;
            case RIGHT_OUTER_JOIN:
                // right outer is never pushed, so we should probably never get here.
                throw new TeiidRuntimeException(JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID11024, "RIGHT OUTER"));
            default: 
                buffer.append(UNDEFINED);
        }
        
        if(rightItem instanceof Join && (useParensForJoins() || obj.getJoinType() == Join.JoinType.CROSS_JOIN)) {
            buffer.append(Tokens.LPAREN);
            append(hasLateralJoin(rightItem)?unwrap(rightItem):rightItem);
            buffer.append(Tokens.RPAREN);
        } else {
            append(hasLateralJoin(rightItem)?unwrap(rightItem):rightItem);
        }
        
        if (condition != null && !ignoreOnClause) {
            buffer.append(Tokens.SPACE)
              .append(ON)
              .append(Tokens.SPACE);
            append(condition);                    
        }        
    }	
    
    private TableReference unwrap(TableReference table) {
        if (table instanceof DerivedTable) {
            return table;
        } else if (table instanceof NamedProcedureCall) {
            NamedProcedureCall npc = (NamedProcedureCall) table;
            return npc.getCall();
        }        
        return null;
    }

    protected void appendCallStart(Call call) {
        if (!isTVF(call.getMetadataObject())) {
            buffer.append(EXEC)
                  .append(Tokens.SPACE);
        }
    }    
    
    public void visit(ColumnReference obj) {
        ColumnSet<?> cs = obj.getMetadataObject().getParent();
        if (cs.getParent() instanceof Procedure && isTVF((Procedure)cs.getParent())) {
            this.shortNameOnly = true;
            super.visit(obj);
            this.shortNameOnly = false;
        } else {
            super.visit(obj);
        }
    }    

    public void visit(DerivedTable obj) {
        buffer.append(Tokens.LPAREN);
        append(obj.getQuery());
        buffer.append(Tokens.RPAREN);
        buffer.append(Tokens.SPACE);
        if(useAsInGroupAlias()) {
            buffer.append(AS);
            buffer.append(Tokens.SPACE);
        }
        buffer.append(obj.getCorrelationName());
    }

    protected void appendLateralKeyword() {
        buffer.append(LATERAL);
    }
    
    public void visit(NamedProcedureCall obj) {
        buffer.append(Tokens.LPAREN);
        append(obj.getCall());
        buffer.append(Tokens.RPAREN);
        buffer.append(Tokens.SPACE);
        if(useAsInGroupAlias()) {
            buffer.append(AS);
            buffer.append(Tokens.SPACE);
        }
        buffer.append(obj.getCorrelationName());
    }    
    
    private boolean hasLateralJoin(TableReference table) {
        if (table instanceof DerivedTable) {
            return ((DerivedTable)table).isLateral();
        } else if (table instanceof NamedProcedureCall) {
            return ((NamedProcedureCall)table).isLateral();
        }
        return false;
    }
    
    private boolean isTVF(TableReference table) {
        if (table instanceof NamedTable) {
            String value = ((NamedTable)table).getMetadataObject().getProperty(PIMetadataProcessor.TVF, false);
            return Boolean.parseBoolean(value);
        } else if (table instanceof NamedProcedureCall) {
            String value = ((NamedProcedureCall)table).getCall().getMetadataObject().getProperty(PIMetadataProcessor.TVF, false);
            return Boolean.parseBoolean(value); 
        }
        return false;
    }
    
    private boolean isTVF(Procedure proc) {
        String value = proc.getProperty(PIMetadataProcessor.TVF, false);
        return Boolean.parseBoolean(value); 
    }    
}
