package org.exolab.castor.mapping.loader;

import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.mapping.FieldDescriptor;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.mapping.xml.types.FieldMappingCollectionType;

public class FieldDescriptorImpl implements FieldDescriptor {

    /** A reference to the containing class descriptor (parent). */
    private ClassDescriptor _parent;

    /** The name of the field. */
    private String _fieldName;

    /** The type of the field. */
    private Class<?> _fieldType;

    /** The type class descriptor, if this field is of a type known by a descriptor. */
    private ClassDescriptor _classDescriptor;
    
    /** The field handler for get/set field value. */
    private FieldHandler _handler;

    /** True if the field is transient and should not be saved/stored. */
    private boolean _transient;

    /** True if the described field type is immutable. */
    private boolean _immutable;
    
    /** True if the field type is required. */
    private boolean _required;

    /** True if access to field is direct. */
    private boolean _direct;
    
    /** True if access to field is lazy. */
    private boolean _lazy;
    
    /** True if the object described by this descriptor is multi-valued. */
    private boolean _multivalued;
    
    /** True if the field is part of the identity of the class. */
    private boolean _identity;

    /** The name of a class that implements the {@link Comparator} interface. */
    private String _comparator;

    /** The name of the 'getter' method used to obtain the value of a property. */
    private String _getMethod;

    /** The name of the 'setter' method used to set the value of a property. */
    private String _setMethod;

    /** The name of the 'create' method used to create an instance of a property. */
    private String _createMethod;
    
    /** The type of the collection, if the field is multi-valued. */
    private FieldMappingCollectionType _collection;

    /**
     * Map holding the properties set and read by natures.
     */
    private Map<String, Object> _properties = new HashMap<String, Object>();

    /**
     * Map holding the available natures.
     */
    private Set<String> _natures = new HashSet<String>();
    
    /**
     * Creates a default instance of a field descriptor.
     */
    protected FieldDescriptorImpl() { }

    /**
     * Constructs a new field descriptor.
     *
     * @param fieldName The field name
     * @param typeInfo The field type information
     * @param handler The field handler (may be null)
     * @param isTransitive True if the field is transient
     */
    public FieldDescriptorImpl(final String fieldName, 
            final TypeInfo typeInfo,
            final FieldHandler handler, 
            final boolean isTransitive) {
        
        if (fieldName == null) {
            throw new IllegalArgumentException("Internal error. Field name not specified.");
        }
        
        if (handler == null) {
            throw new IllegalArgumentException("Internal error. No FieldHandler provided.");
        }
        
        setFieldName(fieldName);
        setFieldType(typeInfo.getFieldType());
        setHandler(handler);
        setTransient(isTransitive);
        setImmutable(typeInfo.isImmutable());
        setRequired(typeInfo.isRequired());
        setMultivalued(typeInfo.getCollectionHandler() != null);
    }

    /**
     * @see org.exolab.castor.mapping.FieldDescriptor#setContainingClassDescriptor(
     *      org.exolab.castor.mapping.ClassDescriptor)
     * {@inheritDoc}
     */
    public final void setContainingClassDescriptor(final ClassDescriptor parent) {
        _parent = parent;
    }
    
    /**
     * @see org.exolab.castor.mapping.FieldDescriptor#getContainingClassDescriptor()
     * {@inheritDoc}
     */
    public final ClassDescriptor getContainingClassDescriptor() {
        return _parent;
    }
    
    /**
     * Set the name of the field.
     * 
     * @param fieldName Field name.
     */
    public final void setFieldName(final String fieldName) {
        _fieldName = fieldName;
    }

    /**
     * @see org.exolab.castor.mapping.FieldDescriptor#getFieldName()
     * {@inheritDoc}
     */
    public final String getFieldName() {
        return _fieldName;
    }

    /**
     * Set the type of the field.
     * 
     * @param fieldType Field type.
     */
    public final void setFieldType(final Class<?> fieldType) {
        _fieldType = fieldType;
    }

    /**
     * @see org.exolab.castor.mapping.FieldDescriptor#getFieldType()
     * {@inheritDoc}
     */
    public final Class<?> getFieldType() {
        return _fieldType;
    }

    /**
     * Set the ClassDescriptor for the described field.
     *
     * @param classDescriptor The ClassDescriptor for the described field.
     */
    public final void setClassDescriptor(final ClassDescriptor classDescriptor) {
        _classDescriptor = classDescriptor;
    }

    /**
     * @see org.exolab.castor.mapping.FieldDescriptor#getClassDescriptor()
     * {@inheritDoc}
     */
    public final ClassDescriptor getClassDescriptor() {
        return _classDescriptor;
    }

    /**
     * Set the FieldHandler for the field being described by this FieldDescriptor.
     *
     * @param handler The FieldHandler for the field being described.
     */
    public final void setHandler(final FieldHandler handler) {
        _handler = handler;
    }

    /**
     * @see org.exolab.castor.mapping.FieldDescriptor#getHandler()
     * {@inheritDoc}
     */
    public final FieldHandler getHandler() {
        return _handler;
    }

