package hirondelle.web4j.config;

import java.util.*;
import java.util.logging.*;
import java.sql.Connection;
import java.sql.SQLException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import hirondelle.web4j.database.DAOException;
import hirondelle.web4j.database.ConnectionSource;
import hirondelle.web4j.util.Util;
import javax.servlet.ServletConfig;

/** 
* Implementation of {@link ConnectionSource}, required by WEB4J.
* 
* <P>This implementation uses a <tt>Connection</tt> pool managed by the container.
* This class is non-final only since it is convenient for {@link hirondelle.fish.test.doubles.FakeConnectionSrc}.
* Only one method can be overridden - {@link #getConnectionByName(String)}. 
*/
public class ConnectionSrc implements ConnectionSource  {

  /** Read in connection strings from <tt>web.xml</tt>. */
  public final void init(ServletConfig aConfig){
    fDefaultDbConnString = aConfig.getInitParameter(DEFAULT_CONN_STRING);
    fAccessControlDbConnectionString = aConfig.getInitParameter(ACCESS_CONTROL_CONN_STRING);
    fTranslationDbConnectionString = aConfig.getInitParameter(TRANSLATION_CONN_STRING);
    ensureAllSettingsPresent();
    
    fMapNameToConnectionString = new LinkedHashMap<String, String>();
    fMapNameToConnectionString.put(DEFAULT, fDefaultDbConnString);
    fMapNameToConnectionString.put(ACCESS_CONTROL, fAccessControlDbConnectionString);
    fMapNameToConnectionString.put(TRANSLATION, fTranslationDbConnectionString);
    fLogger.config("Connection strings : " + Util.logOnePerLine(fMapNameToConnectionString));
  }

  /**
  * Return value contains only {@link #DEFAULT}, {@link #ACCESS_CONTROL}, and {@link #TRANSLATION}.
  */
  public final Set<String> getDatabaseNames(){
    return Collections.unmodifiableSet(fMapNameToConnectionString.keySet()); 
  }
  
  /**
  * Return a {@link Connection} for the default database.
  */
  public final Connection getConnection() throws DAOException {
    return getConnectionByName(DEFAULT);
  }

  /**
  * Return a {@link Connection} for the identified database.
  * 
  * @param aDatabaseName one of the values {@link #DEFAULT}, 
  * {@link #TRANSLATION}, or {@link #ACCESS_CONTROL}.
  */
  public final Connection getConnection(String aDatabaseName) throws DAOException {
    return getConnectionByName(aDatabaseName);
  }
  
  /** 
  * Name used to identify the default database. The default database is the main database,
  * carrying core business data. It is the data that is most often accessed. 
  */
  public static final String DEFAULT = "DEFAULT";
  
  /** Name used to identify the access control database (users, roles, etc.).   */
  public static final String ACCESS_CONTROL = "ACCESS_CONTROL";
  
  /** Name used to identify the translation database.  */
  public static final String TRANSLATION = "TRANSLATION";

  /** 
  * This method can be overridden by a subclass.
  * Such overrides are intended for testing. 
  */ 
  protected Connection getConnectionByName(String aDbName) throws DAOException {
    Connection result = null;
    String dbConnString = getConnectionString(aDbName);  
    if( ! Util.textHasContent(dbConnString) ){
      throw new IllegalArgumentException("Unknown database name : " + Util.quote(aDbName));      
    }
    try {
      Context initialContext = new InitialContext();
      if ( initialContext == null ) {
        fLogger.severe("DataSource problem. InitialContext is null. Db : " + Util.quote(dbConnString));
      }
      DataSource datasource = (DataSource)initialContext.lookup(dbConnString);
      if ( datasource == null ){
        fLogger.severe("Datasource is null for : " + dbConnString);
      }
      result = datasource.getConnection();
    }
    catch (NamingException ex){
      throw new DAOException("Config error with JNDI and datasource, for db " + Util.quote(dbConnString), ex);
    }
    catch (SQLException ex ){
      throw new DAOException("Cannot get JNDI connection from datasource, for db " + Util.quote(dbConnString), ex);
    }
    return result;
  }
  
  /**
  * This item is protected, in order to make it visible to any subclasses created for testing
  * outside of the normal runtime environment.
  */
  protected String getConnectionString(String aDbName){
    return fMapNameToConnectionString.get(aDbName);
  }
  
  //PRIVATE//
  
  /**
  * Maps the database name passed to {@link #getConnection(String)} to the actual connection string.
  */
  private static Map<String, String> fMapNameToConnectionString;

  private static final String DEFAULT_CONN_STRING = "DefaultDbConnectionString";
  private static String fDefaultDbConnString;
  
  private static final String ACCESS_CONTROL_CONN_STRING = "AccessControlDbConnectionString";
  private static String fAccessControlDbConnectionString;
  
  private static final String TRANSLATION_CONN_STRING = "TranslationDbConnectionString";
  private static String fTranslationDbConnectionString;
  
  private static final Logger fLogger = Util.getLogger(ConnectionSrc.class);
  
  private static void ensureAllSettingsPresent(){
    if( ! Util.textHasContent(fDefaultDbConnString) ) {
      logError(DEFAULT_CONN_STRING);
    }
    if( ! Util.textHasContent(fTranslationDbConnectionString) ) {
      logError(TRANSLATION_CONN_STRING);
    }
    if ( ! Util.textHasContent(fAccessControlDbConnectionString) ) {
      logError(ACCESS_CONTROL_CONN_STRING);
    }
  }
  
  private static void logError(String aSettingName){
    fLogger.severe("Web.xml missing init-param setting for " + aSettingName);
  }
}