/*
 *    Geotools2 - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2002-2006, Geotools Project Managment Committee (PMC)
 *
 *    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.arcsde.data;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import org.geotools.arcsde.pool.ArcSDEPooledConnection;
import org.geotools.data.DataSourceException;
import org.geotools.feature.AttributeType;
import org.geotools.feature.FeatureType;

import com.esri.sde.sdk.client.SeException;
import com.esri.sde.sdk.client.SeLayer;
import com.esri.sde.sdk.client.SeRegistration;
import com.esri.sde.sdk.client.SeShape;

/**
 * Strategy object used to manage the different ways an ArcSDE server handles
 * row identity.
 * <p>
 * The supported strategies are:
 * <ul>
 * <li>SDE managed mode: a column is assigned by the sde engine to be the
 * feature id (it uses to be called OBJECTID)
 * <li>User managed: a user specified row is used as the fid column.
 * <li>Shape fid: if none of the above, the fid happens to be the identifier of
 * the geometry column
 * </ul>
 * </p>
 * 
 * @author Gabriel Roldan, Axios Engineering
 * @version $Id: FIDReader.java 25949 2007-06-20 16:07:37Z desruisseaux $
 */
public abstract class FIDReader {

    protected String layerName;

    /** column name holding the feature id attribute */
    private String fidColumn;

    private int columnIndex;

    /**
     * Creates a new FIDStrategy object.
     * 
     * @param fidColumns
     *            DOCUMENT ME!
     */
    private FIDReader(String layerName, String fidColumn) {
        this.layerName = layerName;
        this.fidColumn = fidColumn;
    }

    public String getFidColumn() {
        return fidColumn;
    }

    public void setColumnIndex(int fidIndex) {
        this.columnIndex = fidIndex;
    }

    public int getColumnIndex() {
        return this.columnIndex;
    }

    public long readFid(SdeRow row) throws IOException {
        Object fid = row.getObject(this.columnIndex);
        return ((Number) fid).longValue();
    }

    /**
     * Returns the attribute names of the FeatureType passed to the constructor.
     * 
     * @return DOCUMENT ME!
     * 
     * @throws IOException
     *             if the SeLayer can't be obtained (only if the geomety
     *             attribute was not included in the request).
     */
    public String[] getPropertiesToFetch(FeatureType schema) throws IOException {

        List attNames = new ArrayList(schema.getAttributeCount() + 1);

        // /List attDescriptors = Descriptors.nodes(schema.getDescriptor());
        List attDescriptors = Arrays.asList(schema.getAttributeTypes());

        int i = 0;
        for (Iterator it = attDescriptors.iterator(); it.hasNext(); i++) {
            AttributeType property = (AttributeType) it.next();
            // /attNames.add( property.getName().getLocalPart() );
            attNames.add(property.getName());
        }

        String fidColumn = getFidColumn();
        int fidIndex = attNames.indexOf(fidColumn);
        if (fidColumn != null && fidIndex == -1) {
            attNames.add(fidColumn);
            fidIndex = attNames.size() - 1;
        }
        setColumnIndex(fidIndex);

        return (String[]) attNames.toArray(new String[attNames.size()]);
    }

