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

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.olingo.client.api.edm.xml.XMLMetadata;
import org.apache.olingo.commons.api.edm.provider.*;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.metadata.BaseColumn.NullType;
import org.teiid.metadata.Column;
import org.teiid.metadata.Column.SearchType;
import org.teiid.metadata.ExtensionMetadataProperty;
import org.teiid.metadata.KeyRecord;
import org.teiid.metadata.MetadataFactory;
import org.teiid.metadata.Procedure;
import org.teiid.metadata.ProcedureParameter;
import org.teiid.metadata.Table;
import org.teiid.olingo.common.ODataTypeManager;
import org.teiid.translator.MetadataProcessor;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.TranslatorProperty;
import org.teiid.translator.TranslatorProperty.PropertyType;
import org.teiid.translator.WSConnection;

public class ODataMetadataProcessor implements MetadataProcessor<WSConnection> {
    public enum ODataType {
        COMPLEX, 
        NAVIGATION, 
        ENTITY, 
        ENTITY_COLLECTION, 
        ACTION, 
        FUNCTION, 
        COMPLEX_COLLECTION, 
        NAVIGATION_COLLECTION
    };
    
    // local planning properties
    private static final String PARENT_TABLE = "PARENT_TABLE"; //$NON-NLS-1$
    private static final String CONSTRAINT_PROPERTY = "CONSTRAINT_PROPERTY"; //$NON-NLS-1$
    private static final String CONSTRAINT_REF_PROPERTY = "CONSTRAINT_REF_PROPERTY"; //$NON-NLS-1$
    private static final String FK_NAME = "FK_NAME"; //$NON-NLS-1$
    private static final String NAME_SEPARATOR = "_"; //$NON-NLS-1$
    
    @ExtensionMetadataProperty(applicable = { Table.class, Procedure.class }, 
            datatype = String.class, 
            display = "Name in OData Schema", 
            description = "Name in OData Schema", 
            required = true)    
    public static final String NAME_IN_SCHEMA = MetadataFactory.ODATA_URI+"NameInSchema"; //$NON-NLS-1$

    @ExtensionMetadataProperty(applicable = { Table.class, Procedure.class }, 
            datatype = String.class, 
            display = "OData Type", 
            description = "Type of OData Schema Item",
            allowed = "COMPLEX, NAVIGATION, ENTITY, ENTITY_COLLECTION, ACTION, FUNCTION, COMPLEX_COLLECTION, NAVIGATION_COLLECTION",
            required=true)
    public static final String ODATA_TYPE = MetadataFactory.ODATA_URI+"Type"; //$NON-NLS-1$
    
    @ExtensionMetadataProperty(applicable=Table.class, 
            datatype=String.class, 
            display="Merge Into Table", 
            description="Declare the name of table that this table needs to be merged into.")
    public static final String MERGE = MetadataFactory.ODATA_URI+"MERGE"; //$NON-NLS-1$
    
    @ExtensionMetadataProperty(applicable=Column.class, 
            datatype=String.class, 
            display="Pseudo Column", 
            description="Pseudo column for join purposes")
    public static final String PSEUDO = MetadataFactory.ODATA_URI+"PSEUDO"; //$NON-NLS-1$    
    
    private String schemaNamespace;
    private ODataExecutionFactory ef;

    void setExecutionfactory(ODataExecutionFactory ef) {
        this.ef = ef;
    }
    
    public void process(MetadataFactory mf, WSConnection conn)
            throws TranslatorException {
        XMLMetadata serviceMetadata = getSchema(conn);    
        getMetadata(mf, serviceMetadata);
    }

    protected XMLMetadata getSchema(WSConnection conn) throws TranslatorException {
        if (this.ef != null) {
            return this.ef.getSchema(conn);
        }
        return null;
    }
    
