/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 * 
 *    (C) 2003-2008, Open Source Geospatial Foundation (OSGeo)
 *    
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.data;

import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.factory.Hints;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.IllegalAttributeException;
import org.geotools.feature.NameImpl;
import org.geotools.filter.identity.FeatureIdImpl;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.identity.FeatureId;


/**
 * This is a starting point for providing your own FeatureStore implementation.
 *
 * @author Jody Garnett, Refractions Research
 *
 * @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/modules/library/main/src/main/java/org/geotools/data/AbstractFeatureStore.java $
 */
public abstract class AbstractFeatureStore extends AbstractFeatureSource
    implements SimpleFeatureStore {
    /** Current Transaction this SimpleFeatureSource is opperating against */
    protected Transaction transaction = Transaction.AUTO_COMMIT;
    
    public AbstractFeatureStore() {
        // just to keep the default constructor around
    }
    
    /**
     * This constructors allows to set the supported hints 
     * @param hints
     */
    public AbstractFeatureStore(Set hints) {
        super(hints);
    }

    /**
     * Retrieve the Transaction this SimpleFeatureSource is opperating against.
     *
     * @return Transaction SimpleFeatureSource is operating against
     */
    public Transaction getTransaction() {
        return transaction;
    }

    // 
    // FeatureStore implementation against DataStore API
    // 

    /**
     * Modifies features matching <code>filter</code>.
     * 
     * <p>
     * Equivelent to:
     * </p>
     * <pre><code>
     * FeatureWriter<SimpleFeatureType, SimpleFeature> writer = dataStore.getFeatureWriter( typeName, filter, transaction );
     * while( writer.hasNext() ){
     *    feature = writer.next();
     *    feature.setAttribute( type.getName(), value );
     *    writer.write();
     * }
     * writer.close();
     * </code>
     * </pre>
     * 
     * <p>
     * Subclasses may override this method to perform the appropriate
     * optimization for this result.
     * </p>
     *
     * @param type Attribute to modify
     * @param value Modification being made to type
     * @param filter Identifies features to modify
     *
     * @throws IOException If modification could not be made
     */
    public final void modifyFeatures(AttributeDescriptor type, Object value, Filter filter)
        throws IOException {
        Name attributeName = type.getName();
        modifyFeatures( attributeName, value, filter);
    }
    
    public void modifyFeatures(Name attributeName, Object attributeValue, Filter filter)  throws IOException {
        modifyFeatures(new Name[] { attributeName, }, new Object[] { attributeValue, },
                filter);
    }
    
    public void modifyFeatures(String name, Object attributeValue, Filter filter)
            throws IOException {
        modifyFeatures(new Name[] { new NameImpl( name ), }, new Object[] { attributeValue, },
                filter);
    }

    public void modifyFeatures(String[] names, Object[] values, Filter filter)
            throws IOException {
        Name attributeNames[] = new Name[ names.length ];
        for( int i=0; i < names.length; i ++){
            attributeNames[i] = new NameImpl(names[i]);
        }
        modifyFeatures( attributeNames, values, filter ); 
    }

    /**
     * Modifies features matching <code>filter</code>.
     * 
     * <p>
     * Equivelent to:
     * </p>
     * <pre><code>
     * FeatureWriter<SimpleFeatureType, SimpleFeature> writer = dataStore.getFeatureWriter( typeName, filter, transaction );
     * Feature feature;
     * while( writer.hasNext() ){
     *    feature = writer.next();
     *    feature.setAttribute( type[0].getName(), value[0] );
     *    feature.setAttribute( type[1].getName(), value[1] );
     *    ...
     *    feature.setAttribute( type[N].getName(), value[N] ); 
     *    writer.write();
     * }
     * writer.close();
     * </code>
     * </pre>
     * 
     * <p>
     * Subclasses may override this method to perform the appropriate
     * optimization for this result.
     * </p>
     *
     * @param type Attributes to modify
     * @param value Modifications being made to type
     * @param filter Identifies features to modify
     *
     * @throws IOException If we could not modify Feature
     * @throws DataSourceException See IOException
     */
    public final void modifyFeatures(AttributeDescriptor[] type, Object[] value,
        Filter filter) throws IOException {
        
        Name attributeNames[] = new Name[ type.length ];
        for( int i=0; i < type.length; i ++){
            attributeNames[i] = type[i].getName();
        }
        modifyFeatures( attributeNames, value, filter );       
    }

    public void modifyFeatures(Name[] attributeNames, Object[] attributeValues, Filter filter)  throws IOException{
        String typeName = getSchema().getTypeName();
        if ( filter == null ) {
            String msg = "Must specify a filter, must not be null.";
            throw new IllegalArgumentException( msg );
        }
        FeatureWriter<SimpleFeatureType, SimpleFeature> writer = getDataStore()
                .getFeatureWriter(typeName, filter, getTransaction());
        SimpleFeature feature;        
        for( Name attributeName : attributeNames ){
            if( getSchema().getDescriptor( attributeName) == null ){
                throw new DataSourceException("Cannot modify "+attributeName+" as it is not an attribute of "+getSchema().getName() );
            }
        }
        try {
            while (writer.hasNext()) {
                feature = writer.next();

                for (int i = 0; i < attributeNames.length; i++) {
                    try {
                        feature.setAttribute(attributeNames[i], attributeValues[i]);
                    } catch (Exception e) {
                        throw new DataSourceException(
                            "Could not update feature " + feature.getID()
                            + " with " + attributeNames[i] + "=" + attributeValues[i], e);
                    }
                }

                writer.write();
            }
        } finally {
            writer.close();
        }
    }

    public List<FeatureId> addFeatures(FeatureCollection<SimpleFeatureType,SimpleFeature> collection)
            throws IOException {
    	List<FeatureId> addedFids = new LinkedList<FeatureId>();
        String typeName = getSchema().getTypeName();
        SimpleFeature feature = null;
        SimpleFeature newFeature;
        FeatureWriter<SimpleFeatureType, SimpleFeature> writer = getDataStore()
                .getFeatureWriterAppend(typeName, getTransaction());

        Iterator iterator = collection.iterator();
        try {
        	
            while (iterator.hasNext()) {
                feature = (SimpleFeature) iterator.next();
                newFeature = writer.next();
                try {
                    newFeature.setAttributes(feature.getAttributes());
                } catch (Exception writeProblem) {
                    throw new DataSourceException("Could not create "
                        + typeName + " out of provided feature: "
                        + feature.getID(), writeProblem);
                }
                
                boolean useExisting = Boolean.TRUE.equals(feature.getUserData().get(Hints.USE_PROVIDED_FID));
                if(getQueryCapabilities().isUseProvidedFIDSupported() && useExisting) {
                    ((FeatureIdImpl) newFeature.getIdentifier()).setID(feature.getID());
                }

                writer.write();
                addedFids.add(newFeature.getIdentifier());
            }
        } finally {
            collection.close( iterator );
            writer.close();
        }
        return addedFids;
    }
    
    /**
     * Removes features indicated by provided filter.
     * 
     * <p>
     * Equivelent to:
     * </p>
     * <pre><code>
     * FeatureWriter<SimpleFeatureType, SimpleFeature> writer = dataStore.getFeatureWriter( typeName, filter, transaction );
     * Feature feature;
     * while( writer.hasNext() ){
     *    feature = writer.next();
     *    writer.remove();
     * }
     * writer.close();
     * </code>
     * </pre>
     * 
     * <p>
     * Subclasses may override this method to perform the appropriate
     * optimization for this result.
     * </p>
     *
     * @param filter Identifies features to remove
     *
     * @throws IOException
     */
    public void removeFeatures(Filter filter) throws IOException {
        String typeName = getSchema().getTypeName();
        FeatureWriter<SimpleFeatureType, SimpleFeature> writer = getDataStore().getFeatureWriter(typeName,
                filter, getTransaction());

        try {
            while (writer.hasNext()) {
                writer.next();
                writer.remove();
            }
        } finally {
            writer.close();
        }
    }

    /**
     * Replace with contents of reader.
     * 
     * <p>
     * Equivelent to:
     * </p>
     * <pre><code>
     * FeatureWriter<SimpleFeatureType, SimpleFeature> writer = dataStore.getFeatureWriter( typeName, false, transaction );
     * Feature feature, newFeature;
     * while( writer.hasNext() ){
     *    feature = writer.next();
     *    writer.remove();
     * }
     * while( reader.hasNext() ){
     *    newFeature = reader.next();
     *    feature = writer.next();
     *    newFeature.setAttributes( feature.getAttributes( null ) );
     *    writer.write();
     * }
     * reader.close();
     * writer.close();
     * </code>
     * </pre>
     * 
     * <p>
     * Subclasses may override this method to perform the appropriate
     * optimization for this result.
     * </p>
     *
     * @param reader Contents to replace with
     *
     * @throws IOException if anything goes wrong during replacement
     * @throws DataSourceException See IOException
     */
    public void setFeatures(FeatureReader <SimpleFeatureType, SimpleFeature> reader) throws IOException {
        String typeName = getSchema().getTypeName();
        FeatureWriter<SimpleFeatureType, SimpleFeature> writer = getDataStore().getFeatureWriter(typeName,
                getTransaction());
        SimpleFeature feature;
        SimpleFeature newFeature;

        try {
            while (writer.hasNext()) {
                feature = writer.next();
                writer.remove();
            }

            while (reader.hasNext()) {
                try {
                    feature = reader.next();
                } catch (Exception readProblem) {
                    throw new DataSourceException("Could not add Features, problem with provided reader",
                        readProblem);
                }

                newFeature = writer.next();

                try {
                    newFeature.setAttributes(feature.getAttributes());
                } catch (IllegalAttributeException writeProblem) {
                    throw new DataSourceException("Could not create "
                        + typeName + " out of provided feature: "
                        + feature.getID(), writeProblem);
                }

                writer.write();
            }
        } finally {
            reader.close();
            writer.close();
        }
    }

    public void setTransaction(Transaction transaction) {
        if (transaction == null) {
            throw new IllegalArgumentException(
                "Transaction cannot be null, did you mean Transaction.AUTO_COMMIT?");
        }

        this.transaction = transaction;
    }
}
