package org.apache.ojb.broker.util.sequence;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.ojb.broker.Identity;
import org.apache.ojb.broker.PBFactoryException;
import org.apache.ojb.broker.PBKey;
import org.apache.ojb.broker.PersistenceBroker;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.PersistenceBrokerFactory;
import org.apache.ojb.broker.metadata.ClassDescriptor;
import org.apache.ojb.broker.query.Query;
import org.apache.ojb.broker.util.logging.Logger;
import org.apache.ojb.broker.util.logging.LoggerFactory;

/**
 * threadsafe sequence generator
 *
 * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
 * @version $Id: SequenceGenerator.java,v 1.5 2002/09/21 16:08:17 brj Exp $
 */
public class SequenceGenerator
{
    private static Logger log = LoggerFactory.getLogger(SequenceGenerator.class);
    private static final String SM_SELECT_MAX = "SELECT MAX(";
    private static final String SM_FROM = ") FROM ";

    /**
     * Return the next sequence of keys.
     * @param brokerForClass PB instance responsible for the given class
     * @param clazz Class of the object, we need the next sequence
     * @param targetSequence Sequence who was exhausted
     * @param keyForSequenceOperation Optional. {@link org.apache.ojb.broker.PBKey}
     * used to lookup a separate PB to perform key generation (see {@link SequenceGenerator})
     */
    public synchronized static HighLowSequence getNextSequence(PersistenceBroker brokerForClass,
                                                               Class clazz, HighLowSequence targetSequence,
                                                               PBKey keyForSequenceOperations)
    {
        PersistenceBroker pb_seq = null;
        HighLowSequence newSequence = null;
        try
        {
            if (log.isDebugEnabled()) log.debug("prepare next sequence for " + clazz + " with sequence " + targetSequence);
            //look for sequence in db
            pb_seq = getBrokerForSequenceOperations(keyForSequenceOperations);
            pb_seq.beginTransaction();
            Identity targetSequenceOid = new Identity(targetSequence);
            newSequence = (HighLowSequence) pb_seq.getObjectByIdentity(targetSequenceOid);
            pb_seq.commitTransaction();


            //not in db --> get max id in objects table
            if (newSequence == null)
            {
                if (log.isDebugEnabled()) log.debug("sequence not found in db, create new");
                int maxId = getMaxForExtent(brokerForClass, clazz, targetSequence.getFieldName());
                targetSequence.setMaxKey(maxId);
                newSequence = targetSequence;
            }

            //set current grab size
            newSequence.setGrabSize(targetSequence.getGrabSize());
            //use copy to avoid sync problems!!
            newSequence = newSequence.getCopy();
            //grab the next key scope
            newSequence.grabNextKeySet();
            //store the sequence to db
            pb_seq.beginTransaction();
            pb_seq.store(newSequence);
            pb_seq.commitTransaction();

            if (log.isDebugEnabled()) log.debug("new sequence for " + clazz + " was " + newSequence);
        }
        catch (Exception e)
        {
            log.error("Can not get next " + HighLowSequence.class.getName() + " for next scope of keys", e);
            throw new PersistenceBrokerException(e);
        }
        finally
        {
            if (pb_seq != null) pb_seq.close();
        }
        return newSequence;
    }

    /**
     * @see #getNextSequence(PersistenceBroker brokerForClass,Class clazz, HighLowSequence targetSequence,PBKey keyForSequenceOperations)
     */
    public synchronized static HighLowSequence getNextSequence(PersistenceBroker brokerForClass,
                                                               Class clazz, HighLowSequence targetSequence)
    {
        return getNextSequence(brokerForClass, clazz, targetSequence, null);
    }

    /**
     * lookup all tables in extent clazz to find the current maximum value for fieldName
     */
    private static int getMaxForExtent(PersistenceBroker brokerForClass, Class clazz,
                                       String fieldName) throws PersistenceBrokerException
    {
        int max = 0;
        ClassDescriptor cld = brokerForClass.getClassDescriptor(clazz);
        // Note! MAR
        if ( log.isDebugEnabled() ) {
            log.debug("getMaxForExtent: " + clazz);
        }
        // if class is no Interface we have to search its directly mapped table
        if (!cld.isInterface())
        {
            int tmp = getMaxIdForClass(brokerForClass, cld, fieldName);
            if (tmp > max)
            {
                max = tmp;
            }
        }
        // if class is an extent we have to search through its subclasses
        if (cld.isExtent())
        {
            java.util.Vector extentClasses = cld.getExtentClasses();
            for (int i = 0; i < extentClasses.size(); i++)
            {
                Class ec = (Class) extentClasses.get(i);
                cld = brokerForClass.getClassDescriptor(ec);
                // Note! MAR Call getmaxForExtent recursively
                // Changed from int tmp = getMaxIdForClass(brokerForClass, cld, fieldName);                
                int tmp = getMaxForExtent( brokerForClass, ec, fieldName );
                if (tmp > max)
                {
                    max = tmp;
                }
            }
        }
        return max;
    }

    /**
     * lookup current maximum value for fieldName in table cld.getTableName()
     */
    private static int getMaxIdForClass(PersistenceBroker brokerForClass, ClassDescriptor cld, String fieldName) throws PersistenceBrokerException
    {
        int result = 0;
        ResultSet rs = null;
        Statement stmt = null;
        String table = cld.getFullTableName();
        String column = cld.getFieldDescriptorByName(fieldName).getColumnName();
        String sql = SM_SELECT_MAX + column + SM_FROM + table;
        try
        {
            //lookup max id for the current class
            stmt = brokerForClass.getStatementManager().getGenericStatement(cld, Query.NOT_SCROLLABLE);
            rs = stmt.executeQuery(sql);
            rs.next();
            result = rs.getInt(1);
        }
        catch (Exception e)
        {
            log.error("Cannot lookup max id from table " + table + " for column " + column+
            ", PB was "+brokerForClass+", using jdbc-descriptor " +
            brokerForClass.getDescriptorRepository().getDefaultJdbcConnection(), e);
        }
        finally
        {
            try
            {
                if (rs != null) rs.close();
                if (stmt != null) stmt.close();
            }
            catch (SQLException e)
            {
                log.error("method cleanup fails", e);
            }
        }
        return result;
    }

    private static PersistenceBroker getBrokerForSequenceOperations(PBKey key)
    {
        try
        {
            if(key == null)
            {
                return PersistenceBrokerFactory.defaultPersistenceBroker();
            }
            else
            {
                return PersistenceBrokerFactory.createPersistenceBroker(key);
            }
        }
        catch (PBFactoryException e)
        {
            log.error("Can not obtain a PB instance", e);
            throw new PersistenceBrokerException(e);
        }
    }
}

