/*
  Copyright (C) 2006 Helge Hess

  This file is part of JOPE.

  JOPE 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, or (at your option) any
  later version.

  JOPE 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 JOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

package org.opengroupware.jope.eoaccess;

import java.net.URL;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opengroupware.jope.eocontrol.EOFetchSpecification;
import org.opengroupware.jope.foundation.NSClassLookupContext;
import org.opengroupware.jope.foundation.NSDisposable;
import org.opengroupware.jope.foundation.NSJavaRuntime;
import org.opengroupware.jope.foundation.NSObject;

/*
 * EODatabase
 * 
 * TODO: document
 */
public class EODatabase extends NSObject
  implements NSDisposable, NSClassLookupContext
{
  protected static final Log log = LogFactory.getLog("EODatabase");
  
  protected NSClassLookupContext classLookup;
  protected EOAdaptor adaptor;
  
  public EODatabase(EOAdaptor _adaptor, NSClassLookupContext _clslookup) {
    this.adaptor     = _adaptor;
    this.classLookup = _clslookup != null ? _clslookup : this;
  }
  
  public EODatabase(String _url, EOModel _model, NSClassLookupContext _clslup) {
    this(EOAdaptor.adaptorWithURL(_url, _model), _clslup);
  }
  
  /* accessors */
  
  public EOAdaptor adaptor() {
    return this.adaptor;
  }
  
  public NSClassLookupContext classLookupContext() {
    return this.classLookup;
  }
  
  public EOEntity entityNamed(String _entityName) {
    if (this.adaptor == null)
      return null;
    
    EOModel model = this.adaptor.model();
    if (model == null)
      return null;
    
    return model.entityNamed(_entityName);
  }
  
  public EOEntity entityForObject(Object _object) {
    if (_object == null)
      return null;
    
    /* try to ask the object itself */
    
    EOEntity entity = null;
    if (_object instanceof EOActiveRecord) {
      entity = ((EOActiveRecord)_object).entity();
      if (entity != null)
        return entity;
    }
    
    /* check whether its a generic object */
    
    Class clazz = _object.getClass();
    if (clazz == EOActiveRecord.class) {
      /* can't determine entities for generic objects */
      return null;
    }
    
    /* search in model */
    
    EOModel model = this.adaptor.model();
    if (model == null)
      return null;
    
    return model.entityForObject(_object);
  }
  
  /* operations */
  
  public EODatabaseDataSource dataSourceForEntity(String _ename) {
    return this.dataSourceForEntity(null, _ename);
  }
  public EODatabaseDataSource dataSourceForEntity(EOEntity _entity) {
    return this.dataSourceForEntity(_entity, null);
  }

  public Class dataSourceClassForEntity(EOEntity _entity) {
    if (_entity == null) {
      log.debug("got no entity to detect the database datasource!");
      return EODatabaseDataSource.class;
    }

    String dsClassName = _entity.dataSourceClassName();
    if (dsClassName == null || dsClassName.length() == 0) {
      log.debug("entity has no custom datasource class assigned.");
      return EODatabaseDataSource.class;
    }

    Class dsClass = this.classLookupContext().lookupClass(dsClassName);
    if (dsClass == null) {
      log.error("did not find datasource class '" + dsClassName + "' of " +
                "entity: " + _entity);
    }
    return dsClass;
  }
  
  public EODatabaseDataSource dataSourceForEntity
    (EOEntity _entity, String _ename)
  {
    EOEntity entity = _entity;
    if (entity == null && _ename != null)
      entity = this.entityNamed(_ename);
    if (_ename == null && entity != null)
      _ename = entity.name();
    
    Class dsClass = this.dataSourceClassForEntity(entity);
    if (dsClass == null) return null;

    EODatabaseDataSource ds;
          
    ds = (EODatabaseDataSource)NSJavaRuntime.NSAllocateObject(dsClass,
              new Class[]  { EODatabase.class, String.class },
              new Object[] { this, _ename });
    if (ds == null)
      log.error("could not allocate datasource: " + dsClass);
    
    return ds;
  }
  
  public List objectsWithFetchSpecification(EOFetchSpecification _fs) {
    if (_fs == null)
      return null;
    
    EODatabaseDataSource ds = this.dataSourceForEntity(_fs.entityName());
    ds.setFetchSpecification(_fs);
    List results = ds.fetchObjects();
    ds.dispose();
    return results;
  }
  
  public Exception performDatabaseOperation(EODatabaseOperation _op) {
    if (_op == null)
      return null; /* nothing to do */
    
    EODatabaseChannel channel = new EODatabaseChannel(this);
    Exception error = channel.performDatabaseOperations
      (new EODatabaseOperation[] { _op } );
    channel.dispose();
    return error;
  }

  /* class lookup context (can be used if the subclass lives in the EO pkg) */

  public Class lookupClass(String _name) {
    if (_name == null)
      return null;
    
    // TODO: cache
    
    String fullname = this.getClass().getPackage().getName() + "." + _name;
    Class  cls      = NSJavaRuntime.NSClassFromString(fullname);
    if (cls != null) return cls;
    
    cls = NSJavaRuntime.NSClassFromString(_name);
    if (cls != null) return cls;
    
    log.warn("did not find requested class: " + _name);
    return null;
  }
  
  /* low level adaptor creation method */

  public static EOAdaptor dbAdaptorForURL(Class _cls, String _dbURL) {
    if (_cls == null) {
      log.error("got no EODatabase class to create the adaptor for:" + _dbURL);
      return null;
    }
    if (_dbURL == null) {
      log.error("got no URL create the adaptor for:" + _cls);
      return null;
    }
    
    /* derive model name (eg OGoDatabase => OGoModel.xml) */
    
    String modelName = _cls.getSimpleName();
    if (modelName.endsWith("Database"))
      modelName = modelName.substring(0, modelName.length() - 8);
    modelName += "Model.xml";
    
    /* load model */
    
    
    URL     modelURL     = _cls.getResource(modelName);
    EOModel modelPattern = null;
    try {
      if (modelURL != null)
        modelPattern = EOModel.loadModel(modelURL);
      else
        log.warn("did not find database model resource: " + modelName);
    }
    catch (Exception e) {
      log.error("could not load database model " + modelName, e);
      return null;
    }
    
    /* setup adaptor */
    
    EOAdaptor adaptor = EOAdaptor.adaptorWithURL(_dbURL, modelPattern);
    if (adaptor == null) {
      log.error("got no adaptor for DB URL: " + _dbURL);
      return null;
    }
    
    if (!adaptor.testConnect()) {
      log.error("adaptor could not connect to DB URL: " + _dbURL);
      return null;
    }

    /* make adaptor fetch the model from the database (if necessary) */
    
    EOModel model = adaptor.model();
    if (model == null) {
      log.error("adaptor could not retrieve model for DB URL: " + _dbURL);
      return null;
    }
    
    return adaptor;
  }
  
  /* dispose */
  
  public void dispose() {
    if (this.adaptor != null)
      this.adaptor.dispose();
    this.adaptor = null;
  }
  
  /* description */
  
  public void appendAttributesToDescription(StringBuffer _d) {
    super.appendAttributesToDescription(_d);
    
    if (this.adaptor != null) _d.append(" adaptor=" + this.adaptor);
  }
}