    void getMetadata(MetadataFactory mf, XMLMetadata metadata)
            throws TranslatorException {
        CsdlSchema csdlSchema = getDefaultSchema(metadata);
        CsdlEntityContainer container = csdlSchema.getEntityContainer();

        // add entity sets as tables
        for (CsdlEntitySet entitySet : container.getEntitySets()) {
            addTable(mf, entitySet.getName(), entitySet.getType(), ODataType.ENTITY_COLLECTION, metadata);
        }

        // add singletons sets as tables
        for (CsdlSingleton singleton : container.getSingletons()) {
            addTable(mf, singleton.getName(), singleton.getType(), ODataType.ENTITY_COLLECTION, metadata);
        }

        // build relationships among tables
        for (CsdlEntitySet entitySet : container.getEntitySets()) {
            addNavigationProperties(mf, entitySet.getName(), entitySet,
                    metadata);
        }
        
        for (CsdlSingleton singleton : container.getSingletons()) {
            addNavigationProperties(mf, singleton.getName(), singleton,
                    metadata);
        }        
        
        // add PK colums for complex-types
        for (Table table : mf.getSchema().getTables().values()) {
            String parentTable = table.getProperty(PARENT_TABLE, false);
            if (parentTable != null) {
                addPrimaryKeyToComplexTables(mf, table, mf.getSchema().getTable(parentTable));
            }
        }        

        // build relations between tables
        for (Table table : mf.getSchema().getTables().values()) {
            String parentTable = table.getProperty(PARENT_TABLE, false);
            if (parentTable != null) {
                table.setProperty(PARENT_TABLE, null);
                addForeignKey(mf, table, mf.getSchema().getTable(parentTable));
            }
        }
        
        // add functions
        for (CsdlFunctionImport function : container.getFunctionImports()) {
            addFunctionImportAsProcedure(mf, function, ODataType.FUNCTION, metadata);
        }

        // add actions
        for (CsdlActionImport action : container.getActionImports()) {
            addActionImportAsProcedure(mf, action, ODataType.ACTION, metadata);
        }
    }



