/* Copyright (C) 2004 - 2008  db4objects Inc.  http://www.db4o.com

This file is part of the db4o open source object database.

db4o is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation and as clarified by db4objects' GPL 
interpretation policy, available at
http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
Suite 350, San Mateo, CA 94403, USA.

db4o 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 General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. */
package com.db4o.reflect.generic;

import com.db4o.foundation.*;
import com.db4o.internal.*;
import com.db4o.reflect.*;

/**
 * db4o provides GenericReflector as a wrapper around specific 
 * reflector (delegate). GenericReflector is set when an 
 * ObjectContainer is opened. All subsequent reflector 
 * calls are routed through this interface.<br><br>
 * An instance of GenericReflector can be obtained through
 * {@link com.db4o.ext.ExtObjectContainer#reflector()}.<br><br>
 * GenericReflector keeps list of known classes in memory. 
 * When the GenericReflector is called, it first checks its list of 
 * known classes. If the class cannot be found, the task is 
 * transferred to the delegate reflector. If the delegate fails as 
 * well, generic objects are created, which hold simulated 
 * "field values" in an array of objects.<br><br>
 * Generic reflector makes possible the following usecases:<ul>
 * <li>running a db4o server without deploying application classes;</li>
 * <li>running db4o on Java dialects without reflection (J2ME CLDC, MIDP);</li>
 * <li>easier access to stored objects where classes or fields are not available;</li>
 * <li>running refactorings in the reflector;</li>
 * <li>building interfaces to db4o from any programming language.</li></ul>
 * <br><br>
 * One of the live usecases is ObjectManager, which uses GenericReflector 
 * to read C# objects from Java.
 */
public class GenericReflector implements Reflector, DeepClone {
	
	private KnownClassesRepository _repository;

	/* default delegate Reflector is the JdkReflector */
	private Reflector _delegate;
    private GenericArrayReflector _array;
    
    
    private Collection4 _collectionPredicates = new Collection4();
    private Collection4 _collectionUpdateDepths = new Collection4();

	// todo: Why have this when there is already the _repository by name? Redundant
	private final Hashtable4 _classByClass = new Hashtable4();

	private Transaction _trans;
	private ObjectContainerBase _stream;
	
	/**
	 * Creates an instance of GenericReflector
	 * @param trans transaction
	 * @param delegateReflector delegate reflector, 
	 * providing specific reflector functionality. For example 
	 */
	public GenericReflector(Transaction trans, Reflector delegateReflector){
		_repository=new KnownClassesRepository(new GenericClassBuilder(this,delegateReflector));
		setTransaction(trans);
		_delegate = delegateReflector;
        if(_delegate != null){
            _delegate.setParent(this);
        }
	}
	
	public GenericReflector(Reflector delegateReflector) {
		this(null, delegateReflector);
	}
	
	/**
	 * Creates a clone of provided object
	 * @param obj object to copy
	 * @return copy of the submitted object
	 */
	public Object deepClone(Object obj)  {
        GenericReflector myClone = new GenericReflector(null, (Reflector)_delegate.deepClone(this));
        myClone._collectionPredicates = (Collection4)_collectionPredicates.deepClone(myClone);
        myClone._collectionUpdateDepths = (Collection4)_collectionUpdateDepths.deepClone(myClone);
        
        
        // Interesting, adding the following messes things up.
        // Keep the code, since it may make sense to carry the
        // global reflectors into a running db4o session.
        
        
//        Iterator4 i = _classes.iterator();
//        while(i.hasNext()){
//            GenericClass clazz = (GenericClass)i.next();
//            clazz = (GenericClass)clazz.deepClone(myClone);
//            myClone._classByName.put(clazz.getName(), clazz);
//            myClone._classes.add(clazz);
//        }
        
		return myClone;
	}
	
	ObjectContainerBase getStream(){
		return _stream;
	}

	/**
	 * If there is a transaction assosiated with the current refector.
	 * @return true if there is a transaction assosiated with the current refector.
	 */
	public boolean hasTransaction(){
		return _trans != null;
	}
	
	/**
	 * Associated a transaction with the current reflector.
	 * @param trans
	 */
	public void setTransaction(Transaction trans){
		if(trans != null){
			_trans = trans;
			_stream = trans.container();
		}
		_repository.setTransaction(trans);
	}

