/***
 * Excerpted from "Domain-Driven Design Using Naked Objects",
 * published by The Pragmatic Bookshelf.
 * Copyrights apply to this code. It may not be used to create training material, 
 * courses, books, articles, and the like. Contact us if you are in doubt.
 * We make no guarantees that this code is fit for any purpose. 
 * Visit http://www.pragmaticprogrammer.com/titles/dhnako for more book information.
***/
package com.pragprog.dhnako.carserv.wicket.persistence;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.nakedobjects.metamodel.adapter.NakedObject;
import org.nakedobjects.metamodel.commons.exceptions.UnknownTypeException;
import org.nakedobjects.metamodel.facets.collections.modify.CollectionFacet;
import org.nakedobjects.metamodel.facets.object.callbacks.PersistedCallbackFacet;
import org.nakedobjects.metamodel.facets.object.callbacks.PersistingCallbackFacet;
import org.nakedobjects.metamodel.spec.NakedObjectSpecification;
import org.nakedobjects.metamodel.spec.feature.NakedObjectAssociation;
import org.nakedobjects.metamodel.specloader.SpecificationLoader;
import org.nakedobjects.metamodel.util.CallbackUtils;
import org.nakedobjects.metamodel.util.CollectionFacetUtils;
import org.nakedobjects.plugins.headless.embedded.internal.PersistenceState;
import org.nakedobjects.plugins.headless.embedded.internal.StandaloneAdapter;
import org.nakedobjects.runtime.transaction.ObjectPersistenceException;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;

public class ObjectStore {

	private Set<Object> objects = new HashSet<Object>();
	private SpecificationLoader specificationLoader;
	
	public void persist(final Object object) {
		NakedObjectSpecification spec = getSpecificationLoader().loadSpecification(object.getClass());
		StandaloneAdapter adapter = new StandaloneAdapter(spec, object, PersistenceState.TRANSIENT);

		persist(adapter, objects);
	}
	public void remove(final Object object) {
		objects.remove(object);
	}
	public boolean isPersistent(final Object object) {
		return objects.contains(object);
	}
	public <T> Iterable<T> instances(Class<T> type) {
		return Iterables.filter(objects, type);
	}
	public <T> Iterable<T> find(Class<T> type, Predicate<? super T> predicate) {
		return Iterables.filter(instances(type), predicate);
	}
	public <T> T findFirst(Class<T> type, Predicate<? super T> predicate) {
		Iterator<T> iterator = Iterables.filter(instances(type), predicate).iterator();
		return iterator.hasNext()?iterator.next():null;
	}

	public int getCount() {
		return objects.size();
	}
	
    public void makePersistent(final NakedObject adapter, final Set<Object> persisted) {
        if (adapter.getSpecification().isCollection()) {
            final CollectionFacet facet = CollectionFacetUtils.getCollectionFacetFromSpec(adapter);
            for(NakedObject eachAdapter: facet.iterable(adapter)) {
            	System.out.println(eachAdapter.getObject());
                persist(eachAdapter, persisted);
            }
        } else {
            persist(adapter, persisted);
        }
    }

    protected void persist(final NakedObject adapter, final Set<Object> persisted) {
		Object object = adapter.getObject();
		if (persisted.contains(object)) {
            return;
        }
		persisted.add(object);
        
        final NakedObjectAssociation[] fields = adapter.getSpecification().getAssociations();
        if (!adapter.getSpecification().isEncodeable() && fields.length > 0) {
            CallbackUtils.callCallback(adapter, PersistingCallbackFacet.class);

            for (int i = 0; i < fields.length; i++) {
                final NakedObjectAssociation field = fields[i];
                if (field.isDerived()) {
                    continue;
                }
                if (field.isOneToManyAssociation()) {
                    final NakedObject collection = field.get(adapter);
                    if (collection == null) {
                        throw new ObjectPersistenceException(
                                "Collection " + field.getName() + 
                                " does not exist in " + adapter.getSpecification().getFullName());
                    }
                    makePersistent(collection, persisted);
                } else {
                    final NakedObject fieldValue = field.get(adapter);
                    if (fieldValue == null) {
                        continue;
                    }
                    if (!(fieldValue instanceof NakedObject)) {
                        throw new UnknownTypeException(
                                fieldValue + " is not a NakedObject");
                    }
                    persist(fieldValue, persisted);
                }
            }
            CallbackUtils.callCallback(adapter, PersistedCallbackFacet.class);
        }
    }


	///////////////////////////////////////////////////////////
	// Dependencies
	///////////////////////////////////////////////////////////

	public SpecificationLoader getSpecificationLoader() {
		return specificationLoader;
	}
	public void setSpecificationLoader(SpecificationLoader specificationLoader) {
		this.specificationLoader = specificationLoader;
	}

}
