/*
 * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.amber;

import java.io.IOException;

import java.lang.ref.SoftReference;

import java.util.HashMap;
import java.util.Iterator;
import java.util.ArrayList;

import java.util.logging.Logger;
import java.util.logging.Level;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;

import javax.sql.DataSource;

import javax.naming.InitialContext;

import com.caucho.bytecode.JClass;
import com.caucho.bytecode.JClassLoader;

import com.caucho.loader.DynamicClassLoader;
import com.caucho.loader.EnvironmentLocal;

import com.caucho.loader.enhancer.EnhancerManager;

import com.caucho.jdbc.JdbcMetaData;

import com.caucho.amber.manager.AmberConnection;
import com.caucho.amber.manager.CacheConnection;
import com.caucho.amber.manager.AmberPersistenceUnit;

import com.caucho.amber.entity.AmberEntityHome;
import com.caucho.amber.entity.EntityKey;
import com.caucho.amber.entity.EntityItem;
import com.caucho.amber.entity.AmberCompletion;

import com.caucho.amber.gen.AmberGenerator;
import com.caucho.amber.gen.AmberGeneratorImpl;
import com.caucho.amber.gen.AmberEnhancer;

import com.caucho.amber.query.QueryCacheKey;
import com.caucho.amber.query.ResultSetCacheChunk;

import com.caucho.amber.table.Table;

import com.caucho.amber.type.Type;
import com.caucho.amber.type.EntityType;
import com.caucho.amber.type.GeneratorTableType;
import com.caucho.amber.type.SubEntityType;
import com.caucho.amber.type.TypeManager;

import com.caucho.amber.idgen.IdGenerator;
import com.caucho.amber.idgen.SequenceIdGenerator;

import com.caucho.config.ConfigException;

import com.caucho.java.gen.JavaClassGenerator;

import com.caucho.loader.enhancer.EnhancingClassLoader;

import com.caucho.log.Log;

import com.caucho.util.LruCache;
import com.caucho.util.L10N;

/**
 * Main interface between Resin and the connector.  It's the
 * top-level SPI class for creating the SPI ManagedConnections.
 *
 * The resource configuration in Resin's web.xml will use bean-style
 * configuration to configure the ManagecConnectionFactory.
 */
public class EnvAmberManager {
  private static final Logger log = Log.open(AmberPersistenceUnit.class);
  private static final L10N L = new L10N(AmberPersistenceUnit.class);

  private static EnvironmentLocal<EnvAmberManager> _localManager
    = new EnvironmentLocal<EnvAmberManager>();

  private ClassLoader _parentLoader;

  private ArrayList<AmberPersistenceUnit> _managerList = new ArrayList<AmberPersistenceUnit>();

  private AmberEnhancer _enhancer;

  private long _tableCacheTimeout = 250;

  private JClassLoader _jClassLoader;

  private HashMap<String,AmberEntityHome> _entityHomeMap
    = new HashMap<String,AmberEntityHome>();

  private LruCache<QueryCacheKey,SoftReference<ResultSetCacheChunk>> _queryCache
    = new LruCache<QueryCacheKey,SoftReference<ResultSetCacheChunk>>(1024);

  private LruCache<EntityKey,SoftReference<EntityItem>> _entityCache
    = new LruCache<EntityKey,SoftReference<EntityItem>>(32 * 1024);

  private EntityKey _entityKey = new EntityKey();

  private long _xid;

  private AmberGenerator _generator;

  private volatile boolean _isInit;

  private EnvAmberManager()
  {
    _parentLoader = Thread.currentThread().getContextClassLoader();
    _jClassLoader = EnhancerManager.create(_parentLoader).getJavaClassLoader();

    try {
      if (_parentLoader instanceof DynamicClassLoader)
	((DynamicClassLoader) _parentLoader).make();
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }

    // _enhancer = new AmberEnhancer(this);

    EnhancerManager.create().addClassEnhancer(_enhancer);

    try {
      bindProxy();
    } catch (Throwable e) {
      log.log(Level.FINE, e.toString(), e);
    }
  }

  private void bindProxy()
    throws Throwable
  {
    /*
    EntityManagerProxy userManager = null;//new EntityManagerProxy(this);

    new InitialContext().rebind("java:comp/EntityManager", userManager);
    */
  }

  public static EnvAmberManager createLocal()
  {
    EnvAmberManager manager = _localManager.get();

    if (manager == null) {
      manager = new EnvAmberManager();
      _localManager.set(manager);
    }

    return manager;
  }

  /**
   * Adds an amber manager.
   */
  public void addAmberManager(AmberPersistenceUnit manager)
  {
    _managerList.add(manager);
  }

  /**
   * Set the default table cache time.
   */
  public void setTableCacheTimeout(long timeout)
  {
    _tableCacheTimeout = timeout;
  }

  /**
   * Get the default table cache time.
   */
  public long getTableCacheTimeout()
  {
    return _tableCacheTimeout;
  }

  /**
   * Returns a new xid.
   */
  public long getXid()
  {
    synchronized (this) {
      return _xid++;
    }
  }

  /**
   * Returns the enhanced loader.
   */
  public ClassLoader getEnhancedLoader()
  {
    return _parentLoader;
  }

  /**
   * Returns the enhanced loader.
   */
  public JClassLoader getJClassLoader()
  {
    return _jClassLoader;
  }

  /**
   * Adds the entity home.
   */
  public void addEntityHome(String name, AmberEntityHome home)
  {
    _entityHomeMap.put(name, home);
  }

