/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2011 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * 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; either
 * version 2.1 of the License, or any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.dbm.internal.osgi;

import java.sql.Driver;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.ConnectionPoolDataSource;
import javax.sql.DataSource;
import javax.sql.XADataSource;

import org.osgi.service.jdbc.DataSourceFactory;
import org.ow2.jonas.dbm.internal.DBMConstants;
import org.ow2.jonas.dbm.internal.cm.ConnectionManager;
import org.ow2.jonas.tm.TransactionService;

/**
 * Implementation of the OSGi DataSourceFactory interface.
 * @author Florent Benoit
 */
public class JOnASDataSourceFactory implements DataSourceFactory {

    /**
     * Instance of the driver class which allows to build new driver instance.
     */
    private Class driverClass = null;


    private TransactionService txService = null;

    private String name = null;

    public JOnASDataSourceFactory(final Class driverClass, final TransactionService txService) {
        this.driverClass = driverClass;
        this.txService = txService;
    }

    /**
     * Create a new {@link ConnectionPoolDataSource} using the given properties.
     * @param props The properties used to configure the
     *        {@link ConnectionPoolDataSource}. {@literal null} indicates no
     *        properties. If the property cannot be set on the
     *        {@link ConnectionPoolDataSource} being created then a SQLException
     *        must be thrown.
     * @return A configured {@link ConnectionPoolDataSource}.
     * @throws SQLException If the {@link ConnectionPoolDataSource} cannot be
     *         created.
     */
    @Override
    public ConnectionPoolDataSource createConnectionPoolDataSource(final Properties props) throws SQLException {
        return getConnectionManager(props);
    }

    /**
     * Create a new {@link DataSource} using the given properties.
     * @param props The properties used to configure the {@link DataSource}.
     *        {@literal null} indicates no properties. If the property cannot be
     *        set on the {@link DataSource} being created then a SQLException
     *        must be thrown.
     * @return A configured {@link DataSource}.
     * @throws SQLException If the {@link DataSource} cannot be created.
     */
    @Override
    public DataSource createDataSource(final Properties props) throws SQLException {
        return getConnectionManager(props);
    }

    /**
     * Create a new {@link XADataSource} using the given properties.
     * @param props The properties used to configure the {@link XADataSource}.
     *        {@literal null} indicates no properties. If the property cannot be
     *        set on the {@link XADataSource} being created then a SQLException
     *        must be thrown.
     * @return A configured {@link XADataSource}.
     * @throws SQLException If the {@link XADataSource} cannot be created.
     */
    @Override
    public XADataSource createXADataSource(final Properties props) throws SQLException {
        return getConnectionManager(props);
    }

    protected String getProperty(final Properties props, final String key, final boolean mandatory) throws SQLException {
        return getProperty(props, key, mandatory, null);
    }

    protected String getProperty(final Properties props, final String key, final String defaultValue) throws SQLException {
        return getProperty(props, key, false, defaultValue);
    }

    protected String getProperty(final Properties props, final String key, final boolean mandatory, final String defaultValue) throws SQLException {
        Object val = props.get(key);

        // Check for non null string
        if (val != null && !(val instanceof String)) {
            throw new SQLException("Property value for the '" + key + "' is not a String. Found '" + val + "'.");
        }
        String value = (String) val;

        if (value == null) {
            // mandatory, raise exception
            if (mandatory) {
                throw new SQLException("Property value for the '" + key + "' is missing and it is mandatory.");
            }

            // assign defaultValue
            value = defaultValue;
        }


        return value;


    }

