/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.jdbc.meta;

import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.ClassMetaData;

/**
 * Simple {@link ReverseCustomizer} that uses a properties file to
 * to allow customization of basic class and field properties. The
 * customizer uses the following keys:
 * <ul>
 * <li><i>&lt;table name&gt;.table-type</i>: Override the default type of the
 * given table. Legal values are: <code>base, secondary,
 * secondary-outer, association, subclass, none</code>. See
 * the TABLE_XXX constants in {@link ReverseMappingTool} for descriptions.</li>
 * <li><i>&lt;class name&gt;.rename</i>: Override the tool-generated class name
 * with the given value. Use full class names, including package. Use a
 * value of <code>none</code> to reject the class and leave the
 * corresponding table unmapped.</li>
 * <li><i>&lt;table name&gt;.class-name</i>: Assign the given fully-qualified
 * class name to the type created from the given table. Use a value of
 * <code>none</code> to prevent mapping this table. This property can be
 * used in place of the <code>rename</code> property.
 * <li><i>&lt;class name&gt;.identity</i>: Set this property to
 * <code>datastore</code>, <code>builtin</code>, or the desired
 * fully-qualified application identity class name to override the
 * reverse mapping tool's default identity settings. If the class has been
 * renamed, use the new name.</li>
 * <li><i>&lt;class name&gt;.&lt;field name&gt;.rename</i>: Override the
 * tool-generated field name with the given value. Use the field owner's
 * full class name in the property key. The property value should be the
 * new field name, without the preceding class name. Use a value of
 * <code>none</code> to reject the generated mapping.</li>
 * <li><i>&lt;table name&gt;.&lt;column name&gt;.field-name</i>: Assign the
 * field name to use for the mapping of a particular column. If this is
 * a multi-column mapping, any one of the columns can be used. Use a value
 * of <code>none</code> to prevent the column (and associated columns)
 * from being mapped. This property can be used in place of the
 * <code>rename</code> property.
 * <li><i>&lt;class name&gt;.&lt;field name&gt;.type</i>: The class name of
 * the type to give the named field. Use full class names. If the field
 * has been renamed, use the new name.</li>
 * <li><i>&lt;class name&gt;.&lt;field name&gt;.value</i>: The initial value
 * for the named field. The given string will be placed as-is in the
 * generated Java code, so be sure to add quotes to strings, etc. If the
 * field has been renamed, use the new name.</li>
 * </ul> All keys are optional; if not specified, the customizer keeps the
 * default value generated by the reverse mapping tool.
 */
public class PropertiesReverseCustomizer
    implements ReverseCustomizer {

    private static final Localizer _loc = Localizer.forPackage
        (PropertiesReverseCustomizer.class);

    protected ReverseMappingTool tool = null;
    private Properties _props = null;
    private Set _unaccessed = null;

    @Override
    public void setConfiguration(Properties props) {
        _props = props;
        _unaccessed = new TreeSet(props.keySet());
    }

    @Override
    public void setTool(ReverseMappingTool tool) {
        this.tool = tool;
    }

    @Override
    public int getTableType(Table table, int defaultType) {
        String type = getProperty(table.getName() + ".table-type");
        if (type == null && table.getSchemaName() != null)
            type = getProperty(table.getFullName() + ".table-type");
        if (type == null)
            return defaultType;
        if ("none".equals(type))
            return ReverseMappingTool.TABLE_NONE;
        if ("base".equals(type))
            return ReverseMappingTool.TABLE_BASE;
        if ("secondary".equals(type))
            return ReverseMappingTool.TABLE_SECONDARY;
        if ("secondary-outer".equals(type))
            return ReverseMappingTool.TABLE_SECONDARY_OUTER;
        if ("association".equals(type))
            return ReverseMappingTool.TABLE_ASSOCIATION;
        if ("subclass".equals(type))
            return ReverseMappingTool.TABLE_SUBCLASS;
        throw new IllegalArgumentException(table.getName() + ".table-type: "
            + type);
    }

    @Override
    public String getClassName(Table table, String defaultName) {
        // check for a rename property or a table-naming property
        String name = getProperty(defaultName + ".rename");
        if (name == null) {
            name = getProperty(table.getName() + ".class-name");
            if (name == null && table.getSchemaName() != null)
                name = getProperty(table.getFullName() + ".class-name");
        }

        if (name == null) {
            if (tool.getLog().isTraceEnabled())
                tool.getLog().trace(_loc.get("custom-no-class",
                    defaultName, table));
            return defaultName;
        }

        if ("none".equals(name)) {
            if (tool.getLog().isInfoEnabled())
                tool.getLog().info(_loc.get("custom-rm-class",
                    defaultName, table));
            return null;
        }

        if (tool.getLog().isInfoEnabled())
            tool.getLog().info(_loc.get("custom-class", defaultName, name));
        return name;
    }

    @Override
    public void customize(ClassMapping cls) {
        // customize identity type
        String id = getProperty(cls.getDescribedType().getName()
            + ".identity");
        if ("datastore".equals(id)) {
            cls.setObjectIdType(null, false);
            cls.setIdentityType(ClassMetaData.ID_DATASTORE);
        } else if ("builtin".equals(id)) {
            cls.setObjectIdType(null, false);
            cls.setIdentityType(ClassMetaData.ID_APPLICATION);
        } else if (id != null)
            cls.setObjectIdType(tool.generateClass(id, null), false);
    }

    @Override
    public String getClassCode(ClassMapping mapping) {
        return null;
    }

    @Override
    public void customize(FieldMapping field) {
        String type = getProperty(field.getFullName(false) + ".type");
        if (type != null)
            field.setDeclaredType(ClassUtil.toClass(type, null));
    }

    @Override
    public String getFieldName(ClassMapping dec, Column[] cols, ForeignKey fk,
        String defaultName) {
        // check for a rename property or a column-naming property
        String name = getProperty(dec.getDescribedType().getName() + "."
            + defaultName + ".rename");
        for (int i = 0; name == null && i < cols.length; i++) {
            name = getProperty(cols[i].getTableName() + "."
                + cols[i].getName() + "." + "field-name");
            if (name == null && cols[i].getTable().getSchemaName() != null)
                name = getProperty(cols[i].getTable().getFullName()
                    + "." + cols[i].getName() + "." + "field-name");
        }

        if (name == null) {
            if (tool.getLog().isTraceEnabled())
                tool.getLog().trace(_loc.get("custom-no-field", defaultName,
                    dec));
            return defaultName;
        }

        if ("none".equals(name)) {
            if (tool.getLog().isInfoEnabled())
                tool.getLog().info(_loc.get("custom-rm-field", defaultName,
                    dec));
            return null;
        }

        if (tool.getLog().isInfoEnabled())
            tool.getLog().info(_loc.get("custom-field", defaultName, dec,
                name));
        return name;
    }

    @Override
    public String getInitialValue(FieldMapping field) {
        return getProperty(field.getFullName(false) + ".value");
    }

    @Override
    public String getDeclaration(FieldMapping field) {
        return null;
    }

    @Override
    public String getFieldCode(FieldMapping field) {
        return null;
    }

    @Override
    public boolean unmappedTable(Table table) {
        return false;
    }

    @Override
    public void close() {
        if (!_unaccessed.isEmpty() && tool.getLog().isTraceEnabled())
            tool.getLog().trace(_loc.get("custom-unused-props", _unaccessed));
    }

    /**
     * Return the property value for the given key, or null if none.
     */
    protected String getProperty(String key) {
        String val = StringUtil.trimToNull(_props.getProperty(key));
        _unaccessed.remove(key);
        return val;
    }
}