	/**
	 * @return generic reflect array instance.
	 */
    public ReflectArray array() {
        if(_array == null){
            _array = new GenericArrayReflector(this);
        }
        return _array;
    }

    /**
     * Determines collection update depth for the specified class
     * @param candidate candidate class  
     * @return collection update depth for the specified class
     */
    public int collectionUpdateDepth(ReflectClass candidate) {
        Iterator4 i = _collectionUpdateDepths.iterator();
        while(i.moveNext()){
        	CollectionUpdateDepthEntry entry = (CollectionUpdateDepthEntry) i.current();
        	if (entry._predicate.match(candidate)) {
        		return entry._depth;
        	}
        }
        return 2;
        
        //TODO: will need knowledge for .NET collections here
    }

    GenericClass ensureDelegate(ReflectClass clazz){
        if(clazz == null){
        	return null;
        }
        GenericClass claxx = (GenericClass)_repository.lookupByName(clazz.getName());
        if(claxx == null){
            //  We don't have to worry about the superclass, it can be null
            //  because handling is delegated anyway
			claxx = genericClass(clazz);
			_repository.register(claxx);
        }
        return claxx;
    }

	private GenericClass genericClass(ReflectClass clazz) {
		GenericClass ret;
		String name = clazz.getName();
		if(name.equals(GenericArray.class.getName())){ // special case, comparing name because can't compare class == class directly with ReflectClass
			ret = new GenericArrayClass(this, clazz, name, null);
		} else {
			ret = new GenericClass(this, clazz, name, null);
		}
		return ret;
	}

	/**
	 * Returns a ReflectClass instance for the specified class
	 * @param clazz class
	 * @return a ReflectClass instance for the specified class
	 * @see com.db4o.reflect.ReflectClass
	 */
	public ReflectClass forClass(Class clazz) {
        if(clazz == null){
            return null;
        }        
        ReflectClass claxx = (ReflectClass) _classByClass.get(clazz);
        if(claxx != null){
            return claxx;
        }
        if (!clazz.isArray() && ReflectPlatform.isNamedClass(clazz)) {
	        claxx = forName(ReflectPlatform.fullyQualifiedName(clazz));
	        if(claxx != null){
	            _classByClass.put(clazz, claxx);
	            return claxx;
	        }
        }
        claxx = _delegate.forClass(clazz);
        if(claxx == null){
            return null;
        }
        claxx = ensureDelegate(claxx);
        _classByClass.put(clazz, claxx);
        return claxx;
    }
    
	 /**
	  * Returns a ReflectClass instance for the specified class name
	 * @param className  class name
	 * @return a ReflectClass instance for the specified class name
	 * @see com.db4o.reflect.ReflectClass
	 */
    public ReflectClass forName(String className) {
        ReflectClass clazz = _repository.lookupByName(className);
        if(clazz != null){
            return clazz;
        }
        clazz = _delegate.forName(className);
        if(clazz != null){
            return ensureDelegate(clazz);
        }
    	return _repository.forName(className);
    }

    /**
	  * Returns a ReflectClass instance for the specified class object
	 * @param obj class object
	 * @return a ReflectClass instance for the specified class object
	 * @see com.db4o.reflect.ReflectClass
	 */
    public ReflectClass forObject(Object obj) {
        if (obj instanceof GenericObject){
			return forGenericObject((GenericObject)obj);
        }
        if (obj instanceof GenericArray){
			return ((GenericArray)obj)._clazz;
        }
        return _delegate.forObject(obj);
    }

	private ReflectClass forGenericObject(final GenericObject genericObject) {
		GenericClass claxx = genericObject._class;
		if(claxx == null){
			throw new IllegalStateException(); 
		}
		String name = claxx.getName();
		if(name == null){
			throw new IllegalStateException();
		}
		GenericClass existingClass = (GenericClass) forName(name);
		if(existingClass == null){
			_repository.register(claxx);
			return claxx;
		}
		// TODO: Using .equals() here would be more consistent with 
		//       the equals() method in GenericClass.
		if(existingClass != claxx){
			
			throw new IllegalStateException();
		}
		
		return claxx;
	}
    
	/**
	 * Returns delegate reflector
	 * @return delegate reflector
	 */
    public Reflector getDelegate(){
        return _delegate;
    }

