/*
 * This program is free software; you can redistribute it and/or modify it under the 
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software 
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this 
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html 
 * or from the Free Software Foundation, Inc., 
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program 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.
 *
 * Copyright 2008 - 2009 Pentaho Corporation.  All rights reserved.
*/
package org.pentaho.pac.server.common;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.pentaho.pac.server.i18n.Messages;

/**
 * Configures and provides access to Hibernate sessions, tied to the
 * current thread of execution.  Follows the Thread Local Session
 * pattern, see {@link http://hibernate.org/42.html }.
 */
public class HibernateSessionFactory {

  public static final String DEFAULT_CONFIG_NAME = "$$DEFAULT_CONFIG"; //$NON-NLS-1$

  public static String DEFAULT_CONFIG_FILE_LOCATION = "system/hibernate/hsql.hibernate.cfg.xml"; //$NON-NLS-1$

  public static String SLASH = "/"; //$NON-NLS-1$

  /** 
     * Location of hibernate.cfg.xml file.
     * Location should be on the classpath as Hibernate uses  
     * #resourceAsStream style lookup for its configuration file. 
     * The default classpath location of the hibernate config file is 
     * in the default package. Use #setConfigFile() to update 
     * the location of the configuration file for the current session.   
     */
  // private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
  // private  static org.hibernate.cfg.AnnotationConfiguration configuration = new AnnotationConfiguration();
  //private static org.hibernate.SessionFactory sessionFactory;
  private static Map<String, HibConfig> configs = new HashMap<String, HibConfig>();

  private static String defaultConfigFile = null;

  private static boolean isHibernateManaged = false;

  private static String factoryJndiName;

  private static Context initialContext;

  private HibernateSessionFactory() {
  }

  public static void initDefaultConfiguration() {
    String hibernateConfigPath = AppConfigProperties.getInstance().getHibernateConfigPath();
    String solutionPath = AppConfigProperties.getInstance().getSolutionPath();
    if (hibernateConfigPath != null && hibernateConfigPath.length() > 0) {
      defaultConfigFile = solutionPath + SLASH + hibernateConfigPath;
      addOrUpdateConfiguration(DEFAULT_CONFIG_NAME, defaultConfigFile);
    } else {
      defaultConfigFile = solutionPath + SLASH + DEFAULT_CONFIG_FILE_LOCATION;
      addOrUpdateConfiguration(DEFAULT_CONFIG_NAME, defaultConfigFile);
    }
  }

  public static void addConfiguration(String name, String configFile) {
    if (!configs.containsKey(name))
      addOrUpdateConfiguration(name, configFile);
  }

  public static void addOrUpdateConfiguration(String name, String configFile) {
    Configuration configuration = new AnnotationConfiguration();

    try {
      File file = new File(configFile);
      if (file != null && file.exists()) {
        configuration.configure(file);
      } else {
        configuration.configure(configFile);
      }

      // If Hibernate is running in a managed environment
      isHibernateManaged = AppConfigProperties.getInstance().isHibernateManaged();
      if (!isHibernateManaged) {
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        configs.put(name, new HibConfig(sessionFactory, configuration, configFile));
      } else {
        factoryJndiName = configuration.getProperty(Environment.SESSION_FACTORY_NAME);
        if (factoryJndiName != null && factoryJndiName.length() > 0) {
          configuration.buildSessionFactory(); // Let hibernate Bind it to JNDI...  
        } else {
          throw new HibernateException(
              Messages
                  .getErrorString(
                      "HibernateSessionFactory.ERROR_0002_MISSING_MANAGED_CONFIGURATION", Environment.SESSION_FACTORY_NAME.toString()));//$NON-NLS-1$
        }
      }
    } catch (Exception e) {
      throw new HibernateException(Messages.getErrorString(
          "HibernateSessionFactory.ERROR_0003_UNABLE_TO_CREATE_SESSION_FACTORY", e.getLocalizedMessage()), e);//$NON-NLS-1$
    }
  }

  /**
     * Returns the ThreadLocal Session instance.  Lazy initialize
     * the <code>SessionFactory</code> if needed.
     *
     *  @return Session
     *  @throws HibernateException
     */
  public static Session getSession() throws HibernateException {
    return getSession(DEFAULT_CONFIG_NAME);
  }