    /**
     * Build a datasource object for the given properties.
     * @param props the given properties
     * @return an instance of the datasource
     * @throws SQLException if datasource can't be built
     */
    protected ConnectionManager getConnectionManager(final Properties props) throws SQLException {

        // Name
        String name = "OSGi/" + driverClass.getName();

        ConnectionManager datasource;
        try {
            datasource = new ConnectionManager(txService);
        } catch (Exception e) {
            throw new SQLException("Unable to create datasource", e);
        }

        // Mandatory properties specified in OSGi interface
        String url = getProperty(props, DataSourceFactory.JDBC_URL, true);
        String user = getProperty(props, DataSourceFactory.JDBC_USER, true);
        String password = getProperty(props, DataSourceFactory.JDBC_PASSWORD, true);

        // Optional specified in OSGi interface
        name = getProperty(props, DataSourceFactory.JDBC_PASSWORD, true);
        String description = getProperty(props, DataSourceFactory.JDBC_DESCRIPTION, "");
        String dataSourceName = getProperty(props, DataSourceFactory.JDBC_DATASOURCE_NAME, name);
        String databaseName = getProperty(props, DataSourceFactory.JDBC_DATABASE_NAME, name);

        String maxPoolSize = getProperty(props, DataSourceFactory.JDBC_MAX_POOL_SIZE, DBMConstants.DEF_MAXCONPOOL);
        String initPoolSize = getProperty(props, DataSourceFactory.JDBC_INITIAL_POOL_SIZE, DBMConstants.DEF_MINCONPOOL);
        String minPoolSize = getProperty(props, DataSourceFactory.JDBC_MIN_POOL_SIZE, initPoolSize);
        String maxIdletime =  getProperty(props, DataSourceFactory.JDBC_MAX_IDLE_TIME, DBMConstants.DEF_MAXWAITTIME);
        String maxStatements = getProperty(props, DataSourceFactory.JDBC_MAX_STATEMENTS, DBMConstants.DEF_PSTMTMAX);

        // not read :
        // DataSourceFactory.JDBC_NETWORK_PROTOCOL
        // DataSourceFactory.JDBC_PROPERTY_CYCLE
        // DataSourceFactory.JDBC_PORT_NUMBER


        // Specific JOnAS features
        String connCheckLevel = getProperty(props, DBMConstants.CONNCHECKLEVEL, DBMConstants.DEF_CONNCHECKLEVEL);
        String connMaxAge = getProperty(props, DBMConstants.CONNMAXAGE, DBMConstants.DEF_CONNMAXAGE);

        String maxOpenTime = getProperty(props, DBMConstants.MAXOPENTIME, DBMConstants.DEF_MAXOPENTIME);
        String maxwaiters = getProperty(props, DBMConstants.MAXWAITERS, DBMConstants.DEF_MAXWAITERS);
        String samplingperiod = getProperty(props, DBMConstants.SAMPLINGPERIOD, DBMConstants.DEF_SAMPLINGPERIOD);
        String adjustperiod = getProperty(props, DBMConstants.ADJUSTPERIOD, DBMConstants.DEF_ADJUSTPERIOD);
        String defaultStatement = getProperty(props, DBMConstants.CONNTESTSTMT, DBMConstants.DEF_CONNTESTSTMT);



        // Initialize ConnectionManager
        datasource.setDatasourceName(dataSourceName);
        datasource.setDSName(databaseName);
        datasource.setUrl(url);
        datasource.setDriverClass(driverClass);
        datasource.setUserName(user);
        datasource.setPassword(password);
        datasource.setDataSourceDescription(description);

        // Not configured
        // datasource.setTransactionIsolation();
        // datasource.setMapperName(dsd.getProperty(MAPPERNAME, DEF_MAPPERNAME).trim());
        datasource.poolConfigure(connCheckLevel,
                         connMaxAge,
                         maxOpenTime,
                         defaultStatement,
                         maxStatements,
                         minPoolSize,
                         maxPoolSize,
                         maxIdletime,
                         maxwaiters,
                         samplingperiod,
                         adjustperiod);


        return datasource;
    }


    /**
     * Create a new {@link Driver} using the given properties.
     * @param props The properties used to configure the {@link Driver}.
     *        {@literal null} indicates no properties. If the property cannot be
     *        set on the {@link Driver} being created then a SQLException must
     *        be thrown.
     * @return A configured {@link Driver}.
     * @throws SQLException If the {@link Driver} cannot be created.
     */
    @Override
    public Driver createDriver(final Properties props) throws SQLException {
        Driver driver;
        try {
            driver = (Driver) driverClass.newInstance();
        } catch (InstantiationException e) {
            throw new SQLException("Unable to build a driver instance", e);
        } catch (IllegalAccessException e) {
            throw new SQLException("Unable to build a driver instance", e);
        }

        //TODO: Apply the properties


        return driver;
    }



}
