/*******************************************************************************
 * Copyright (c) 1998, 2012 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
 * which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/

package org.eclipse.persistence.internal.xr;

// Javase imports
import java.util.ArrayList;
import java.util.List;

// Java extension imports
import javax.xml.namespace.QName;

// EclipseLink imports
import org.eclipse.persistence.exceptions.DBWSException;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.mappings.structures.ObjectRelationalDatabaseField;
import org.eclipse.persistence.queries.DataModifyQuery;
import org.eclipse.persistence.queries.DataReadQuery;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.queries.StoredFunctionCall;
import org.eclipse.persistence.queries.StoredProcedureCall;
import org.eclipse.persistence.queries.ValueReadQuery;
import static org.eclipse.persistence.internal.xr.Util.SXF_QNAME;
import static org.eclipse.persistence.internal.xr.Util.getTypeNameForJDBCType;
import static org.eclipse.persistence.oxm.XMLConstants.EMPTY_STRING;

/**
 * <p><b>INTERNAL:</b> StoredProcedureQueryHandler sets up the StoredProcedureCall
 * and its arguments in the given {@link DatabaseQuery}
 *
 * @author Mike Norman - michael.norman@oracle.com
 * @since EclipseLink 1.x
 */

public class StoredProcedureQueryHandler extends QueryHandler {
    public static String CURSOR_STR = "CURSOR";
    protected String name;
    protected List<ProcedureArgument> inArguments = new ArrayList<ProcedureArgument>();
    protected List<ProcedureOutputArgument> inOutArguments = new ArrayList<ProcedureOutputArgument>();
    protected List<ProcedureOutputArgument> outArguments = new ArrayList<ProcedureOutputArgument>();

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public List<ProcedureArgument> getInArguments() {
        return inArguments;
    }

    public List<ProcedureOutputArgument> getInOutArguments() {
        return inOutArguments;
    }

    public List<ProcedureOutputArgument> getOutArguments() {
        return outArguments;
    }
    public boolean isStoredFunctionQueryHandler() {
        return false;
    }

