/*
 * ModeShape (http://www.modeshape.org)
 *
 * 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.modeshape.jcr.value;

import java.io.Serializable;
import java.util.Iterator;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.jcr.api.value.DateTime;

/**
 * Representation of a property consisting of a name and value(s). Note that this property is immutable, meaning that the property
 * values may not be changed through this interface.
 * <p>
 * This class is designed to be used with the {@link ValueFactories} interface and the particular {@link ValueFactory} that
 * corresponds to the type of value you'd like to use. The <code>ValueFactory</code> will then return the values (if no type
 * conversion is required) or will convert the values using the appropriate conversion algorithm.
 * </p>
 * <p>
 * The following example shows how to obtain the {@link String} representations of the {@link #getValues() property values}:
 * 
 * <pre>
 *   ValueFactories valueFactories = ...
 *   Property property = ...
 *   Iterator&lt;String&gt; iter = valueFactories.getStringFactory().create(property.getValues());
 *   while ( iter.hasNext() ) {
 *       System.out.println(iter.next());
 *   }
 * </pre>
 * 
 * Meanwhile, the {@link ValueFactories#getLongFactory() long value factory} converts the values to <code>long</code>, the
 * {@link ValueFactories#getDateFactory() date value factory} converts the values to {@link DateTime} instances, and so on.
 * </p>
 * <p>
 * This technique is much better and far safer than casting the values. It is possible that some Property instances contain
 * heterogeneous values, so casting may not always work. Also, this technique guarantees that the values are properly converted if
 * the type is not what you expected.
 * </p>
 */
@Immutable
public interface Property extends Iterable<Object>, Comparable<Property>, Readable, Serializable {

    /**
     * Get the name of the property.
     * 
     * @return the property name; never null
     */
    Name getName();

    /**
     * Get the number of actual values in this property. If the property allows {@link #isMultiple() multiple values}, then this
     * method may return a value greater than 1. If the property only allows a {@link #isSingle() single value}, then this method
     * will return either 0 or 1. This method may return 0 regardless of whether the property allows a {@link #isSingle() single
     * value}, or {@link #isMultiple() multiple values}.
     * 
     * @return the number of actual values in this property; always non-negative
     */
    int size();

    /**
     * Determine whether the property currently has multiple values.
     * 
     * @return true if the property has multiple values, or false otherwise.
     * @see #isSingle()
     * @see #isEmpty()
     */
    boolean isMultiple();

    /**
     * Determine whether the property currently has a single value.
     * 
     * @return true if the property has a single value, or false otherwise.
     * @see #isMultiple()
     * @see #isEmpty()
     */
    boolean isSingle();

    /**
     * Determine whether this property has no actual values. This method may return <code>true</code> regardless of whether the
     * property allows a {@link #isSingle() single value}, or {@link #isMultiple() multiple values}.
     * <p>
     * This method is a convenience method that is equivalent to <code>size() == 0</code>.
     * </p>
     * 
     * @return true if this property has no values, or false otherwise
     * @see #isMultiple()
     * @see #isSingle()
     */
    boolean isEmpty();

    /**
     * Determine whether this property contains reference values, based upon the first value in the property. Note that
     * {@link PropertyType#SIMPLEREFERENCE} properties *are not* treated as references in order to avoid setting the
     * back-pointer.
     * 
     * @return true if this property is a reference property, or false otherwise.
     */
    boolean isReference();

    /**
     * Determine whether this property contains simple reference values, based upon the first value in the property.
     * @see PropertyType#SIMPLEREFERENCE
     *
     * @return true if this property is a simple reference property, or false otherwise.
     */
    boolean isSimpleReference();

    /**
     * Determine whether this property is a binary property or not.
     *
     * @return true if the property is a binary property, false otherwise.
     */
    boolean isBinary();

    /**
     * Obtain the property's first value in its natural form. This is equivalent to calling
     * <code>isEmpty() ? null : iterator().next()</code>
     * 
     * @return the first value, or null if the property is {@link #isEmpty() empty}
     * @see Iterable#iterator()
     * @see #getValues()
     * @see #getValuesAsArray()
     * @see #isEmpty()
     */
    Object getFirstValue();

    /**
     * Obtain the property's values in their natural form. This is equivalent to calling {@link Iterable#iterator() iterator()}.
     * <p>
     * A valid iterator is returned if the property has {@link #isSingle() single valued} or {@link #isMultiple() multi-valued}.
     * </p>
     * <p>
     * The resulting iterator is immutable, and all property values are immutable.
     * </p>
     * 
     * @return an iterator over the values; never null
     * @see #getFirstValue()
     * @see Iterable#iterator()
     * @see #getValuesAsArray()
     * @see ValueFactory#create(Iterator)
     */
    Iterator<?> getValues();

    /**
     * Obtain the property's values as an array of objects in their natural form.
     * <p>
     * A valid array is return if the property has {@link #isSingle() single valued} or {@link #isMultiple() multi-valued}, or a
     * null value is returned if the property is {@link #isEmpty() empty}.
     * </p>
     * <p>
     * The resulting array is a copy, guaranteeing immutability for the property.
     * </p>
     * 
     * @return the array of values
     * @see #getFirstValue()
     * @see Iterable#iterator()
     * @see #getValues()
     * @see ValueFactory#create(Object[])
     */
    Object[] getValuesAsArray();

    /**
     * Returns the value of the property at the given index.
     *
     * @param index an {@code int} representing the index at which to retrieve the value. If the value is {@code 0}, this is
     * equivalent to calling {@link org.modeshape.jcr.value.Property#getFirstValue()}
     * @return the value of the property at the given index; may be {@code null} if the property is empty.
     * @throws IndexOutOfBoundsException if the given index is outside the interval of values of the property.
     */
    Object getValue(int index) throws IndexOutOfBoundsException;

    /**
     * Convert the values of this property to whatever type the given value factory is used to create.
     *
     * @param valueFactory a {@link org.modeshape.jcr.value.basic.AbstractValueFactory} representing the factory which will
     * be used to attempt the conversion of each value.
     * @return the array of values for this property converted (if possible) to given type.
     * @throws ValueFormatException if the conversion cannot be performed for any value in the array of values.
     */
    <T> T[] getValuesAsArray( ValueFactory<T> valueFactory ) throws ValueFormatException;

    /**
     * Convert the values of this property to the given type, using the specified type transformer.
     *
     * @param valueTypeTransformer a {@link ValueTypeTransformer} representing the transformer which will
     * be used to attempt the conversion of each value.
     * @param  type a {@link Class} indicating the type to which the transformation is performed; required because of type erasure.
     * @return the array of values for this property converted (if possible) to given type.
     * @throws ValueFormatException if the conversion cannot be performed for any value in the array of values.
     */
    <T> T[] getValuesAsArray( ValueTypeTransformer<T> valueTypeTransformer,
                              Class<T> type ) throws ValueFormatException;

    /**
     * Interface which allows the conversion of a property value to a given type.
     * @param <T> the generic type
     */
    public interface ValueTypeTransformer<T> {
        /**
         * Transforms the given value to a specific type.
         *
         * @param value the individual value of a property; never {@code null}
         * @return the value transformed to the given type.
         */
        T transform(Object value);
    }
}
