/*
 *    GeoTools - 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.data.postgis.fidmapper;

import java.io.IOException;
import java.sql.Connection;
import java.sql.Types;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import org.geotools.data.jdbc.fidmapper.BasicFIDMapper;
import org.geotools.data.jdbc.fidmapper.DefaultFIDMapperFactory;
import org.geotools.data.jdbc.fidmapper.FIDMapper;
import org.geotools.data.jdbc.fidmapper.FIDMapperFactory;
import org.geotools.data.jdbc.fidmapper.TypedFIDMapper;
import org.geotools.data.postgis.VersionedPostgisDataStore;

/**
 * A fid mapper factory that makes sure the revision attribute does not become part of the key
 * (since users outside are unaware of it).
 * 
 * @author aaime
 * @since 2.4
 */
public class VersionedFIDMapperFactory extends DefaultFIDMapperFactory {

    Set versionedTypes = new HashSet();

    private FIDMapperFactory unversionedFactory;

    public VersionedFIDMapperFactory(FIDMapperFactory unversionedFactory) {
        super();
        this.unversionedFactory = unversionedFactory;
        returnFIDColumnsAsAttributes = true;
        returningTypedFIDMapper = false;
    }

    public void setVersionedTypes(String[] versionedTypes) {
        this.versionedTypes.clear();
        this.versionedTypes.addAll(Arrays.asList(versionedTypes));
    }

    /**
     * Gets the appropriate FIDMapper for the specified table.
     * 
     * @param catalog
     * @param schema
     * @param tableName
     * @param connection
     *            the active database connection to get table key information
     * 
     * @return the appropriate FIDMapper for the specified table.
     * 
     * @throws IOException
     *             if any error occurs.
     */
    public FIDMapper getMapper(String catalog, String schema, String tableName,
            Connection connection) throws IOException {
        // handle changesets as a special one
        if(VersionedPostgisDataStore.TBL_CHANGESETS.equals(tableName)) {
            PostGISAutoIncrementFIDMapper mapper = new PostGISAutoIncrementFIDMapper(VersionedPostgisDataStore.TBL_CHANGESETS, "revision", Types.NUMERIC, true);
            return new TypedFIDMapper(mapper, VersionedPostgisDataStore.TBL_CHANGESETS);
        }
        
        // for non versioned types we're good with the standard mappers, but we
        // must remember that versioned data store uses typed fids externally
        // (only internal ones are non typed)
        if (!versionedTypes.contains(tableName)) {
            if(tableName.endsWith("_vfc_view")) {
                try {
                    String otn = VersionedPostgisDataStore.getVFCTableName(tableName);
                    // let's see if the original feature table is there and versioned
                    VersionedFIDMapper mapper = (VersionedFIDMapper) getMapper(catalog, schema,otn, connection);
                    return new VersionedFeatureCollectionFidMapper(mapper);
//                    return new VersionedFeatureCollectionFidMapper(mapper);
                } catch(Exception e ) {
                    // ok, it wasn't a versioned feature collection view
                }
            }
                
            return unversionedFactory.getMapper(catalog, schema, tableName, connection);
            
        }

        ColumnInfo[] colInfos = getPkColumnInfo(catalog, schema, tableName, connection);
        if (colInfos.length <= 1)
            throw new IOException("Versioned type (" + tableName
                    + ") with a primary key with less than 2 columns,"
                    + " this cannot be, there's a error");

        // assume revision is the last column
        if (colInfos.length == 2) {
            return buildSingleColumnVersionedFidMapper(schema, tableName, connection, colInfos);
        } else {
            return buildMultiColumnFIDMapper(schema, tableName, connection, colInfos);
        }
    }

    protected FIDMapper buildSingleColumnVersionedFidMapper(String schema, String tableName,
            Connection connection, ColumnInfo[] colInfos) {
        if (colInfos[1].isAutoIncrement() && colInfos.length == 2) {
            return new VersionedAutoincrementFIDMapper(schema, tableName, colInfos[1].colName,
                    colInfos[1].dataType, colInfos[0].decimalDigits);
        } else if (isIntegralType(colInfos[1].dataType)) {
            return buildMultiColumnFIDMapper(schema, tableName, connection, colInfos);
        } else {
            return buildMultiColumnFIDMapper(schema, tableName, connection, colInfos);
        }
    }

    protected FIDMapper buildMultiColumnFIDMapper(String schema, String tableName,
            Connection connection, ColumnInfo[] colInfos) {
        String[] colNames = new String[colInfos.length];
        int[] colTypes = new int[colInfos.length];
        int[] colSizes = new int[colInfos.length];
        int[] colDecimalDigits = new int[colInfos.length];
        boolean[] autoIncrement = new boolean[colInfos.length];

        for (int i = 0; i < colInfos.length; i++) {
            ColumnInfo ci = colInfos[i];
            colNames[i] = ci.colName;
            colTypes[i] = ci.dataType;
            colSizes[i] = ci.size;
            colDecimalDigits[i] = ci.decimalDigits;
            autoIncrement[i] = ci.autoIncrement;
        }

        return new VersionedMulticolumnFIDMapper(schema, tableName, colNames, colTypes, colSizes,
                colDecimalDigits, autoIncrement);
    }
}