    /**
     * Sets whether or not the describled field is 'transient'.
     * 
     * @param isTransient The flag indicating if the described field is 'transient'.
     */
    public final void setTransient(final boolean isTransient) {
        _transient = isTransient;
    }

    /**
     * @see org.exolab.castor.mapping.FieldDescriptor#isTransient()
     * {@inheritDoc}
     */
    public final boolean isTransient() {
        return _transient;
    }

    /**
     * Set the immutable flag which indicates that changes to this Field result in a
     * new Object to be created, such as java.lang.String. It serves to identify fields
     * which should not be constructed until all the data is available.
     * 
     * @param immutable Flag which if true indicates that the field is immutable.
     */
    public final void setImmutable(final boolean immutable) {
        _immutable = immutable;
    }

    /**
     * @see org.exolab.castor.mapping.FieldDescriptor#isImmutable()
     * {@inheritDoc}
     */
    public final boolean isImmutable() {
        return _immutable;
    }

    /**
     * Set whether or not the described field is required.
     * 
     * @param required The flag indicating whether or not the described field is required.
     */
    public final void setRequired(final boolean required) {
        _required = required;
    }

    /**
     * @see org.exolab.castor.mapping.FieldDescriptor#isRequired()
     * {@inheritDoc}
     */
    public final boolean isRequired() {
        return _required;
    }

    /**
     * Set wheter the object described by this descriptor is multivalued or not.
     * 
     * @param multivalued True if the object described by this descriptor is multivalued.
     */
    public final void setMultivalued(final boolean multivalued) {
        _multivalued = multivalued;
    }
    
    /**
     * @see org.exolab.castor.mapping.FieldDescriptor#isMultivalued()
     * {@inheritDoc}
     */
    public final boolean isMultivalued() {
        return _multivalued;
    }
    
    /**
     * Set wether the described field is part of the identity of the class it belongs to.
     * 
     * @param identity <code>true</code> if field is part of the classes identity.
     */
    public final void setIdentity(final boolean identity) {
        _identity = identity;
    }
    
    /**
     * Is the described field is part of the identity of the class it belongs to?
     * 
     * @return <code>true</code> if field is part of the classes identity.
     */
    public final boolean isIdentity() {
        return _identity;
    }

    /**
     * @see org.exolab.castor.builder.info.nature.PropertyHolder#
     *      getProperty(java.lang.String)
     * @param name
     *            of the property
     * @return value of the property
     */
    public Object getProperty(final String name) {
        return _properties.get(name);
    }

    /**
     * @see org.exolab.castor.builder.info.nature.PropertyHolder#
     *      setProperty(java.lang.String, java.lang.Object)
     * @param name
     *            of the property
     * @param value
     *            of the property
     */
    public void setProperty(final String name, final Object value) {
        _properties.put(name, value);
    }

    /**
     * @see org.exolab.castor.builder.info.nature.NatureExtendable#
     *      addNature(java.lang.String)
     * @param nature
     *            ID of the Nature
     */
    public void addNature(final String nature) {
        _natures.add(nature);
    }

    /**
     * @see org.exolab.castor.builder.info.nature.NatureExtendable#
     *      hasNature(java.lang.String)
     * @param nature
     *            ID of the Nature
     * @return true if the Nature ID was added.
     */
    public boolean hasNature(final String nature) {
        return _natures.contains(nature);
    }

    public String getComparator() {
        return _comparator;
    }

    public void setComparator(String comparator) {
        _comparator = comparator;
    }

    public FieldMappingCollectionType getCollection() {
        return _collection;
    }

    public void setCollection(FieldMappingCollectionType collection) {
        _collection = collection;
    }

    public boolean isDirect() {
        return _direct;
    }

    public void setDirect(boolean direct) {
        _direct = direct;
    }

    public String getGetMethod() {
        return _getMethod;
    }

    public void setGetMethod(String getMethod) {
        _getMethod = getMethod;
    }

    public String getSetMethod() {
        return _setMethod;
    }

    public void setSetMethod(String setMethod) {
        _setMethod = setMethod;
    }

    public String getCreateMethod() {
        return _createMethod;
    }

    public void setCreateMethod(String createMethod) {
        _createMethod = createMethod;
    }

    public boolean isLazy() {
        return _lazy;
    }

    public void setLazy(boolean lazy) {
        _lazy = lazy;
    }

    public String toString() {
        StringBuffer buffer = new StringBuffer();
        buffer.append(getFieldName() + "(" + getFieldType().getName() + ")");
        // TODO[WG]: find a way to emit additional nature-specific information.
//        try {
//            Class natureClass = Class.forName("org.exolab.castor.jdo.engine.nature.FieldDescriptorJDONature");
//            if (hasNature(natureClass.getName())) {
//                FieldDescriptorJDONature jdoNature = new FieldDescriptorJDONature(this);
//                buffer.append(jdoNature.getSQLName() == null ? "" : " AS " 
//                    + jdoNature.getSQLName()[0]);
//            }
//        } catch (ClassNotFoundException e) {
//            // ignore
//        }
        return buffer.toString();
    }

}
