/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.cxf.aegis.type.encoded;

import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.cxf.aegis.Context;

/**
 * MarshalRegistry maintains tracks which object instances have been marshaled
 * and which objects still need to be marshaled.
 * <p/>
 * Note: only the StructType register objects with this class.
 */
public class MarshalRegistry implements Iterable<Object> {
    /**
     * All objects instances with ids.  It is CRITICAL that is be an IdentityHashMap to assure we don't
     * exclude instances that are equivilant but different instances.
     */
    private final Map<Object, String> instances = new IdentityHashMap<Object, String>();

    /**
     * The objects not yet marshaled.  The is maintained as a map for debugging purposes. It is IMPORTANT
     * that this be a LinkedHashMap so we write the objects in the order they were discovered in the object
     * graphs (and writes them in numeric order).
     */
    private final Map<String, Object> notMarshalled = new LinkedHashMap<String, Object>();

    /**
     * The next id.
     */
    private int nextId;

    /**
     * Get the MarshalRegistry stored in the context, and if necessary create a new one.
     *
     * @param context the unmarshal context
     * @return the SoapRefRegistry; never null
     */
    public static MarshalRegistry get(Context context) {
        MarshalRegistry marshalRegistry = context.getProperty(MarshalRegistry.class);
        if (marshalRegistry == null) {
            marshalRegistry = new MarshalRegistry();
            context.setProperty(marshalRegistry);
        }
        return marshalRegistry;
    }


    public String getInstanceId(Object instance) {
        String id = instances.get(instance);
        if (id == null) {
            id = "" + nextId++;
            instances.put(instance, id);
            notMarshalled.put(id, instance);
        }
        return id;
    }

    /**
     * Returns an iterator over the object instances that have not been marshalled yet.  As each instance in
     * this iterator is written, it may contain references to additional objects that have not been written.
     * Those references objects will be added to the end of this iterator, so the "list" that is being
     * iterated over grows as the iteration preceeds.
     * <p/>
     * When an instance is returned from this iterator it is marked as marshalled.
     *
     * @return an iterator over the objects to be marshalled.
     */
    public Iterator<Object> iterator() {
        return new Iterator<Object>() {
            public boolean hasNext() {
                return !notMarshalled.isEmpty();
            }

            public Object next() {
                // remove the first entry in the notMarshalled map
                Iterator<Object> iterator = notMarshalled.values().iterator();
                Object instance = iterator.next();
                iterator.remove();

                // return the instance
                return instance;
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
}