// Copyright (c) 1998, 2006, Oracle. All rights reserved.  
package oracle.toplink.essentials.internal.ejb.cmp3.xml.sequencing;

import java.util.HashMap;

import java.util.Iterator;
import oracle.toplink.essentials.internal.ejb.cmp3.xml.TableInfo;
import oracle.toplink.essentials.internal.helper.DatabaseField;
import oracle.toplink.essentials.exceptions.ValidationException;
import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.internal.helper.DatabaseTable;
import oracle.toplink.essentials.sequencing.*;
import oracle.toplink.essentials.sessions.DatasourceLogin;
import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataHelper;

public class SequencingProcessor {
    public static final String AUTO = "AUTO";
    public static final String IDENTITY = "IDENTITY";
    public static final String NONE = "NONE";
    public static final String TABLE = "TABLE";
    public static final String SEQUENCE = "SEQUENCE";
    public static final String DEFAULT_AUTO_GENERATOR = "SEQ_GEN";
    public static final String DEFAULT_TABLE_GENERATOR = "SEQ_GEN_TABLE";
    public static final String DEFAULT_SEQUENCE_GENERATOR = "SEQ_GEN_SEQUENCE";
    
    // SequenceGenerators keyed on generator name
    private HashMap sequenceGenerators;
    
    // TableGenerators keyed on generator name
    private HashMap tableGenerators;

    // Ids keyed on descriptor
    private HashMap ids;
    
    public SequencingProcessor() {
        tableGenerators = new HashMap();
        sequenceGenerators = new HashMap();
        ids = new HashMap();
    }
	
	/**
     *
	 */
    public void preProcessTableGenerator(TableGenerator tableGenerator) {
        if(tableGenerator == null) {
    		return;
        }

        String generatorName = tableGenerator.name();
        
        // DEFAULT_SEQUENCE_GENERATOR can't be used as TableGenerator name
        if(generatorName.equals(DEFAULT_SEQUENCE_GENERATOR)) {
            // TODO: change the exception
            throw ValidationException.reservedName("objectUsingReservedName", DEFAULT_SEQUENCE_GENERATOR, "correctType");
        }
        
        TableGenerator otherTableGenerator = (TableGenerator) tableGenerators.get(generatorName);
        if(otherTableGenerator != null) {
            if(otherTableGenerator.equals(tableGenerator)) {
                // duplication
                return;
            }
        }
        Iterator tableGeneratorIt = tableGenerators.values().iterator();
        while (tableGeneratorIt.hasNext()) {
            checkForConflict((TableGenerator) tableGeneratorIt.next(), tableGenerator);
        }

        Iterator sequenceGeneratorIt = sequenceGenerators.values().iterator();
        while (sequenceGeneratorIt.hasNext()) {
            checkForConflict((SequenceGenerator) sequenceGeneratorIt.next(), tableGenerator);
        }
        
        tableGenerators.put(generatorName, tableGenerator);
	}
	
	/**
     *
	 */
    public void preProcessSequenceGenerator(SequenceGenerator sequenceGenerator) {
        if(sequenceGenerator == null) {
    		return;
        }

        String generatorName = sequenceGenerator.name();

        // DEFAULT_TABLE_GENERATOR can't be used as SequenceGenerator name
        if(generatorName.equals(DEFAULT_TABLE_GENERATOR)) {
            // TODO: change the exception
            throw ValidationException.reservedName("objectUsingReservedName", DEFAULT_TABLE_GENERATOR, "correctType");
        }
        
        SequenceGenerator otherSequenceGenerator = (SequenceGenerator) sequenceGenerators.get(generatorName);
        if(otherSequenceGenerator != null) {
            if(otherSequenceGenerator.equals(sequenceGenerator)) {
                // duplication
                return;
            }
        }
        Iterator sequenceGeneratorIt = sequenceGenerators.values().iterator();
        while (sequenceGeneratorIt.hasNext()) {
            checkForConflict((SequenceGenerator) sequenceGeneratorIt.next(), sequenceGenerator);
        }

        Iterator tableGeneratorIt = tableGenerators.values().iterator();
        while (tableGeneratorIt.hasNext()) {
            checkForConflict((TableGenerator) tableGeneratorIt.next(), sequenceGenerator);
        }
        
        sequenceGenerators.put(generatorName, sequenceGenerator);
	}

	/**
     * id argument should have been obtained from annotatedElement argument:
     * Id id = getAnnotation(annotatedElement, Id.class);
	 */
	public void preProcessId(Id id, DatabaseField field, ClassDescriptor descriptor) {
        if (id == null) {
            return;
        }
        
        String generatorType = id.strategy();
        String generatorName = id.generator();
        if (generatorType.equals(AUTO)) {
            if(generatorName.equals("")) {
                // if the generator type is NONE, bail!
                return;
            } else {
                // TODO: change the exception
                throw ValidationException.idAnnotationCannotSpecifyGenerator("annotation", generatorType.toString());
            }
        } 
        
        Id otherId = (Id) ids.get(descriptor);
        if(otherId != null) {
            throwConflictException(id, otherId);
        }
        if (generatorType.equals(AUTO)) {
            if(generatorName.equals("")) {
                // nothing to do
            } else {
                // TODO: fix this exception
                throw ValidationException.idAnnotationCannotSpecifyGenerator("annotation", generatorType.toString());
            }
        }
        // set the sequence number field on the descriptor
        descriptor.setSequenceNumberField(field);

        Iterator idIt = ids.values().iterator();
        while (idIt.hasNext()) {
            checkForConflict((Id) idIt.next(), id);
        }
        
        ids.put(descriptor, id);
    }