    /**
     * Returns a FID strategy appropriate for the given SeLayer
     * 
     * @param conn
     *            DOCUMENT ME!
     * @param tableName
     *            DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
     * 
     * @throws IOException
     *             DOCUMENT ME!
     * @throws DataSourceException
     *             DOCUMENT ME!
     */
    public static FIDReader getFidReader(ArcSDEPooledConnection conn, SeLayer layer)
            throws IOException {
        FIDReader fidReader = null;

        final String tableName;
        try {
            tableName = layer.getQualifiedName();
        } catch (SeException e) {
            throw new DataSourceException("asking qualified name of " + layer.getName(), e);
        }
        try {
            SeRegistration reg = new SeRegistration(conn, tableName);

            // final int rowIdAllocationType = reg.getRowIdAllocation();
            final int rowIdColumnType = reg.getRowIdColumnType();
            final String rowIdColumnName = reg.getRowIdColumnName();

            if(rowIdColumnType == SeRegistration.SE_REGISTRATION_ROW_ID_COLUMN_TYPE_SDE){
                // use column name, value maintained by sde
                fidReader = new SdeManagedFidReader(tableName, rowIdColumnName);
            }else if(rowIdColumnType == SeRegistration.SE_REGISTRATION_ROW_ID_COLUMN_TYPE_USER){
                // use column name, value maintained by user
                fidReader = new UserManagedFidReader(tableName, rowIdColumnName);
            }else if(rowIdColumnType == SeRegistration.SE_REGISTRATION_ROW_ID_COLUMN_TYPE_NONE){
                // use geometry id
                String shapeColName = layer.getSpatialColumn();
                String shapeIdColName = layer.getShapeAttributeName(SeLayer.SE_SHAPE_ATTRIBUTE_FID);
                fidReader = new ShapeFidReader(tableName, shapeColName, shapeIdColName);
            }else{
                // may have been returned 0, meaning there is no registered
                // column id
                throw new IllegalStateException("Unkown ArcSDE row ID registration type: "
                        + rowIdColumnType + " for layer " + tableName);
            }
            return fidReader;
        } catch (SeException e) {
            throw new DataSourceException("Obtaining FID strategy for " + layer, e);
        }
    }

    public static class ShapeFidReader extends FIDReader {
        private final String shapeColName;

        private int shapeIndex;

        public ShapeFidReader(final String layerName, final String shapeColName,
                final String shapeIdColName) {
            super(layerName, shapeIdColName);
            this.shapeColName = shapeColName;
            this.shapeIndex = -1;
        }

        public long readFid(SdeRow row) throws IOException {
            if (shapeIndex != -1) {
                // we have the shape, so SHAPE.fid couldn't be retrieved
                // at the same time, need to get the shape and ask it for the id
                try {
                    SeShape shape = row.getShape(shapeIndex);
                    if (shape == null) {
                        throw new NullPointerException("Can't get FID from " + layerName
                                + " as it has SHAPE fid reading strategy and got a null shape");
                    }
                    return shape.getFeatureId().longValue();
                } catch (SeException e) {
                    throw new DataSourceException("Getting fid from shape", e);
                }
            } else {
                int shapeIdIndex = getColumnIndex();
                Integer id = (Integer) row.getObject(shapeIdIndex);
                return id.longValue();
            }
        }

        public String[] getPropertiesToFetch(FeatureType schema) throws IOException {
            List attNames = new ArrayList(schema.getAttributeCount() + 1);

            // /List attDescriptors = Descriptors.nodes(schema.getDescriptor());
            List attDescriptors = Arrays.asList(schema.getAttributeTypes());

            int i = 0;
            for (Iterator it = attDescriptors.iterator(); it.hasNext(); i++) {
                AttributeType property = (AttributeType) it.next();
                attNames.add(property.getName());
            }

            shapeIndex = attNames.indexOf(shapeColName);
            if (shapeIndex == -1) {
                String fidColumn = getFidColumn();
                int fidIndex = attNames.indexOf(shapeColName);
                if (fidIndex == -1) {
                    attNames.add(fidColumn);
                    fidIndex = attNames.size() - 1;
                }
                setColumnIndex(fidIndex);
            }
            return (String[]) attNames.toArray(new String[attNames.size()]);
        }

    }

    public static class SdeManagedFidReader extends FIDReader {

        public SdeManagedFidReader(final String layerName, final String rowIdColName) {
            super(layerName, rowIdColName);
        }
    }

    public static class UserManagedFidReader extends FIDReader {

        public UserManagedFidReader(final String layerName, final String rowIdColName) {
            super(layerName, rowIdColName);
        }
    }

    public static final FIDReader NULL_READER = new FIDReader(null, null) {
        public long readFid(SdeRow row) throws IOException {
            return (long) (10000 * Math.random());
        }
    };
}
