/*
 * Copyright 2006 Le Duc Bao, Ralf Joachim
 * 
 * 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.ddlgen.schemaobject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.ddlgen.DDLGenConfiguration;
import org.castor.ddlgen.DDLWriter;
import org.castor.ddlgen.GeneratorException;

/**
 * Abstract base class of all table implementations.
 * 
 * @author <a href="mailto:leducbao AT gmail DOT com">Le Duc Bao</a>
 * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a>
 * @version $Revision: 5951 $ $Date: 2006-04-25 16:09:10 -0600 (Tue, 25 Apr 2006) $
 * @since 1.1
 */
public abstract class Table extends AbstractSchemaObject {
    //--------------------------------------------------------------------------
    
    /** The <a href="http://jakarta.apache.org/commons/logging/">Jakarta Commons
     *  Logging </a> instance used for all logging. */
    private static final Log LOG = LogFactory.getLog(Table.class);

    //--------------------------------------------------------------------------

    /** List of indices for this table. */
    private List<Index> _indices = new ArrayList<Index>();
    
    /** List of foreign keys for this table. */
    private List<ForeignKey> _foreignKeys = new ArrayList<ForeignKey>();
    
    /** List of field for this table. */
    private List<Field> _fields = new ArrayList<Field>();
    
    /** Map of fields assoizated with their name. */
    private Map<String, Field> _fieldMap = new HashMap<String, Field>();
    
    /** Key generator used for identities of this table. */
    private KeyGenerator _keyGenerator;
    
    /** Primary key with identities of this table. */
    private PrimaryKey _primaryKey;

    /** Schema this table belongs to. */
    private Schema _schema;  
    
    //--------------------------------------------------------------------------

    /**
     * Add given index to list of indices.
     * 
     * @param index Index to add to list of indices.
     */
    public final void addIndex(final Index index) {        
        _indices.add(index);
    }
    
    /**
     * Get number of indices.
     * 
     * @return Number of indices.
     */
    public final int getIndexCount() {
        return _indices.size();
    }
    
    /**
     * Get index at given index.
     * 
     * @param index Index of index to return.
     * @return Index at given index.
     */
    public final Index getIndex(final int index) {
        return _indices.get(index);        
    }
    
    /**
     * Add given foreign key to list of foreign keys.
     * 
     * @param foreignKey Foreign key to add to list of foreign keys.
     */
    public final void addForeignKey(final ForeignKey foreignKey) {
        _foreignKeys.add(foreignKey);       
    }
    
    /**
     * Get number of foreign keys.
     * 
     * @return Number of foreign keys.
     */
    public final int getForeignKeyCount() {
        return _foreignKeys.size();
    }

    /**
     * Get foreign key at given index.
     * 
     * @param index Index of foreign key to return.
     * @return Foreign key at given index.
     */
    public final ForeignKey getForeignKey(final int index) {
        return _foreignKeys.get(index);
    }

    /**
     * Add given field to list of fields.
     * 
     * @param field Field to add to list of fields.
     */
    public final void addField(final Field field) {
        _fields.add(field);
        _fieldMap.put(field.getName(), field);
    }
    
    /**
     * Get number of fields.
     * 
     * @return Number of fields.
     */
    public final int getFieldCount() {
        return _fields.size();
    }
    
    /**
     * Get field at given index.
     * 
     * @param index Index of field to return.
     * @return Field at given index.
     */
    public final Field getField(final int index) {
        return _fields.get(index);
    }
    
    /**
     * Get field with given name.
     * 
     * @param name Name of field to return.
     * @return Field with given name.
     */
    public final Field getField(final String name) {
        return _fieldMap.get(name);
    }
    
    /**
     * Set key generator used for identities of this table.
     * 
     * @param keyGenerator Key generator used for identities of this table.
     */
    public final void setKeyGenerator(final KeyGenerator keyGenerator) {
        _keyGenerator = keyGenerator;
    }

    /**
     * Get key generator used for identities of this table.
     * 
     * @return Key generator used for identities of this table.
     */
    public final KeyGenerator getKeyGenerator() {
        return _keyGenerator;
    }

    /**
     * Set primary key with identities of this table.
     * 
     * @param primaryKey Primary key with identities of this table.
     */
    public final void setPrimaryKey(final PrimaryKey primaryKey) {
        _primaryKey = primaryKey;
    }