  /**
   * Returns the ThreadLocal Session instance.  Lazy initialize
   * the <code>SessionFactory</code> if needed.
   *
   *  @return Session
   *  @throws HibernateException
   */
  public static Session getSession(String configName) throws HibernateException {
    HibConfig cfg = configs.get(configName);
    if (cfg == null)
      throw new HibernateException(Messages.getErrorString(
          "HibernateSessionFactory.ERROR_0001_UNKNOWN_CONFIGURATION", configName)); //$NON-NLS-1$
    Session session = cfg.threadLocal.get();

    if (session == null || !session.isOpen()) {
      if (cfg.sessionFactory == null) {
        rebuildSessionFactory(cfg);
      }
      session = (cfg.sessionFactory != null) ? cfg.sessionFactory.openSession() : null;
      cfg.threadLocal.set(session);
    }

    return session;
  }

  /**
     *  Rebuild hibernate session factory
     *
     */
  public static void rebuildSessionFactory(HibConfig cfg) {
    try {
      cfg.configuration.configure(cfg.configFile);
      cfg.sessionFactory = cfg.configuration.buildSessionFactory();
    } catch (Exception e) {
      throw new HibernateException(Messages.getErrorString(
          "HibernateSessionFactory.ERROR_0004_UNABLE_TO_REBUILD_SESSION_FACTORY", e.getLocalizedMessage()));//$NON-NLS-1$
    }
  }

  /**
     *  Close the single hibernate session instance.
     *
     *  @throws HibernateException
     */
  public static void closeSession() throws HibernateException {
    closeSession(DEFAULT_CONFIG_NAME);
  }

  public static void closeSession(String configName) {
    HibConfig cfg = configs.get(configName);
    if (cfg == null)
      throw new HibernateException(Messages.getErrorString(
          "HibernateSessionFactory.ERROR_0001_UNKNOWN_CONFIGURATION", configName)); //$NON-NLS-1$    	
    Session session = (Session) cfg.threadLocal.get();
    cfg.threadLocal.set(null);

    if (session != null) {
      session.close();
    }
  }

  /**
     *  return session factory
     *
     */
  public static org.hibernate.SessionFactory getSessionFactory() {
    return getSessionFactory(defaultConfigFile);
  }

  public static org.hibernate.SessionFactory getSessionFactory(String configName) {
    isHibernateManaged = AppConfigProperties.getInstance().isHibernateManaged();
    return getSessionFactory(configName, isHibernateManaged);
  }

  private static org.hibernate.SessionFactory getSessionFactory(String configName, boolean isHibernateManaged) {
    // If Hibernate is managed than get the session factory from Jndi otherwise return the session factory from the map
    if (!isHibernateManaged) {
      HibConfig cfg = configs.get(configName);
      if (cfg == null)
        throw new HibernateException(Messages.getErrorString(
            "HibernateSessionFactory.ERROR_0001_UNKNOWN_CONFIGURATION", configName)); //$NON-NLS-1$
      return cfg.sessionFactory;
    } else {
      SessionFactory sf = null;
      try {
        if (initialContext == null) {
          initialContext = new InitialContext();
        }
        String jndiName = factoryJndiName;
        try {
          sf = (SessionFactory) initialContext.lookup(jndiName);
        } catch (Exception ignored) {
        }
        if (sf == null) {
          try {
            sf = (SessionFactory) initialContext.lookup("java:" + jndiName); //$NON-NLS-1$
          } catch (Exception ignored) {
            ignored.printStackTrace();
          }
        }
      } catch (NamingException ignored) {
      }
      return sf;

    }
  }

  /**
     *  return hibernate configuration
     *
     */
  public static Configuration getConfiguration() {
    return getConfiguration(defaultConfigFile);
  }

  /**
     *  return hibernate configuration
     *
     */
  public static Configuration getConfiguration(String configName) {
    HibConfig cfg = configs.get(configName);
    if (cfg == null)
      throw new HibernateException(Messages.getErrorString(
          "HibernateSessionFactory.ERROR_0001_UNKNOWN_CONFIGURATION", configName)); //$NON-NLS-1$

    return cfg.configuration;
  }

  private static class HibConfig {
    HibConfig(SessionFactory sessionFactory, Configuration configuration, String configFile) {
      this.sessionFactory = sessionFactory;
      this.configuration = configuration;
      this.configFile = configFile;
    }

    SessionFactory sessionFactory;

    Configuration configuration;

    ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();

    String configFile;

  }

}