	/**
     * Performs sequencing setup on Login and Descriptors
	 */
	public void process(DatasourceLogin login) {
        if(ids.isEmpty()) {
            // sequencing is not used
            return;
        }

        // Generators referenced from Id should have correct type
        Iterator idIt = ids.values().iterator();
        while (idIt.hasNext()) {
            Id id = (Id) idIt.next();
            String type = id.strategy();
            String generatorName = id.generator();
            if(type.equals(TABLE)) {
                SequenceGenerator sequenceGenerator = (SequenceGenerator) sequenceGenerators.get(generatorName);
                if(sequenceGenerator != null) {
                    throwConflictException(id, sequenceGenerator);
                }
            } else if(type.equals(SEQUENCE) || type.equals(IDENTITY)) {
                TableGenerator tableGenerator = (TableGenerator) tableGenerators.get(generatorName);
                if(tableGenerator != null) {
                    throwConflictException(id, tableGenerator);
                }
            }
        }

        Sequence defaultAutoSequence = null;
        NativeSequence defaultNativeSequence = new NativeSequence(DEFAULT_SEQUENCE_GENERATOR);
        TableSequence defaultTableSequence = new TableSequence(DEFAULT_TABLE_GENERATOR);
        // Sequences keyed on generator names.
        HashMap sequences = new HashMap();
        
        String seqName;
        Iterator seqIt = sequenceGenerators.values().iterator();
        while (seqIt.hasNext()) {
            SequenceGenerator sequenceGenerator = (SequenceGenerator) seqIt.next();
            String sequenceGeneratorName = sequenceGenerator.name();
            if(sequenceGenerator.sequenceName().equals("")) {
                seqName = sequenceGeneratorName;
            } else {
                seqName = sequenceGenerator.sequenceName();
            }
            NativeSequence sequence = new NativeSequence(seqName, sequenceGenerator.allocationSize());
            sequences.put(sequenceGeneratorName, sequence);
            if(sequenceGeneratorName.equals(DEFAULT_AUTO_GENERATOR)) {
                // The user has defined SequenceGenerator with DEFAULT_AUTO_GENERATOR.
                // The sequence it defines will be used as a defaultSequence.
                defaultAutoSequence = sequence;
            } else if(sequenceGeneratorName.equals(DEFAULT_SEQUENCE_GENERATOR)) {
                // The user has defined SequenceGenerator with DEFAULT_SEQUENCE_GENERATOR.
                // All sequences of GeneratorType SEQUENCE and IDENTITY referencing non-defined generators
                // will use a clone of the sequence defined by this generator.
                defaultNativeSequence = sequence;
            }
        }
        
        Iterator tableIt = tableGenerators.values().iterator();
        while (tableIt.hasNext()) {
            TableGenerator tableGenerator = (TableGenerator) tableIt.next();
            String tableGeneratorName = tableGenerator.name();
            
            if (tableGenerator.pkColumnValue().equals("")) {
                seqName = tableGeneratorName;
            } else {
                seqName = tableGenerator.pkColumnValue();
            }
            
            TableSequence sequence = new TableSequence(seqName, tableGenerator.allocationSize());
            sequences.put(tableGeneratorName, sequence);
            
            TableInfo table = tableGenerator.table();
                
            if (table.specified()) {
                sequence.setTable(new DatabaseTable(MetadataHelper.getFullyQualifiedTableName(table.getTableName(), sequence.getTableName(), table.getCatalog(), table.getSchema())));
            }
                
            if (!tableGenerator.pkColumnName().equals("")) {
                sequence.setNameFieldName(tableGenerator.pkColumnName());
            }
                
            if (!tableGenerator.valueColumnName().equals("")) {
                sequence.setCounterFieldName(tableGenerator.valueColumnName());
            }
            
            if (tableGeneratorName.equals(DEFAULT_AUTO_GENERATOR)) {
                // The user has defined TableGenerator with DEFAULT_AUTO_GENERATOR.
                // The sequence it defines will be used as a defaultSequence.
                defaultAutoSequence = sequence;
            } else if (tableGeneratorName.equals(DEFAULT_TABLE_GENERATOR)) {
                // The user has defined SequenceGenerator with DEFAULT_TABLE_GENERATOR.
                // All sequences of GeneratorType TABLE referencing non-defined generators
                // will use a clone of the sequence defined by this generator.
                defaultTableSequence = sequence;
            }
        }

        // Finally loop through descriptors and set sequences as required into Descriptors and Login
        Iterator idKeyIt = ids.keySet().iterator();
        while (idKeyIt.hasNext()) {
            ClassDescriptor descriptor = (ClassDescriptor) idKeyIt.next();
            Id id = (Id) ids.get(descriptor);
            String generatorName = id.generator();
            Sequence sequence = null;
            if (!generatorName.equals("")) {
                sequence = (Sequence) sequences.get(generatorName);
            }
            
            if (sequence == null) {
                if (id.strategy().equals(TABLE)) {
                    if(generatorName.equals("")) {
                        sequence = defaultTableSequence;
                    } else {
                        sequence = (Sequence)defaultTableSequence.clone();
                        sequence.setName(generatorName);
                    }
                } else if(id.strategy().equals(SEQUENCE) || id.strategy().equals(IDENTITY)) {
                    if(generatorName.equals("")) {
                        sequence = defaultNativeSequence;
                    } else {
                        sequence = (Sequence)defaultNativeSequence.clone();
                        sequence.setName(generatorName);
                    }
                }
            }
            
            if (sequence != null) {
                descriptor.setSequenceNumberName(sequence.getName());
                login.addSequence(sequence);
            } else if(id.strategy().equals(AUTO)) {
                if(defaultAutoSequence != null) {
                    descriptor.setSequenceNumberName(defaultAutoSequence.getName());
                    login.setDefaultSequence(defaultAutoSequence);
                } else {
                    descriptor.setSequenceNumberName(DEFAULT_AUTO_GENERATOR);
                }
            } else {
                // should never happen
                assert false;
            }
        }
    }	
    