    private CsdlSchema getDefaultSchema(XMLMetadata metadata) throws TranslatorException {
        CsdlSchema csdlSchema = null;
        if (this.schemaNamespace != null) {
            csdlSchema = metadata.getSchema(this.schemaNamespace);
        } 
        else {
            if (!metadata.getSchemas().isEmpty()) {
                csdlSchema = metadata.getSchemas().get(0);
            }
        }
        if(csdlSchema == null) {
            throw new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17019));
        }
        return csdlSchema;
    }

    private Table buildTable(MetadataFactory mf, String name) {
        Table table = mf.addTable(name);
        table.setSupportsUpdate(true);
        return table;
    }

    private boolean isSimple(String type) {
        return type.startsWith("Edm");
    }
    
    private boolean isEnum(XMLMetadata metadata, String type)
            throws TranslatorException {
        return getEnumType(metadata, type) != null;
    }

    private boolean isComplexType(XMLMetadata metadata, String type)
            throws TranslatorException {
        return getComplexType(metadata, type) != null;
    }

    private boolean isEntityType(XMLMetadata metadata, String type) throws TranslatorException {
        return getEntityType(metadata, type) != null;
    }

    private Table addTable(MetadataFactory mf, String tableName,
            String entityType, ODataType odataType, XMLMetadata metadata) 
            throws TranslatorException {
        Table table = buildTable(mf, tableName);
        table.setProperty(ODATA_TYPE, odataType.name());
        table.setProperty(NAME_IN_SCHEMA, entityType);
        
        CsdlEntityType type = getEntityType(metadata, entityType);
        addEntityTypeProperties(mf, metadata, table, type);
        return table;
    }    
    
    private void addEntityTypeProperties(MetadataFactory mf,
            XMLMetadata metadata, Table table, CsdlEntityType entityType)
            throws TranslatorException {
        // add columns; add complex types as child tables with 1-1 or 1-many
        // relation
        for (CsdlProperty property : entityType.getProperties()) {
            addProperty(mf, metadata, table, property);
        }

        // add properties from base type; if any to flatten the model
        String baseType = entityType.getBaseType();
        while (baseType != null) {
            CsdlEntityType baseEntityType = getEntityType(metadata, baseType);
            for (CsdlProperty property : baseEntityType.getProperties()) {
                addProperty(mf, metadata, table, property);
            }
            baseType = baseEntityType.getBaseType();
        }

        // add PK
        addPrimaryKey(mf, table, entityType.getKey()); //$NON-NLS-1$
    }

    private void addProperty(MetadataFactory mf, XMLMetadata metadata,
            Table table, CsdlProperty property) throws TranslatorException {
        if (isSimple(property.getType()) || isEnum(metadata, property.getType())) {
            addPropertyAsColumn(mf, table, property);
        }
        else {
            CsdlComplexType childType = (CsdlComplexType)getComplexType(metadata, property.getType());            
            addComplexPropertyAsTable(mf, property, childType, metadata, table);
        }
    }
    
    static String getPseudo(Column column) {
        return column.getProperty(ODataMetadataProcessor.PSEUDO, false);
    }
    
    static String getMerge(Table table) {
        return table.getProperty(ODataMetadataProcessor.MERGE, false);
    }    
    
    static boolean isComplexType(Table table) {
        ODataType type = ODataType.valueOf(table.getProperty(ODataMetadataProcessor.ODATA_TYPE, false));
        return type == ODataType.COMPLEX || type == ODataType.COMPLEX_COLLECTION;
    }
    
    static boolean isNavigationType(Table table) {
        ODataType type = ODataType.valueOf(table.getProperty(ODataMetadataProcessor.ODATA_TYPE, false));
        return type == ODataType.NAVIGATION || type == ODataType.NAVIGATION_COLLECTION;
    }    
    
    static boolean isCollection(Table table) {
        ODataType type = ODataType.valueOf(table.getProperty(ODataMetadataProcessor.ODATA_TYPE, false));
        return type == ODataType.ENTITY_COLLECTION
                || type == ODataType.COMPLEX_COLLECTION
                || type == ODataType.NAVIGATION_COLLECTION;
    }    
    
    static boolean isEntitySet(Table table) {
        ODataType type = ODataType.valueOf(table.getProperty(ODataMetadataProcessor.ODATA_TYPE, false));
        return type == ODataType.ENTITY_COLLECTION;
    }    
    
    static String getNativeType(Column column) {
        String nativeType = column.getNativeType();
        if (nativeType == null) {
            nativeType = "Edm.String";
        }
        return nativeType;
    }
    
    private void addComplexPropertyAsTable(MetadataFactory mf, CsdlProperty parentProperty, 
            CsdlComplexType complexType, XMLMetadata metadata, Table parentTable) 
            throws TranslatorException {
        
        String tableName = parentTable.getName()+NAME_SEPARATOR+parentProperty.getName();
        Table childTable = buildTable(mf, tableName);
        childTable.setProperty(NAME_IN_SCHEMA, parentProperty.getType()); // complex type
        childTable.setProperty(ODATA_TYPE, 
                parentProperty.isCollection() ? 
                ODataType.COMPLEX_COLLECTION.name() : ODataType.COMPLEX.name()); // complex type
        childTable.setProperty(PARENT_TABLE, parentTable.getName());
        childTable.setProperty(MERGE, parentTable.getFullName());
        if (isComplexType(parentTable)) {
            childTable.setNameInSource(parentTable.getNameInSource()+"/"+parentProperty.getName());
        } else {
            childTable.setNameInSource(parentProperty.getName());
        }
        
        for (CsdlProperty property:complexType.getProperties()) {
            addProperty(mf, metadata, childTable, property);
        }
        
        // add properties from base type; if any to flatten the model
        String baseType = complexType.getBaseType();
        while(baseType != null) {
            CsdlComplexType baseComplexType = getComplexType(metadata, baseType);
            for (CsdlProperty property:baseComplexType.getProperties()) {
                addProperty(mf, metadata, childTable, property);
            }
            baseType = baseComplexType.getBaseType();
        }
    }

    void addPrimaryKey(MetadataFactory mf, Table table,
            List<CsdlPropertyRef> keys) throws TranslatorException {
        List<String> pkNames = new ArrayList<String>();
        for (CsdlPropertyRef ref : keys) {
            pkNames.add(ref.getName());
            if (ref.getAlias() != null) {
                throw new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17018, 
                        table.getName(),ref.getName()));
            }
        }
        mf.addPrimaryKey("PK", pkNames, table);
    }

    private CsdlNavigationPropertyBinding getNavigationPropertyBinding(CsdlBindingTarget entitySet, String name) {
        List<CsdlNavigationPropertyBinding> bindings = entitySet.getNavigationPropertyBindings();
        for (CsdlNavigationPropertyBinding binding:bindings) {
            String path = binding.getPath();
            int index = path.lastIndexOf('/');
            if (index != -1) {
                path = path.substring(index+1);
            }
            if (path.equals(name)) {
                return binding;
            }
        }
        return null;
    }
    
    private CsdlEntityType getEntityType(XMLMetadata metadata, String name) throws TranslatorException {
        if(name == null) {
            return null;
        }
        
        if (name.contains(".")) {
            int idx = name.lastIndexOf('.');
            CsdlSchema schema = metadata.getSchema(name.substring(0, idx));
            if (schema == null) {
                throw new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17021, name));
            }
            return schema.getEntityType(name.substring(idx+1));
        }
        return getDefaultSchema(metadata).getEntityType(name);
    }
    
    private List<CsdlFunction> getFunctions(XMLMetadata metadata, String name) throws TranslatorException {
        if (name.contains(".")) {
            int idx = name.lastIndexOf('.');
            CsdlSchema schema = metadata.getSchema(name.substring(0, idx));
            if (schema == null) {
                throw new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17021, name));
            }
            return schema.getFunctions(name.substring(idx+1));
        }
        return getDefaultSchema(metadata).getFunctions(name);
    }    

    private List<CsdlAction> getActions (XMLMetadata metadata, String name) throws TranslatorException {
        if (name.contains(".")) {
            int idx = name.lastIndexOf('.');
            CsdlSchema schema = metadata.getSchema(name.substring(0, idx));
            if (schema == null) {
                throw new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17021, name));
            }
            return schema.getActions(name.substring(idx+1));
        }
        return getDefaultSchema(metadata).getActions(name);
    }    
    
    private CsdlComplexType getComplexType(XMLMetadata metadata, String name) throws TranslatorException {
        if (name.contains(".")) {
            int idx = name.lastIndexOf('.');
            CsdlSchema schema = metadata.getSchema(name.substring(0, idx));
            if (schema == null) {
                throw new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17021, name));
            }
            return schema.getComplexType(name.substring(idx+1));
        }
        return getDefaultSchema(metadata).getComplexType(name);
    }
    
    private CsdlEnumType getEnumType(XMLMetadata metadata, String name) throws TranslatorException {
        if (name.contains(".")) {
            int idx = name.lastIndexOf('.');
            CsdlSchema schema = metadata.getSchema(name.substring(0, idx));
            if (schema == null) {
                throw new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17021, name));
            }
            return schema.getEnumType(name.substring(idx+1));
        }
        return getDefaultSchema(metadata).getEnumType(name);
    }    

    void addNavigationProperties(MetadataFactory mf, String tableName,
            CsdlBindingTarget entitySet, XMLMetadata metadata)
            throws TranslatorException {

        Table fromTable = mf.getSchema().getTable(tableName);
        CsdlEntityType fromEntityType = getEntityType(metadata,entitySet.getType());

        Table toTable = null;
        for (CsdlNavigationProperty property : fromEntityType.getNavigationProperties()) {
            CsdlNavigationPropertyBinding binding = getNavigationPropertyBinding(
                    entitySet, property.getName());

            if (binding != null) {
                String target = binding.getTarget();
                int index = target.lastIndexOf('/');
                if (index != -1) {
                    target = target.substring(index+1);
                }
                toTable = mf.getSchema().getTable(target);
                if(index != -1) {
                    toTable.setNameInSource(binding.getTarget());
                }
            } else {
                // this means there is no EntitySet defined for this EntityType,
                // or even if it is defined the set of rows are specific to this EntitySet
                StringBuilder name = new StringBuilder()
                        .append(fromTable.getName()).append(NAME_SEPARATOR)
                        .append(property.getName());
                toTable = addTable(mf, name.toString(), property.getType(), 
                        property.isCollection()?ODataType.NAVIGATION_COLLECTION:ODataType.NAVIGATION, 
                        metadata);
                toTable.setNameInSource(property.getName());                
            }

            // support for self-joins
            if (same(fromTable, toTable)) {
                StringBuilder name = new StringBuilder()
                        .append(fromTable.getName()).append(NAME_SEPARATOR)
                        .append(property.getName());
                toTable = addTable(mf, name.toString(), toTable.getProperty(NAME_IN_SCHEMA, false), 
                        property.isCollection()?ODataType.NAVIGATION_COLLECTION:ODataType.NAVIGATION, 
                        metadata);
                toTable.setNameInSource(property.getName());
            }
            toTable.setProperty(PARENT_TABLE, fromTable.getName());
            toTable.setProperty(MERGE, fromTable.getFullName());
            toTable.setProperty(FK_NAME, property.getName());
            
            int i = 0;
            for (CsdlReferentialConstraint constraint : property.getReferentialConstraints()) {
                toTable.setProperty(CONSTRAINT_PROPERTY + i, constraint.getReferencedProperty());
                toTable.setProperty(CONSTRAINT_REF_PROPERTY + i, constraint.getProperty());
                i++;
            }
        }
    }
    
    private KeyRecord getPK(MetadataFactory mf, Table table) throws TranslatorException {
        KeyRecord record = table.getPrimaryKey();
        if (record == null) {
            String parentTable = table.getProperty(PARENT_TABLE, false);
            if (parentTable == null) {
                throw new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17024, 
                        table.getName()));
            }
            return getPK(mf, mf.getSchema().getTable(parentTable));
        }
        return record;
    }
    
    private void addPrimaryKeyToComplexTables(MetadataFactory mf, Table childTable, Table parentTable) 
            throws TranslatorException {
        KeyRecord record = null;
        
        if (isComplexType(childTable)) {
            // these are complex type based tables.
            record = getPK(mf, parentTable);
        } else {
            // this is entity types now.
            record = parentTable.getPrimaryKey();
        }
        
        if (record == null) {
            throw new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17025, 
                    parentTable.getName()));
        }

        int i = 0;
        List<String> columnNames = new ArrayList<String>();
        for (Column column:record.getColumns()) {
            String targetColumnName = isCollection(childTable) ? 
                    parentTable.getName() + "_" + column.getName() : column.getName();
            if (isNavigationType(childTable)) {
                Column c = mf.getSchema().getTable(childTable.getName()).getColumnByName(targetColumnName);
                if (c == null) {
                    c = addColumn(mf, childTable, column, targetColumnName);
                    c.setProperty(PSEUDO, column.getName());
                } else {
                    targetColumnName = column.getName();
                }
                
            } else {
                Column c = mf.getSchema().getTable(childTable.getName()).getColumnByName(column.getName());
                if (c == null) {
                    c = addColumn(mf, childTable, column, targetColumnName);
                    c.setProperty(PSEUDO, column.getName());
                } else {
                    targetColumnName = column.getName();
                }
            }
            columnNames.add(targetColumnName);
            
            // if there are no constraints are available then, define some implicit ones 
            if (childTable.getProperty(CONSTRAINT_PROPERTY + i, false) == null) {
                childTable.setProperty(CONSTRAINT_PROPERTY + i, targetColumnName);
                childTable.setProperty(CONSTRAINT_REF_PROPERTY + i, column.getName());
            }
            i++;
        }
        
        if (isComplexType(childTable)) {
            mf.addPrimaryKey("PK0", columnNames, childTable);
        }
    }

    private void addForeignKey(MetadataFactory mf, Table childTable, Table parentTable) 
            throws TranslatorException {
        String fkName = childTable.getProperty(FK_NAME, false);
        childTable.setProperty(FK_NAME, null);
        if (fkName == null) {
            fkName = "FK0";
        }
        
        int i = 0;
        ArrayList<String> keyColumns = new ArrayList<String>();
        ArrayList<String> refColumns = new ArrayList<String>();
        while(true) {
            if (childTable.getProperty(CONSTRAINT_PROPERTY + i, false) == null) {
                break;
            }
            keyColumns.add(childTable.getProperty(CONSTRAINT_PROPERTY + i, false));
            refColumns.add(childTable.getProperty(CONSTRAINT_REF_PROPERTY + i, false));
            
            childTable.setProperty(CONSTRAINT_PROPERTY + i, null);
            childTable.setProperty(CONSTRAINT_REF_PROPERTY + i, null);
            
            i++;
        }
        mf.addForiegnKey(fkName, keyColumns, refColumns, parentTable.getName(), childTable); //$NON-NLS-1$
    }    

    boolean same(Table x, Table y) {
        return (x.getFullName().equalsIgnoreCase(y.getFullName()));
    }

    boolean keyMatches(List<String> names, KeyRecord record) {
        if (names.size() != record.getColumns().size()) {
            return false;
        }
        Set<String> keyNames = new TreeSet<String>(
                String.CASE_INSENSITIVE_ORDER);
        for (Column c : record.getColumns()) {
            keyNames.add(c.getName());
        }
        for (int i = 0; i < names.size(); i++) {
            if (!keyNames.contains(names.get(i))) {
                return false;
            }
        }
        return true;
    }

    private Column addPropertyAsColumn(MetadataFactory mf, Table table,
            CsdlProperty property) {

        Column c = buildColumn(mf, table, property);

        c.setNullType(property.isNullable() ? NullType.Nullable
                : NullType.No_Nulls);

        if (property.getMaxLength() != null) {
            c.setLength(property.getMaxLength());
        }
        if (property.getPrecision() != null) {
            c.setPrecision(property.getPrecision());
        }
        if (property.getScale() != null) {
            c.setScale(property.getScale());
        }
        if (property.getDefaultValue() != null) {
            c.setDefaultValue(property.getDefaultValue());
        }

        if (property.getMimeType() != null) {
            c.setProperty("MIME-TYPE", property.getMimeType());
        }

        if (property.getSrid() != null) {
            c.setProperty("SRID", property.getSrid().toString());
        }
        if (!property.getType().equals("Edm.String")) {
            if (property.isCollection()) {
                c.setNativeType("Collection("+property.getType()+")");
            } else {
                c.setNativeType(property.getType());
            }
        }
        if (property.getType().equals("Edm.String") && property.isCollection()) {
            c.setNativeType("Collection("+property.getType()+")");
        }        
        return c;
    }
    
    private Column addColumn(MetadataFactory mf, Table table, Column property, String newName) {
        Column c = mf.addColumn(newName, property.getRuntimeType(),table);
        c.setUpdatable(true);
        c.setNullType(property.getNullType());
        c.setLength(property.getLength());
        c.setPrecision(property.getPrecision());
        c.setScale(property.getScale());
        c.setDefaultValue(property.getDefaultValue());
        c.setProperty("MIME-TYPE", c.getProperty("MIME-TYPE", false));
        c.setProperty("SRID", property.getProperty("SRID", false));
        c.setProperty("NATIVE_TYPE", property.getProperty("NATIVE_TYPE", false));
        return c;
    }

    private ProcedureParameter addParameterAsColumn(MetadataFactory mf,
            Procedure procedure, CsdlParameter parameter) {
        ProcedureParameter p = mf.addProcedureParameter(
                parameter.getName(),
                ODataTypeManager.teiidType(parameter.getType(),parameter.isCollection()), 
                ProcedureParameter.Type.In,
                procedure);
        
        p.setNullType(parameter.isNullable()?NullType.Nullable:NullType.No_Nulls);
        
        if (parameter.getMaxLength() != null) {
            p.setLength(parameter.getMaxLength());
        }
        if (parameter.getPrecision() != null) {
            p.setPrecision(parameter.getPrecision());
        }
        if (parameter.getScale() != null) {
            p.setScale(parameter.getScale());
        }
        if (parameter.getSrid() != null) {
            p.setProperty("SRID", parameter.getSrid().toString());
        }
        return p;
    }    

    private Column buildColumn(MetadataFactory mf, Table table, CsdlProperty property) {
        String columnName = property.getName();
        Column c = mf.addColumn(columnName, ODataTypeManager.teiidType(property.getType(), 
                property.isCollection()), table);
        if(property.isCollection()) {
            c.setSearchType(SearchType.Unsearchable);
        }
        c.setUpdatable(true);
        return c;
    }

    private void addParameter(MetadataFactory mf, XMLMetadata metadata,
            Procedure procedure, CsdlParameter parameter) throws TranslatorException {
        if (isSimple(parameter.getType())) {
            addParameterAsColumn(mf, procedure, parameter);
        }
        else {
            throw new TranslatorException(ODataPlugin.Util.gs(
                    ODataPlugin.Event.TEIID17022, parameter.getName()));
        }
    }
    
    void addFunctionImportAsProcedure(MetadataFactory mf,
            CsdlFunctionImport functionImport, ODataType odataType, XMLMetadata metadata)
            throws TranslatorException {
        List<CsdlFunction> functions = getFunctions(metadata,
                functionImport.getFunction());
        for (CsdlFunction function : functions) {
            Procedure procedure = mf.addProcedure(function.getName());
            addOperation(mf, metadata, odataType, function, procedure);
        }
    }

    private void addProcedureTableReturn(MetadataFactory mf, XMLMetadata metadata, Procedure procedure, 
            CsdlComplexType type, String namePrefix) throws TranslatorException {
        for (CsdlProperty property:type.getProperties()) {
            if (isSimple(property.getType()) || isEnum(metadata, property.getType())) {                 
                mf.addProcedureResultSetColumn(
                        namePrefix == null? property.getName():namePrefix+"_"+property.getName(),
                        ODataTypeManager.teiidType(property.getType(), property.isCollection()), procedure);
            }
            else if (isComplexType(metadata, property.getType())) {
                CsdlComplexType childType = (CsdlComplexType)getComplexType(metadata, property.getType());
                addProcedureTableReturn(mf, metadata, procedure, childType, property.getName());
            }
            else {
                throw new TranslatorException(ODataPlugin.Util.gs(
                        ODataPlugin.Event.TEIID17023, procedure.getName()));
            }
        }
    }
    
    private void addProcedureTableReturn(MetadataFactory mf, XMLMetadata metadata, Procedure procedure, 
            CsdlEntityType type, String namePrefix) throws TranslatorException {
        for (CsdlProperty property:type.getProperties()) {
            if (isSimple(property.getType()) || isEnum(metadata, property.getType())) {
                mf.addProcedureResultSetColumn(
                        namePrefix == null? property.getName():namePrefix+"_"+property.getName(),
                        ODataTypeManager.teiidType(property.getType(), property.isCollection()), procedure);
            }
            else if (isComplexType(metadata, property.getType())) {
                CsdlComplexType childType = (CsdlComplexType)getComplexType(metadata, property.getType());
                addProcedureTableReturn(mf, metadata, procedure, childType, property.getName());
            }            
            else {
                throw new TranslatorException(ODataPlugin.Util.gs(
                        ODataPlugin.Event.TEIID17023, procedure.getName()));
            }
        }
    }
    
    private void addActionImportAsProcedure(MetadataFactory mf, CsdlActionImport actionImport, 
            ODataType odataType, XMLMetadata metadata) throws TranslatorException {
        List<CsdlAction> actions = getActions(metadata, actionImport.getAction());
        
        for (CsdlAction action : actions) {
            if (!hasComplexParameters(action.getParameters())) { 
                Procedure procedure = mf.addProcedure(action.getName());
                addOperation(mf, metadata, odataType, action, procedure);
            } else {
                LogManager.logInfo(LogConstants.CTX_ODATA, 
                        ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17033, action.getName()));
            }
        }
        
    }

    private boolean hasComplexParameters(List<CsdlParameter> parameters) {
        for (CsdlParameter parameter:parameters) {
            if (!isSimple(parameter.getType())) {
                return false;
            }
        }
        return false;
    }

    private void addOperation(MetadataFactory mf, XMLMetadata metadata, ODataType odataType,
            CsdlOperation function, Procedure procedure)
            throws TranslatorException {
        
        procedure.setProperty(ODATA_TYPE, odataType.name());
        
        for (CsdlParameter parameter : function.getParameters()) {
            addParameter(mf, metadata, procedure, parameter);
        }
        
        CsdlReturnType returnType = function.getReturnType();
        if (returnType != null) {
            if (isSimple(returnType.getType())) {
                mf.addProcedureParameter("return", ODataTypeManager.teiidType(returnType.getType(), 
                        returnType.isCollection()), ProcedureParameter.Type.ReturnValue, procedure); 
            } 
            else if (isComplexType(metadata, returnType.getType())) {
                addProcedureTableReturn(mf, metadata, procedure,
                        getComplexType(metadata, returnType.getType()), null);
                procedure.getResultSet().setProperty(ODATA_TYPE, 
                        returnType.isCollection()?ODataType.COMPLEX_COLLECTION.name():ODataType.COMPLEX.name());
            }
            else if (isEntityType(metadata, returnType.getType())){
                addProcedureTableReturn(mf, metadata, procedure,
                        getEntityType(metadata, returnType.getType()), null);
                procedure.getResultSet().setProperty(ODATA_TYPE, 
                        returnType.isCollection()?ODataType.ENTITY_COLLECTION.name():ODataType.ENTITY.name());
            }
            else {
                throw new TranslatorException(ODataPlugin.Util.gs(
                        ODataPlugin.Event.TEIID17005, function.getName(),
                        returnType.getType()));
            }
        }
    }

    public void setSchemaNamespace(String namespace) {
        this.schemaNamespace = namespace;
    }

    List<String> getColumnNames(List<Column> columns) {
        ArrayList<String> names = new ArrayList<String>();
        for (Column c : columns) {
            names.add(c.getName());
        }
        return names;
    }

    @TranslatorProperty(display="Schema Namespace", category=PropertyType.IMPORT, description="Namespace of the schema to import")
    public String getSchemaNamespace() {
        return schemaNamespace;
    }
}
