/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) Copyright IBM Corporation, 2005-2007. All rights reserved.
 *
 *    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.db2;

import org.geotools.data.AbstractDataStoreFactory;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.jdbc.ConnectionPool;
import org.geotools.data.jdbc.JDBCDataStoreConfig;
import org.geotools.data.jdbc.datasource.DataSourceUtil;
import org.geotools.data.jdbc.datasource.ManageableDataSource;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Logger;

import javax.sql.DataSource;


/**
 * Implements the DataStoreFactorySpi interface to create an instance of a
 * DB2DataStore.
 *
 * @author David Adler - IBM Corporation
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/db2/src/main/java/org/geotools/data/db2/DB2DataStoreFactory.java $
 */
public class DB2DataStoreFactory extends AbstractDataStoreFactory
    implements DataStoreFactorySpi {
    public static final String DRIVERNAME = "com.ibm.db2.jcc.DB2Driver";

    private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(
            "org.geotools.data.db2");

    // The DB2 JDBC universal driver class.  isAvailable() uses this to
    // check whether the DB2 JDBC library is in the classpath
    private static final String DRIVER_CLASS = DRIVERNAME;
    private static boolean isAvailable = false;
    private static final Param DBTYPE = new Param("dbtype", String.class,
            "must be 'DB2'", true, "DB2");
    private static final Param HOST = new Param("host", String.class,
            "DB2 host machine", true, "localhost");
    private static final Param PORT = new Param("port", Integer.class,
            "DB2 connection port", true, new Integer(50000));
    private static final Param DATABASE = new Param("database", String.class,
            "database name", true);
    private static final Param USER = new Param("user", String.class,
            "user name to login as", false);
    private static final Param PASSWD = new Param("passwd", String.class,
            "password used to login", false);
    private static final Param TABSCHEMA = new Param("tabschema", String.class,
            "default table schema", false);
    public static final Param MAXCONN = new Param("max connections", Integer.class,
            "maximum number of open connections", false, new Integer(10));
    
    public static final Param MINCONN = new Param("min connections", Integer.class,
            "minimum number of pooled connection", false, new Integer(4));
    
    public static final Param VALIDATECONN = new Param("validate connections", Boolean .class,
            "check connection is alive before using it", false, Boolean.FALSE);
    static final Param[] DB2PARMS = {
            DBTYPE, HOST, PORT, DATABASE, USER, PASSWD, TABSCHEMA, MAXCONN, MINCONN, VALIDATECONN
        };

    /**
     * canProcess and lastParams are used to cut out processing when
     * 'canProcess' is called successively.
     */
    private boolean canProcess = false;
    private Map lastParams = null;

    /**
     * Constructs a DB2 data store using the params.
     * 
     * If the port number is zero we will try to use the JDBC type 2 driver
     * and if the port number is non-zer, we will try to use the JDBC type 4
     * driver
     *
     * @param params The full set of information needed to construct a live
     *        data source.  Should have  dbtype equal to DB2, as well as host,
     *        user, passwd, database, and table schema.
     *
     * @return The created DataSource, this may be null if the required
     *         resource was not found or if insufficent parameters were given.
     *         Note that canProcess() should have returned false if the
     *         problem is to do with insuficent parameters.
     *
     * @throws IOException See DataSourceException
     * @throws DataSourceException Thrown if there were any problems creating
     *         or connecting the datasource.
     */
    public DataStore createDataStore(Map params) throws IOException {
        if (!canProcess(params)) {
            throw new IOException("Invalid parameters");
        }

        String host = (String) HOST.lookUp(params);
        String user = (String) USER.lookUp(params);
        String passwd = (String) PASSWD.lookUp(params);
        int port = ((Integer) PORT.lookUp(params)).intValue();
        String database = (String) DATABASE.lookUp(params);
        String tabschema = (String) TABSCHEMA.lookUp(params);
        Integer maxConn = (Integer) MAXCONN.lookUp(params);
        Integer minConn = (Integer) MINCONN.lookUp(params);
        Boolean validateConn = (Boolean) VALIDATECONN.lookUp(params);
        
        boolean validate = validateConn != null && validateConn.booleanValue();
        int maxActive = maxConn != null ? maxConn.intValue() : 10;
        int maxIdle = minConn != null ? minConn.intValue() : 4;
        String url = getJDBCUrl(host, port, database);
        DataSource source = getDefaultDataSource(url, user, passwd, maxActive, maxIdle, validate);

        // If the table schema is null or blank, uset the userid for the table schema
        if (tabschema == null || tabschema.length() == 0) {
        	tabschema = user;
        }
        // if the table schema is not double-quoted, convert it to uppercase.
        // if it is double-quoted, remove the double quotes.
        if (tabschema.startsWith("\"")) {
        	tabschema = tabschema.substring(1,tabschema.length()-1);
        } else {
        	tabschema = tabschema.toUpperCase();
        }
        // Set the namespace and databaseSchemaName both to the table schema name
        // Set the timeout value to 100 seconds to force FeatureTypeHandler caching
        JDBCDataStoreConfig config = new JDBCDataStoreConfig(tabschema,
                tabschema, 10000);
        DB2DataStore ds;

        try {
            ds = new DB2DataStore(source, config, url);
        } catch (IOException e) {
            LOGGER.info("Create DB2Datastore failed: "
                    + e);
            throw new DataSourceException("Could not create DB2DataStore", e);
        }

        LOGGER.info("Successfully created DB2Datastore for: "
            + host + ":" + port + "/" + database);

        return ds;
    }

    public static ManageableDataSource getDefaultDataSource(String url, String user, String passwd, int maxActive, int minIdle, boolean validate) throws DataSourceException {
        return DataSourceUtil.buildDefaultDataSource(url, DRIVER_CLASS, user, passwd, maxActive, minIdle, validate ? "select current date from sysibm.sysdummy1" : null, false, 0);
    }

    /**
     * Returns the JDBC url used for connecting to a specific database
     */
    public static String getJDBCUrl(String host, int port, String database) {
        return "jdbc:db2://" + host + ":" + port + "/" + database;
    }

    /**
     * Creating a new DB2 database is not supported.
     *
     * @param params Doesn't much matter what this contains.
     *
     * @return DataStore But will always throw an exception
     *
     * @throws UnsupportedOperationException Cannot create new database
     */
    public DataStore createNewDataStore(Map params)
        throws UnsupportedOperationException {
        throw new UnsupportedOperationException(
            "Creating a new DB2 database is not supported");
    }

    /**
     * Provide a String description of this data store.
     *
     * @return the data store description.
     */
    public String getDescription() {
        return "DB2 Data Store";
    }

    /**
     * Name suitable for display to end user.
     * 
     * <p>
     * A non localized display name for this data store type.
     * </p>
     *
     * @return A short name suitable for display in a user interface.
     */
    public String getDisplayName() {
        return "DB2";
    }

    /**
     * Returns the array of parameters used by DB2.
     *
     * @return Param[] Array of parameters.
     */
    public Param[] getParametersInfo() {
        return DB2PARMS;
    }

    /**
     * Check whether the parameter list passed identifies it as a request for a
     * DB2DataStore.
     * 
     * <p>
     * Most critical is the 'dbtype' parameter which must have the value 'DB2'.
     * If it is, then the remaining parameter values can be checked.
     * </p>
     *
     * @param params Key/Value parameter list containing values required to
     *        identify a request for a DB2DataStore and remaining values to
     *        identify the database to be connected to.
     *
     * @return true if dbtype equals DB2, and contains keys for host, user,
     *         passwd, and database.
     */
    public boolean canProcess(Map params) {
        String logInfo = "";

        // Hopefully we won't be called with a null parameter list.		
        if (params == null) {
            return false;
        }

        // Can't do anything if no dbtype or the dbtype is not DB2	
        String dbtype = (String) params.get("dbtype");

        if (dbtype == null) {
            return (false);
        }

        if (!(dbtype.equalsIgnoreCase("DB2"))) {
            return (false);
        }

        // If the parameters are the same as last time and it was ok last time
        // it should still be ok.
        if (this.canProcess && (this.lastParams == params)) {
            return true;
        }

        if (!super.canProcess(params)) {
            return false;
        }

        this.lastParams = params;
        this.canProcess = true;

        return true;
    }

    /**
     * Check whether the DB2 JDBC type 4 driver is found in the classpath.
     * 
     * <p>
     * If it isn't, there is a problem since the FactoryFinder found the
     * DB2DataStoreFactory but there is no driver to connect to a DB2
     * database.
     * </p>
     * 
     * <p>
     * The classpath should have db2jcc.jar and db2jcc_license_cu.jar
     * </p>
     *
     * @return true if a DB2 driver is available for the DB2DataStore to
     *         connect to a DB2 database.
     */
    public boolean isAvailable() {
        if (isAvailable) {
            return isAvailable;
        }

        try {
            Class.forName(DRIVER_CLASS);
            isAvailable = true;
        } catch (ClassNotFoundException e) {
            isAvailable = false;
        }

        LOGGER.info("DB2 driver found: " + isAvailable);

        return isAvailable;
    }
}
