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

import org.apache.ojb.broker.PersistenceBroker;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.PBKey;
import org.apache.ojb.broker.util.configuration.Configurable;
import org.apache.ojb.broker.util.configuration.Configuration;
import org.apache.ojb.broker.util.configuration.ConfigurationException;
import org.apache.ojb.broker.util.configuration.impl.OjbConfigurator;
import org.apache.ojb.broker.util.logging.Logger;
import org.apache.ojb.broker.util.logging.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

/**
 * HighLowSequence manager. This manger itself is not threadsafe, but the
 * id generation was {@link SequenceGenerator}.
 *
 * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
 */
public class SequenceManagerHiLoImpl implements SequenceManager, Configurable
{
    private Logger log = LoggerFactory.getLogger(SequenceManagerHiLoImpl.class);
    private static final String GLOBAL_SEQUENCE_NAME = "global - ";

    private Map sequences;
    private int grabSize;
    private boolean globalSequence;
    private PersistenceBroker brokerForClass;
    // if not null, this induce SequenceGenerator to
    // use a different DB for sequence generation
    private PBKey keyToSeparateSeqTable = null;


    public SequenceManagerHiLoImpl(PersistenceBroker broker)
    {
        this.brokerForClass = broker;
        sequences = new HashMap();
        OjbConfigurator.getInstance().configure(this);
    }

    public void configure(Configuration pConfig) throws ConfigurationException
    {
        SequenceConfiguration conf = (SequenceConfiguration) pConfig;
        grabSize = conf.getSequenceManagerGrabSize();
        keyToSeparateSeqTable = conf.getSeparatePBKey();
    }

    /**
     * Gets a new uniqueId for the given Class and fieldname
     */
    public int getUniqueId(Class clazz, String fieldName)
            throws PersistenceBrokerException
    {
        if ( log.isDebugEnabled() ) {
            log.debug("getUniqueId: " + clazz + "\t" + fieldName);
        }
        HighLowSequence seq;
        String seqName;

        if (globalSequence)
        {
            seqName = GLOBAL_SEQUENCE_NAME + fieldName;
        }
        else
        {
            /**
             * MBAIRD
             * Should not use classname for the sequenceName as we will end up
             * re-using sequence numbers for classes mapped to the same table.
             * Instead, make the FullTableName the discriminator since it will
             * always be unique for that table, and hence that class.
             */      
            if (brokerForClass.getClassDescriptor(clazz).getExtentClasses().size() > 0)
            {                
                if ( log.isDebugEnabled() ) {
                    log.debug("\tHAS extent classes");
                }
                // Note! MAR Because of changes in multi-level extents need to use the fq class name (
                //      as long as it it is not longer wider than the field it is stored in 175 chars I think)
                // Changed from:
                //seqName = brokerForClass.getClassDescriptor(
                //        (Class) (brokerForClass.getClassDescriptor(clazz).getExtentClasses().get(0)))
                //        .getFullTableName();
                seqName = clazz.getName();
                if ( seqName.length() > 175 ) {
                    // use last 175 chars as these are likely to change "more" than first 175
                    seqName = seqName.substring(seqName.length() - 175);
                }
                if ( log.isDebugEnabled() ) {
                    log.debug("\tUsing full class name: " + seqName);
                }
            }
            else
            {
                seqName = brokerForClass.getClassDescriptor(clazz).getFullTableName();
                if ( log.isDebugEnabled() ) {
                    log.debug("\tNOT an extent");
                    log.debug("\tUsing full table name: " + seqName);
                }
            }
        }
        //try to found sequence in map
        seq = (HighLowSequence) sequences.get(seqName);

        if (seq == null)
        {
            //look up sequence
            seq = new HighLowSequence();
            seq.setTableName(seqName);
            seq.setFieldName(fieldName);
            seq.setGrabSize(grabSize);
            seq = SequenceGenerator.getNextSequence(brokerForClass, clazz, seq, keyToSeparateSeqTable);
            sequences.put(seqName, seq);
        }

        //now we have a sequence
        int id = seq.getNextId();
        if (id == 0)
        {
            //seq does not have reserved IDs => reload seq
            seq = SequenceGenerator.getNextSequence(brokerForClass, clazz, seq, keyToSeparateSeqTable);
            // replace old sequence!!
            sequences.put(seqName, seq);
            id = seq.getNextId();
        }
        if (id == 0)
        {
            throw new PersistenceBrokerException("Thread: " + Thread.currentThread() + " PB: " + brokerForClass + ". Unable to build new ID");
        }
        else
        {
            return id;
        }
    }

    /**
     * returns a unique String for class clazz and field fieldName.
     * the returned uid is unique accross all tables in the extent of clazz.
     *
     */
    public String getUniqueString(Class clazz, String fieldName)
    {
        return Integer.toString(getUniqueId(clazz, fieldName));
    }

    /**
     * returns a unique long value for class clazz and field fieldName.
     * the returned number is unique accross all tables in the extent of clazz.
     */
    public long getUniqueLong(Class clazz, String fieldName)
    {
        return (long) getUniqueId(clazz, fieldName);
    }

    /**
     * returns a unique Object for class clazz and field fieldName.
     * the returned Object is unique accross all tables in the extent of clazz.
     */
    public Object getUniqueObject(Class clazz, String fieldName)
    {
        return getUniqueString(clazz, fieldName);
    }


    public int getGrabSize()
    {
        return grabSize;
    }

    public void setGrabSize(int grabSize)
    {
        this.grabSize = grabSize;
    }

    public boolean isGlobalSequence()
    {
        return globalSequence;
    }

    public void setGlobalSequence(boolean globalSequence)
    {
        this.globalSequence = globalSequence;
    }
}
