/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 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.shapefile;

import java.io.IOException;
import java.util.List;
import java.util.NoSuchElementException;

import org.geotools.data.AbstractAttributeIO;
import org.geotools.data.shapefile.dbf.DbaseFileHeader;
import org.geotools.data.shapefile.dbf.DbaseFileReader;
import org.geotools.data.shapefile.indexed.RecordNumberTracker;
import org.geotools.data.shapefile.shp.ShapefileReader;
import org.opengis.feature.type.AttributeDescriptor;

import com.vividsolutions.jts.geom.Envelope;

/**
 * An AttributeReader implementation for Shapefile. Pretty straightforward.
 * <BR/>The default geometry is at position 0, and all dbf columns follow.
 * <BR/>The dbf file may not be necessary, if not, just pass null as the
 * DbaseFileReader
 *
 *
 * @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/modules/plugin/shapefile/src/main/java/org/geotools/data/shapefile/ShapefileAttributeReader.java $
 */
public class ShapefileAttributeReader extends AbstractAttributeIO implements
        /*AttributeReader,*/ RecordNumberTracker {

    protected ShapefileReader shp;
    protected DbaseFileReader dbf;
    protected DbaseFileReader.Row row;
    protected ShapefileReader.Record record;
    protected int[] dbfindexes;
    protected Envelope targetBBox;
    protected Object geometry;
    protected boolean featureAvailable = false;

    public ShapefileAttributeReader(List<AttributeDescriptor> atts,
            ShapefileReader shp, DbaseFileReader dbf) {
        this(atts.toArray(new AttributeDescriptor[0]), shp, dbf);
    }
    
    /**
     * Sets a search area. If the geometry does not fall into it
     * it won't be read and will return a null geometry instead 
     * @param envelope
     */
    public void setTargetBBox(Envelope envelope) {
        this.targetBBox = envelope;
    }
    
    public void setSimplificationDistance(double distance) {
    }
    
    /**
     * Create the shapefile reader
     * 
     * @param atts -
     *                the attributes that we are going to read.
     * @param shp -
     *                the shapefile reader, required
     * @param dbf -
     *                the dbf file reader. May be null, in this case no
     *                attributes will be read from the dbf file
     */
    public ShapefileAttributeReader(AttributeDescriptor[] atts,
            ShapefileReader shp, DbaseFileReader dbf) {
        super(atts);
        this.shp = shp;
        this.dbf = dbf;
        
        if(dbf != null) {
            dbfindexes = new int[atts.length];
            DbaseFileHeader head = dbf.getHeader();
            AT: for (int i = 0; i < atts.length; i++) {
                String attName = atts[i].getLocalName();
                int count = 0;
                if (atts[i].getUserData().get(ShapefileDataStore.ORIGINAL_FIELD_NAME) != null){
                	attName = (String)atts[i].getUserData().get(ShapefileDataStore.ORIGINAL_FIELD_NAME);
                	count = (Integer)atts[i].getUserData().get(ShapefileDataStore.ORIGINAL_FIELD_DUPLICITY_COUNT);
                }

                for(int j = 0; j < head.getNumFields(); j++) {
                    if(head.getFieldName(j).equals(attName) && count-- <= 0){
                        dbfindexes[i] = j;
                        continue AT;
                    }
                }
                dbfindexes[i] = -1; // geometry
            }
        }
    }

    public void close() throws IOException {
        try {
            if (shp != null) {
                shp.close();
            }

            if (dbf != null) {
                dbf.close();
            }
        } finally {
            row = null;
            record = null;
            shp = null;
            dbf = null;
        }
    }

    boolean internalReadersHaveNext() throws IOException {
        int n = shp.hasNext() ? 1 : 0;

        if (dbf != null) {
            n += (dbf.hasNext() ? 2 : 0);
        }

        if ((n == 3) || ((n == 1) && (dbf == null))) {
            return true;
        }

        if (n == 0) {
            return false;
        }

        throw new IOException(((n == 1) ? "Shp" : "Dbf") + " has extra record");
    }
    
    public boolean hasNext() throws IOException {
        while(!featureAvailable && internalReadersHaveNext()) {
            record = shp.nextRecord();
            
            // read the geometry, so that we can decide if this row is to be skipped or not
            Envelope envelope = record.envelope();
            boolean skip = false;
            // ... if geometry is out of the target bbox, skip both geom and row
            if (targetBBox != null && !targetBBox.isNull() && !targetBBox.intersects(envelope)) {
                geometry = null;
                skip = true;
            // ... if the geometry is awfully small avoid reading it (unless it's a point)
            /*} else if (simplificationDistance > 0 && envelope.getWidth() < simplificationDistance
                    && envelope.getHeight() < simplificationDistance) {
                try {
                    if(screenMap != null && screenMap.checkAndSet(envelope)) {
                        geometry = null;
                        skip = true;
                    } else {
                        // if we are using the screenmap better provide a slightly modified
                        // version of the geometry bounds or we'll end up with many holes
                        // in the rendering
                        geometry = record.getSimplifiedShape(screenMap);
                    }
                } catch(Exception e) {
                    geometry = record.getSimplifiedShape();
                }*/
            // ... otherwise business as usual
            } else {
                geometry = record.shape();
            }

            // read the dbf only if the geometry was not skipped
            if (dbf != null) {
                if(skip) {
                    dbf.skip();
                    row = null;
                } else {
                    row = dbf.readRow();
                }
            } else {
                row = null;
            }
            featureAvailable = !skip;
        }
        
        return featureAvailable;
    }

    public void next() throws IOException {
        if(!hasNext()) {
            throw new NoSuchElementException("hasNext() returned false");
        }
        featureAvailable = false;
    }

    public Object read(int param) throws IOException,
            java.lang.ArrayIndexOutOfBoundsException {
        int index = dbfindexes != null ? dbfindexes[param] : -1;
                   
        switch (index) {
        case -1:
            return geometry; // geometry is considered dbf index -1

        default:
            if (row != null) {
                return row.read( index );
            }
            return null;
        }
    }
    
    public int getRecordNumber() {
        return this.record.number;
    }

    
    
}