  /**
   * Returns the entity home.
   */
  public AmberEntityHome getEntityHome(String name)
  {
    if (! _isInit) {
      /* XXX:
      try {
	initEntityHomes();
      } catch (RuntimeException e) {
	throw e;
      } catch (Exception e) {
	throw new AmberRuntimeException(e);
      }
      */
    }
    
    return _entityHomeMap.get(name);
  }

  /**
   * Returns a matching entity.
   */
  public EntityType getEntity(String className)
  {
    AmberEntityHome home = _entityHomeMap.get(className);

    if (home != null)
      return home.getEntityType();
    else
      return null;
  }

  /**
   * Returns a matching entity.
   */
  public EntityType getEntityByInstanceClass(String className)
  {
    throw new UnsupportedOperationException();
  }

  /**
   * Sets the generator.
   */
  public void setGenerator(AmberGenerator generator)
  {
    _generator = generator;
  }

  /**
   * Sets the generator.
   */
  public AmberGenerator getGenerator()
  {
    if (_generator != null)
      return _generator;
    else if (_enhancer != null)
      return _enhancer;
    else {
      // _generator = new AmberGeneratorImpl(this);

      return _generator;
    }
  }

  /**
   * Initialize the resource.
   */
  public void initLoaders()
    throws ConfigException, IOException
  {
  }

  /**
   * Returns the cache connection.
   */
  public AmberConnection createAmberConnection()
  {
    // XXX: needs to be an EnvAmberConnection
    return _managerList.get(0).createAmberConnection();
  }

  /**
   * Initialize the home interfaces.
   */
  public void initEntityHomes()
    throws Exception
  {
    for (AmberPersistenceUnit manager : _managerList)
      manager.initEntityHomes();
  }

  /**
   * Initialize the resource.
   */
  public void init()
    throws ConfigException, IOException
  {
    initLoaders();
  }

  /**
   * Returns an EntityHome.
   */
  public AmberEntityHome getHome(Class cl)
  {
    return getEntityHome(cl.getName());
  }

  /**
   * Returns the query result.
   */
  public ResultSetCacheChunk getQueryChunk(QueryCacheKey key)
  {
    SoftReference<ResultSetCacheChunk> ref = _queryCache.get(key);

    if (ref == null)
      return null;
    else {
      ResultSetCacheChunk chunk = ref.get();

      if (chunk != null && chunk.isValid())
	return chunk;
      else
	return null;
    }
  }

  /**
   * Sets the query result.
   */
  public void putQueryChunk(QueryCacheKey key, ResultSetCacheChunk chunk)
  {
    _queryCache.put(key, new SoftReference<ResultSetCacheChunk>(chunk));
  }

  /**
   * Returns the entity item.
   */
  public EntityItem getEntityItem(String homeName, Object key)
    throws AmberException
  {
    AmberEntityHome home = getEntityHome(homeName);

    // return home.findEntityItem(getCacheConnection(), key, false);

    return null; // XXX:
  }

  /**
   * Returns the query result.
   */
  public EntityItem getEntity(EntityType rootType, Object key)
  {
    SoftReference<EntityItem> ref;

    synchronized (_entityKey) {
      _entityKey.init(rootType, key);
      ref = _entityCache.get(_entityKey);
    }

    if (ref != null)
      return ref.get();
    else
      return null;
  }

  /**
   * Sets the entity result.
   */
  public EntityItem putEntity(EntityType rootType,
			      Object key,
			      EntityItem entity)
  {
    SoftReference<EntityItem> ref = new SoftReference<EntityItem>(entity);
    EntityKey entityKey = new EntityKey(rootType, key);
    
    ref = _entityCache.putIfNew(entityKey, ref);

    return ref.get();
  }

  /**
   * Remove the entity result.
   */
  public EntityItem removeEntity(EntityType rootType, Object key)
  {
    SoftReference<EntityItem> ref;

    synchronized (_entityKey) {
      _entityKey.init(rootType, key);
      ref = _entityCache.remove(_entityKey);
    }

    if (ref != null)
      return ref.get();
    else
      return null;
  }

  /**
   * Completions affecting the cache.
   */
  public void complete(ArrayList<AmberCompletion> completions)
  {
    int size = completions.size();
    if (size == 0)
      return;

    synchronized (_entityCache) {
      Iterator<LruCache.Entry<EntityKey,SoftReference<EntityItem>>> iter;

      iter = _entityCache.iterator();
      while (iter.hasNext()) {
	LruCache.Entry<EntityKey,SoftReference<EntityItem>> entry;
	entry = iter.next();

	EntityKey key = entry.getKey();
	SoftReference<EntityItem> valueRef = entry.getValue();
	EntityItem value = valueRef.get();

	if (value == null)
	  continue;

	EntityType entityRoot = key.getEntityType();
	Object entityKey = key.getKey();

	for (int i = 0; i < size; i++) {
	  if (completions.get(i).complete(entityRoot, entityKey, value)) {
	    // XXX: delete
	  }
	}
      }
    }

    synchronized (_queryCache) {
      Iterator<SoftReference<ResultSetCacheChunk>> iter;

      iter = _queryCache.values();
      while (iter.hasNext()) {
	SoftReference<ResultSetCacheChunk> ref = iter.next();

	ResultSetCacheChunk chunk = ref.get();

	if (chunk != null) {
	  for (int i = 0; i < size; i++) {
	    if (completions.get(i).complete(chunk)) {
	      // XXX: delete
	    }
	  }
	}
      }
    }
  }

  /**
   * destroys the manager.
   */
  public void destroy()
  {
    _queryCache = null;
    _entityCache = null;
    _parentLoader = null;
  }

  public String toString()
  {
    return "EnvAmberManager[]";
  }
}