	/**
	 * Checks two TableGenerators objects for conflicts. Assumes that the 
     * objects are not equal (so may indicate a conflict if obj1==obj2).
     * If conflict is found then throws conflict exception.
	 */
    void checkForConflict(TableGenerator obj1, TableGenerator obj2) {
        boolean nameConflict = obj1.name().equals(obj2.name());
        
        // this needs further verification, very confusing ....
        
        boolean tableConflict = obj1.table().equals(obj2.table()) && (
            !obj1.pkColumnName().equals(obj2.pkColumnName()) ||
            !obj1.valueColumnName().equals(obj2.valueColumnName()));
        
        boolean sequenceConflict = obj1.pkColumnValue().equals(obj2.pkColumnValue()) && (
            obj1.initialValue() != obj2.initialValue() ||
            obj1.allocationSize() != obj2.allocationSize());
        
        if (nameConflict || sequenceConflict || tableConflict) {
            throwConflictException(obj1, obj2);
        }
    }

	/**
	 * Checks two SequenceGenerators objects for conflicts.
     * Assumes that the objects are not equal (so may indicate a conflict if obj1==obj2).
     * If conflict is found then throws conflict exception.
	 */
    void checkForConflict(SequenceGenerator obj1, SequenceGenerator obj2) {
        // this doesn't even check the initial value??
        
        boolean nameConflict = obj1.name().equals(obj2.name());
        
        boolean sequenceConflict = 
            obj1.sequenceName().equals(obj2.sequenceName()) && 
            obj1.allocationSize() != obj2.allocationSize();
            
        if (nameConflict || sequenceConflict) {
            throwConflictException(obj1, obj2);
        }
    }

	/**
	 * Checks SequenceGenerator, TableGenerator conflicts.
     * If conflict is found then throws conflict exception.
	 */
    void checkForConflict(SequenceGenerator obj1, TableGenerator obj2) {
        checkForConflict(obj2, obj1);
    }

	/**
	 * Checks TableGenerator, SequenceGenerator conflicts.
     * If conflict is found then throws conflict exception.
	 */
    void checkForConflict(TableGenerator obj1, SequenceGenerator obj2) {
        boolean nameConflict = obj1.name().equals(obj2.name());
        boolean sequenceConflict = obj1.pkColumnValue().equals(obj2.sequenceName());
        if(nameConflict || sequenceConflict) {
            throwConflictException(obj1, obj2);
        }
    }

	/**
	 * Checks two Id objects for conflicts.
     * Assumes that the objects are not equal (so may indicate a conflict if obj1==obj2).
     * If conflict is found then throws conflict exception.
	 */
    void checkForConflict(Id obj1, Id obj2) {
        if(!obj1.generator().equals(obj2.generator())) {
            return;
        } else if(obj1.generator().equals("")) {
            return;
        }
        String type1 = obj1.strategy();
        String type2 = obj2.strategy();
        if(type1.equals(type2)) {
            return;
        }
        if( type1.equals(IDENTITY) && type2.equals(SEQUENCE) ||
            type2.equals(IDENTITY) && type1.equals(SEQUENCE)) {
            return;    
        }
        throwConflictException(obj1, obj2);
    }

	/**
	 * Throws an exception indicating a conflict between two objects
	 */
    void throwConflictException(Object obj1, Object obj2) {
        // TODO:  nedda  different exception
        throw ValidationException.annotationsConflict(obj1.toString(), obj2.toString());
    }
}