    /**
     * Determines if a candidate ReflectClass is a collection
     * @param candidate candidate ReflectClass 
     * @return true  if a candidate ReflectClass is a collection.
     */
    public boolean isCollection(ReflectClass candidate) {
        //candidate = candidate.getDelegate(); 
        Iterator4 i = _collectionPredicates.iterator();
        while(i.moveNext()){
            if (((ReflectClassPredicate)i.current()).match(candidate)) {
            	return true;
            }
        }
        return _delegate.isCollection(candidate.getDelegate());
        
        //TODO: will need knowledge for .NET collections here
        // possibility: call registercollection with strings
    }

    /**
     * Register a class as a collection 
     * @param clazz class to be registered
     */
    public void registerCollection(Class clazz) {
		registerCollection(classPredicate(clazz));
    }

    /**
     * Register a predicate as a collection 
     * @param predicate predicate to be registered
     */
    	public void registerCollection(ReflectClassPredicate predicate) {
		_collectionPredicates.add(predicate);
	}

	private ReflectClassPredicate classPredicate(Class clazz) {
		final ReflectClass collectionClass = forClass(clazz);
		ReflectClassPredicate predicate = new ReflectClassPredicate() {
			public boolean match(ReflectClass candidate) {
	            return collectionClass.isAssignableFrom(candidate);
			}
		};
		return predicate;
	}

	/**
	 * Register update depth for a collection class
	 * @param clazz class
	 * @param depth update depth
	 */
    public void registerCollectionUpdateDepth(Class clazz, int depth) {
		registerCollectionUpdateDepth(classPredicate(clazz), depth);
    }

    /**
     * Register update depth for a collection class
     * @param predicate class predicate
     * @param depth update depth
     */
	public void registerCollectionUpdateDepth(ReflectClassPredicate predicate, int depth) {
        _collectionUpdateDepths.add(new CollectionUpdateDepthEntry(predicate, depth));
	}
    
	/**
	 * Register a class
	 * @param clazz class
	 */
    public void register(GenericClass clazz) {
    	String name = clazz.getName();
    	if(_repository.lookupByName(name) == null){
    		_repository.register(clazz);
    	}
    }
    
    /**
     * Returns an array of classes known to the reflector
     * @return an array of classes known to the reflector
     */
	public ReflectClass[] knownClasses() {
        Collection4 classes = new Collection4();
		collectKnownClasses(classes);
		return (ReflectClass[])classes.toArray(new ReflectClass[classes.size()]);
	}

	private void collectKnownClasses(Collection4 classes) {
		Iterator4 i = _repository.classes();
		while(i.moveNext()){
            GenericClass clazz = (GenericClass)i.current();
            if(! _stream._handlers.ICLASS_INTERNAL.isAssignableFrom(clazz)){
                ClassMetadata clazzMeta = _stream.classMetadataForReflectClass(clazz);
				if(clazzMeta == null || !clazzMeta.isSecondClass()){
					if(! clazz.isArray()){
						classes.add(clazz);
					}
                }
            }
		}
	}
	
	/**
	 * Registers primitive class
	 * @param id class id
	 * @param name class name
	 * @param converter class converter
	 */
	public void registerPrimitiveClass(int id, String name, GenericConverter converter) {
        GenericClass existing = (GenericClass)_repository.lookupByID(id);
		if (existing != null) {
			if (null != converter) {
				existing.setSecondClass();
			} else {
				existing.setConverter(null);
			}
			return;
		}
		ReflectClass clazz = _delegate.forName(name);
		
		GenericClass claxx = null;
		if(clazz != null) {
	        claxx = ensureDelegate(clazz);
		}else {
	        claxx = new GenericClass(this, null, name, null);
	        register(claxx);
		    claxx.initFields(new GenericField[] {new GenericField(null, null, true)});
		    claxx.setConverter(converter);
		}
	    claxx.setSecondClass();
	    claxx.setPrimitive();
	    _repository.register(id,claxx);
	}

	/**
	 * method stub: generic reflector does not have a parent
	 */
    public void setParent(Reflector reflector) {
        // do nothing, the generic reflector does not have a parant
    }
    
	public void configuration(ReflectorConfiguration config) {
		if(_delegate != null) {
			_delegate.configuration(config);
		}
	}
	
}