    /**
     * Get primary key with identities of this table.
     * 
     * @return Primary key with identities of this table.
     */
    public final PrimaryKey getPrimaryKey() {
        return _primaryKey;
    }

    /**
     * Set schema this table belongs to.
     * 
     * @param schema Schema this table belongs to.
     */
    public final void setSchema(final Schema schema) {
        _schema = schema;
    }

    /**
     * Get schema this table belongs to.
     * 
     * @return Schema this table belongs to.
     */
    public final Schema getSchema() {
        return _schema;
    }

    //--------------------------------------------------------------------------
    
    /**
     * Concatenate all fields names delimited by line separator.
     * 
     * @param writer DDLWriter to write schema objects to.
     * @throws GeneratorException If generation of the script failed or is not supported.
     */
    protected final void fields(final DDLWriter writer) throws GeneratorException {
        String delimiter = DDLGenConfiguration.DEFAULT_FIELD_DELIMITER;
        
        writer.indent();
        for (int i = 0; i < getFieldCount(); i++) {
            if (i > 0) { writer.println(delimiter); }
            getField(i).toCreateDDL(writer);
        }
        writer.unindent();
    }

    //--------------------------------------------------------------------------

    /**
     * Check if given table can be merged with this one.
     * 
     * @param table Table to check if it is able to be merged.
     * @throws GeneratorException If tables cannot be merged.
     */
    public final void merge(final Table table) throws GeneratorException {
        if (table == null) {
            String msg = "Table to merge is missing.";
            LOG.error(msg);
            throw new GeneratorException(msg); 
        }
        
        if (!equals(getName(), table.getName())) {
            String msg = "Name of table differs from: " + getName();
            LOG.error(msg);
            throw new GeneratorException(msg); 
        }
        
        if (getFieldCount() != table.getFieldCount()) {
            String msg = "Field count of table differs from: " + getFieldCount();
            LOG.error(msg);
            throw new GeneratorException(msg); 
        }
        
        for (int i = 0; i < getFieldCount(); i++) {
            Field field1 = getField(i);
            Field field2 = null;
            for (int j = 0; j < table.getFieldCount(); j++) {
                field2 = table.getField(j);
                if (!equals(field1.getName(), field2.getName())) { break; }
            }
            field1.merge(field2);
        }
        
        for (int i = 0; i < getForeignKeyCount(); i++) {
            ForeignKey fk1 = getForeignKey(i);
            ForeignKey fk2 = null;
            for (int j = 0; j < table.getForeignKeyCount(); j++) {
                fk2 = table.getForeignKey(j); 
                if (!equals(fk1.getName(), fk2.getName())) { break; }
            }
            fk1.merge(fk2);
        }
        
        if (_keyGenerator != null) { _keyGenerator.merge(table.getKeyGenerator()); }
    }

    //--------------------------------------------------------------------------

    /**
     * {@inheritDoc}
     */
    public final boolean equals(final Object other) {
        if (other == this) { return true; }
        if (other == null) { return false; }
        if (other.getClass() != this.getClass()) { return false; }
        
        Table table = (Table) other;
        return equals(getName(), table.getName())
            && equals(_schema, table._schema)
            && equals(_primaryKey, table._primaryKey)
            && equals(_keyGenerator, table._keyGenerator)
            && equals(_fields, table._fields)
            && equals(_foreignKeys, table._foreignKeys)
            && equals(_indices, table._indices);
    }

    /**
     * {@inheritDoc}
     */
    public final int hashCode() {
        int hashCode = 0;
        if (getName() != null) { hashCode += getName().hashCode(); }
        hashCode *= HASHFACTOR;
        if (_schema != null) { hashCode += _schema.hashCode(); }
        hashCode *= HASHFACTOR;
        if (_primaryKey != null) { hashCode += _primaryKey.hashCode(); }
        hashCode *= HASHFACTOR;
        if (_keyGenerator != null) { hashCode += _keyGenerator.hashCode(); }
        hashCode *= HASHFACTOR;
        hashCode += _fields.hashCode();
        hashCode *= HASHFACTOR;
        hashCode += _foreignKeys.hashCode();
        hashCode *= HASHFACTOR;
        hashCode += _indices.hashCode();
        return hashCode;
    }

    //--------------------------------------------------------------------------
}
