/*
 * Copyright 2010 Werner Guttmann
 *
 * Licensed 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.castor.cpa.persistence.sql.keygen;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.core.util.Messages;
import org.castor.cpa.jpa.info.JPATableGeneratorDescriptor;
import org.castor.cpa.persistence.sql.keygen.typehandler.KeyGeneratorTypeHandler;
import org.castor.cpa.persistence.sql.keygen.typehandler.KeyGeneratorTypeHandlerFactory;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.persist.spi.PersistenceFactory;

public class TableKeyGenerator extends AbstractBeforeKeyGenerator {

    private static final Log LOG = LogFactory.getLog(TableKeyGenerator.class);
    
    public static final String DESCRIPTOR_KEY = "descriptor";
    public static final String DEFAULT_TABLE_NAME = "GENERATOR_TABLE";
    public static final int DEFAULT_ALLOCATION_SIZE = 50;
    public static final int DEFAULT_INITIAL_VALUE = 0;
    public static final String DEFAULT_PK_COLUMN_NAME = "ID_NAME";
    public static final String DEFAULT_VALUE_COLUMN_NAME = "ID_VALUE";
    public static final String DEFAULT_PK_COLUMN_VALUE = "ID_GEN";

    private PersistenceFactory factory;
    private JPATableGeneratorDescriptor descriptor;
    private int sqlType;

    /**
     * Creates an instance of this key generator.
     * 
     * @param factory The current {@link PersistenceFactory} instance.
     * @param params Parameters for the key generator.
     * @param sqlType The SQL type of the identity field.
     * @throws MappingException
     */
    public TableKeyGenerator(PersistenceFactory factory, Properties params,
            int sqlType) throws MappingException {
        super(factory);
        this.factory = factory;
        this.sqlType = sqlType;
        this.descriptor = (JPATableGeneratorDescriptor) params.get(DESCRIPTOR_KEY);

        initializeDescriptor();

        assertNumericSqlType(sqlType);
    }

    private void assertNumericSqlType(int sqlType) throws MappingException {
        switch (sqlType) {
        case Types.BIGINT:
        case Types.SMALLINT:
        case Types.INTEGER:
        case Types.DECIMAL:
        case Types.NUMERIC:
            break;
        default:
            String msg = Messages.format("mapping.keyGenSQLType", getClass()
                    .getName(), sqlType);
            throw new MappingException(msg);
        }
    }

    private void initializeDescriptor() {
        if (descriptor == null) {
            descriptor = new JPATableGeneratorDescriptor();
        }
        if ((descriptor.getTable() == null) || ("".equals(descriptor.getTable()))) {
            descriptor.setTable(DEFAULT_TABLE_NAME);
        }
        if (descriptor.getAllocationSize() < 1) {
            descriptor.setAllocationSize(DEFAULT_ALLOCATION_SIZE);
        }
        if (descriptor.getInitialValue() < 0) {
            descriptor.setInitialValue(DEFAULT_INITIAL_VALUE);
        }
        if ((descriptor.getPkColumnName() == null)
                || ("".equals(descriptor.getPkColumnName()))) {
            descriptor.setPkColumnName(DEFAULT_PK_COLUMN_NAME);
        }
        if ((descriptor.getValueColumnName() == null)
                || ("".equals(descriptor.getValueColumnName()))) {
            descriptor.setValueColumnName(DEFAULT_VALUE_COLUMN_NAME);
        }
        if ((descriptor.getPkColumnValue() == null)
                || ("".equals(descriptor.getPkColumnValue()))) {
            descriptor.setPkColumnValue(DEFAULT_PK_COLUMN_VALUE);
        }
    }

    public Object generateKey(Connection connection, String tableName,
            String primKeyName) throws PersistenceException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Generating table-based primary key " + primKeyName
                    + " for table " + tableName);
        }
        String sql = "SELECT " + descriptor.getValueColumnName() + " FROM "
                + descriptor.getTable() + " WHERE "
                + descriptor.getPkColumnName() + "='"
                + descriptor.getPkColumnValue() + "'";
        Object key;
        try {
            PreparedStatement statement = connection.prepareStatement(sql);
            ResultSet result = statement.executeQuery();
            KeyGeneratorTypeHandler<?> handler = KeyGeneratorTypeHandlerFactory
                    .getTypeHandler(sqlType, descriptor.getAllocationSize());
            key = handler.getNextValue(result);
            sql = "UPDATE " + descriptor.getTable() + " SET "
                    + descriptor.getValueColumnName() + "="
                    + key + " WHERE "
                    + descriptor.getPkColumnName() + "='"
                    + descriptor.getPkColumnValue() + "'";
            statement = connection.prepareStatement(sql);
            statement.executeUpdate();
        } catch (SQLException e) {
            throw new PersistenceException(e.getMessage());
        } catch (MappingException e) {
            throw new PersistenceException(e.getMessage());
        }

        return key;
    }

    public boolean isInSameConnection() {
        return true;
    }

    public PersistenceFactory getFactory() {
        return factory;
    }

    public JPATableGeneratorDescriptor getDescriptor() {
        return descriptor;
    }

}
