/*
 * Copyright (c) 2008, Harald Kuhr
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name "TwelveMonkeys" nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.twelvemonkeys.sql;


import java.lang.reflect.*;
import java.util.*;
import java.sql.SQLException;
import java.sql.Connection;

/*
  Det vi trenger er en mapping mellom
  - abstrakt navn/klasse/type/identifikator (tilsv. repository)
  - java klasse
  - selve mappingen av db kolonne/java property

  I tillegg en mapping mellom alle objektene som brukes i VM'en, og deres id'er

*/

/**
 * Under construction.
 *
 * @author Harald Kuhr (haraldk@iconmedialab.no), 
 * @version 0.5
 */
public abstract class ObjectManager {
    private ObjectReader mObjectReader = null;

    private WeakHashMap mLiveObjects = new WeakHashMap(); // object/id

    private Hashtable mTypes = new Hashtable();    // type name/java class
    private Hashtable mMappings = new Hashtable(); // type name/mapping

    /**
     * Creates an Object Manager with the default JDBC connection
     */

    public ObjectManager() {
    	this(DatabaseConnection.getConnection());
    }

    /**
     * Creates an Object Manager with the given JDBC connection
     */

    public ObjectManager(Connection pConnection) {
    	mObjectReader = new ObjectReader(pConnection);
    }


    /**
     * Gets the property/column mapping for a given type
     */

    protected Hashtable getMapping(String pType) {
    	return (Hashtable) mMappings.get(pType);
    }

    /**
     * Gets the class for a type
     *
     * @return The class for a type. If the type is not found, this method will
     * throw an excpetion, and will never return null.
     */

    protected Class getType(String pType) {
    	Class cl = (Class) mTypes.get(pType);

    	if (cl == null) {
    	    // throw new NoSuchTypeException();
    	}
    	
    	return cl;
    }

    /**
     * Gets a java object of the class for a given type.
     */

    protected Object getObject(String pType) 
    /*throws XxxException*/ {
    	// Get class
    	Class cl = getType(pType);

    	// Return the new instance (requires empty public constructor)
    	try {
    	    return cl.newInstance();
    	}
    	catch (Exception e) {
    	    // throw new XxxException(e);
    	    throw new RuntimeException(e.getMessage());
    	}
    	
    	// Can't happen
    	//return null;
    }

    /**
     * Gets a DatabaseReadable object that can be used for looking up the 
     * object properties from the database.
     */

    protected DatabaseReadable getDatabaseReadable(String pType) {
    	
    	return new DatabaseObject(getObject(pType), getMapping(pType));
    }

    /**
     * Reads the object of the given type and with the given id from the 
     * database
     */

    // interface
    public Object getObject(String pType, Object pId)
        throws SQLException {
    	
    	// Create DatabaseReadable and set id
    	DatabaseObject dbObject = (DatabaseObject) getDatabaseReadable(pType); 
    	dbObject.setId(pId);

    	// Read it
    	dbObject = (DatabaseObject) mObjectReader.readObject(dbObject);

    	// Return it
    	return dbObject.getObject();
    }
    
    /**
     * Reads the objects of the given type and with the given ids from the 
     * database
     */

    // interface
    public Object[] getObjects(String pType, Object[] pIds)
        throws SQLException {
    	
    	// Create Vector to hold the result
    	Vector result = new Vector(pIds.length);

    	// Loop through Id's and fetch one at a time (no good performance...)
    	for (int i = 0; i < pIds.length; i++) {
    	    // Create DBObject, set id and read it
    	    DatabaseObject dbObject = 
    	    	(DatabaseObject) getDatabaseReadable(pType);

    	    dbObject.setId(pIds[i]);
    	    dbObject = (DatabaseObject) mObjectReader.readObject(dbObject);

    	    // Add to result if not null
    	    if (dbObject != null) {
    	    	result.add(dbObject.getObject());
    	    }
    	}

    	// Create array of correct type, length equal to Vector
    	Class cl = getType(pType);
    	Object[] arr = (Object[]) Array.newInstance(cl, result.size());

    	// Return the vector as an array
    	return result.toArray(arr);
    }

    /**
     * Reads the objects of the given type and with the given properties from
     * the database
     */

    // interface
    public Object[] getObjects(String pType, Hashtable pWhere)
        throws SQLException {
    	return mObjectReader.readObjects(getDatabaseReadable(pType), pWhere); 
    }

    /**
     * Reads all objects of the given type from the database
     */

    // interface
    public Object[] getObjects(String pType)
        throws SQLException {
    	return mObjectReader.readObjects(getDatabaseReadable(pType)); 
    }
    
    // interface
    public Object addObject(Object pObject) {
    	// get id...

    	return pObject;
    }

    // interface
    public Object updateObject(Object pObject) {
    	// get id...

    	return pObject;
    }

    // interface
    public abstract Object deleteObject(String pType, Object pId);

    // interface
    public abstract Object deleteObject(Object pObject);

    // interface
    public abstract Object createObject(String pType, Object pId);

    // interface
    public abstract Object createObject(String pType);

}

/**
 * Utility class for reading Objects from the database
 */

class DatabaseObject implements DatabaseReadable {
    Hashtable mMapping = null;

    Object mId = null;
    Object mObject = null;
    
    public DatabaseObject(Object pObject, Hashtable pMapping) {
    	setObject(pObject);
    	setMapping(pMapping);
    }

    public Object getId() {
    	return mId;
    }
    
    public void setId(Object pId) {
    	mId = pId;
    }
    
    public void setObject(Object pObject) {
	mObject = pObject;
    }
    public Object getObject() {
	return mObject;
    }

    public void setMapping(Hashtable pMapping) {
    	mMapping = pMapping;
    }

    public Hashtable getMapping() {
    	return mMapping;
    }       
}