    @Override
    public void initializeDatabaseQuery(XRServiceAdapter xrService, QueryOperation queryOperation) {
        DatabaseQuery databaseQueryToInitialize;

        if (queryOperation.hasResponse()) {
            QName type = queryOperation.getResult().getType();
            if (queryOperation.isCollection()) {
                if (queryOperation.isSimpleXMLFormat()) {
                    databaseQueryToInitialize = new DataReadQuery();
                }
                else {
                    if (!xrService.descriptorsByQName.containsKey(type)) {
                        // data-read query
                        databaseQueryToInitialize = new DataReadQuery();
                    }
                    else {
                        //check if descriptor is aggregate
                        Class<?> typeClass = xrService.getTypeClass(type);
                        if (xrService.getORSession().getDescriptor(typeClass).isAggregateDescriptor()) {
                            databaseQueryToInitialize = new DataReadQuery();
                        }
                        else {
                            // read-all query for the class mapped to the type
                            databaseQueryToInitialize = new ReadAllQuery(typeClass);
                        }
                    }
                }
            }
            else {
                if (getOutArguments().size() == 0 && getInOutArguments().size() == 0) {
                    if (isStoredFunctionQueryHandler()) {
                        if (!xrService.descriptorsByQName.containsKey(type)) {
                            databaseQueryToInitialize = new ValueReadQuery();
                        }
                        else {
                            // read object query for the class mapped to the type
                            databaseQueryToInitialize = new ReadObjectQuery(xrService.getTypeClass(type));
                        }
                    }
                    else {
                        // special case - no out args for SP: the return
                        // will be a single int
                        // rowcount
                        databaseQueryToInitialize = new DataModifyQuery();
                    }
                }
                else {
                    if (!xrService.descriptorsByQName.containsKey(type)) {
                    	if (type.equals(SXF_QNAME)) {
                    		databaseQueryToInitialize = new DataReadQuery();
                    	}
                    	else {
                    		databaseQueryToInitialize = new ValueReadQuery();
                    	}
                    }
                    else {
                        // read object query for the class mapped to the type
                        databaseQueryToInitialize = new ReadObjectQuery(xrService.getTypeClass(type));
                    }
                }
            }
        }
        else {
            databaseQueryToInitialize = new ValueReadQuery();
        }
        databaseQueryToInitialize.bindAllParameters();
        setDatabaseQuery(databaseQueryToInitialize);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void initializeCall(XRServiceAdapter xrService, QueryOperation queryOperation,
        DatabaseQuery databaseQuery) {

        StoredProcedureCall spCall = createCall();
        if (getName() != null) {
            spCall.setProcedureName(getName());
        } else {
            spCall.setProcedureName(queryOperation.getName());
        }

        QName resultType = queryOperation.getResultType();

        if ((getInOutArguments().size() + getOutArguments().size()) > 1 &&
            !queryOperation.isSimpleXMLFormat()) {
            throw DBWSException.multipleOutputArgumentsOnlySupportedForSimpleXML();
        }

        // find IN and INOUT parameters
        for (Parameter p : queryOperation.getParameters()) {
            ProcedureArgument arg = findInOutArgument(p.getName());

            // default argument name to parameter name
            String argName = p.getName();
            // override with explicit argument name
            if (arg != null) {
                argName = arg.getName();
            }

            if (arg != null && arg instanceof ProcedureOutputArgument) {
                if (isCursorType(xrService, p.getType())) {
                    throw DBWSException.inoutCursorArgumentsNotSupported();
                }
                spCall.addNamedInOutputArgument(argName, p.getName());
            } else {
                spCall.addNamedArgument(argName, p.getName());
            }
        }

        // find OUT parameters
        if (queryOperation.hasResponse()) {
            if (!queryOperation.isSimpleXMLFormat() ||
                (spCall.isStoredFunctionCall() && !isCursorType(xrService, resultType))) {
                setSingleResult(xrService, spCall, resultType);
                if (queryOperation.getResult().isJdbcTypeSet()) {
                    ObjectRelationalDatabaseField field = new ObjectRelationalDatabaseField(EMPTY_STRING);
                    field.setSqlType(queryOperation.getResult().getJdbcType());
                    field.setSqlTypeName(getTypeNameForJDBCType(queryOperation.getResult().getJdbcType()));
                    // replace the original field with the new one
                    ((StoredFunctionCall)spCall).getParameters().remove(0);
                    ((StoredFunctionCall)spCall).getParameters().add(0, field);
                }
                // support stored function with out args
                for (ProcedureOutputArgument arg : getOutArguments()) {
                    // use argument type
                    if (arg.getResultType() == null || !isCursorType(xrService, arg.getResultType())) {
                        if (arg.isJdbcTypeSet()) {
                            spCall.addNamedOutputArgument(arg.getName(), arg.getName(), arg.getJdbcType(), getTypeNameForJDBCType(arg.getJdbcType()));
                        } else {
                            spCall.addNamedOutputArgument(arg.getName());
                        }
                    }
                }                
            } else {
                if (spCall.isStoredFunctionCall() && isCursorType(xrService, resultType)) {
                    spCall.setIsCursorOutputProcedure(true);
                    // remove the null OUT parameter added by the constructor of SFC
                    spCall.getParameters().remove(0);
                    spCall.getParameters().add(0, new DatabaseField(CURSOR_STR));
                } else if (getOutArguments().isEmpty()) {
                    spCall.setReturnsResultSet(true);
                } else {
                    for (ProcedureOutputArgument arg : getOutArguments()) {
                        // use argument type
                        if (arg.getResultType() != null && isCursorType(xrService, arg.getResultType())) {
                            spCall.useNamedCursorOutputAsResultSet(arg.getName());
                        } else {
                            if (arg.isJdbcTypeSet()) {
                                spCall.addNamedOutputArgument(arg.getName(), arg.getName(), arg.getJdbcType(), getTypeNameForJDBCType(arg.getJdbcType()));
                            } else {
                                spCall.addNamedOutputArgument(arg.getName());
                            }
                        }
                    }
                }
            }
        }
        databaseQuery.setCall(spCall);
    }

    @Override
    public void initializeArguments(XRServiceAdapter xrService, QueryOperation queryOperation,
        DatabaseQuery databaseQuery) {
        for (Parameter p : queryOperation.getParameters()) {
            databaseQuery.addArgument(p.getName());
        }
    }

    protected void setSingleResult(XRServiceAdapter xrService, StoredProcedureCall spCall, QName resultType) {
        if (getOutArguments().size() == 1) {
            ProcedureArgument arg = getOutArguments().get(0);
            // check query's returnType or arg's returnType
            if (isCursorType(xrService, resultType) ||
                ( arg instanceof ProcedureOutputArgument && isCursorType(xrService,
                    ((ProcedureOutputArgument)arg).getResultType())))  {
                spCall.useNamedCursorOutputAsResultSet(arg.getName());
            } else {
                spCall.addNamedOutputArgument(arg.getName());
            }
        }
    }

    protected StoredProcedureCall createCall() {
        StoredProcedureCall spCall = new StoredProcedureCall();
        return spCall;
    }

    private ProcedureArgument findInOutArgument(String name) {
        for (ProcedureArgument arg : getInArguments()) {
            if (arg.getParameterName() != null && arg.getParameterName().equalsIgnoreCase(name)) {
                return arg;
            }
            if (arg.getName().equalsIgnoreCase(name)) {
                return arg;
            }
        }
        for (ProcedureArgument arg : getInOutArguments()) {
            if (arg.getParameterName() != null && arg.getParameterName().equalsIgnoreCase(name)) {
                return arg;
            }
            if (arg.getName().equalsIgnoreCase(name)) {
                return arg;
            }
        }

        return null;
    }

    protected boolean isCursorType(@SuppressWarnings("unused") XRServiceAdapter xrService, QName type) {
        if (type.getLocalPart().startsWith("cursor of")) {
            return true;
        }
        return false;
    }

}
