/*
 * Copyright 2013 Samppa Saarela
 *
 * Licensed 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.javersion.object;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
import java.util.Optional;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;

import org.javersion.object.mapping.MappingResolver;
import org.javersion.object.mapping.TypeMapping;
import org.javersion.object.types.ValueType;
import org.javersion.path.PropertyPath;
import org.javersion.path.PropertyPath.SubPath;
import org.javersion.path.Schema;
import org.javersion.reflect.TypeDescriptor;

import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;

@NotThreadSafe
public final class DescribeContext {

    private final Map<TypeContext, Schema.Builder<ValueType>> schemaMappings = Maps.newHashMap();

    private final TypeMappings typeMappings;

    private final Deque<QueueItem<SubPath, TypeContext>> queue = new ArrayDeque<>();


    private Schema.Builder<ValueType> schemaRoot;

    public DescribeContext(TypeMappings typeMappings) {
        this.typeMappings = typeMappings;
    }

    public Schema describeSchema(Class<?> rootClass) {
        return describeSchema(typeMappings.getTypeDescriptor(rootClass));
    }

    public Schema describeSchema(TypeToken rootType) {
        return describeSchema(typeMappings.getTypeDescriptor(rootType));
    }

    public Schema describeSchema(TypeDescriptor rootType) {
        schemaRoot = new Schema.Builder<>();
        TypeContext typeContext = new TypeContext(rootType);

        schemaRoot.setValue(createValueType(PropertyPath.ROOT, typeContext));

        processMappings();

        return schemaRoot.build();
    }

    public void describeAsync(SubPath path, TypeContext typeContext) {
        queue.add(new QueueItem<>(path, typeContext));
    }

    public ValueType describeNow(@Nullable PropertyPath path, @Nonnull  TypeContext typeContext) {
        return registerMapping(path, typeContext);
    }

    public ValueType getValueType(PropertyPath path) {
        return schemaRoot.get(path).getValue();
    }

    public void processMappings() {
        QueueItem<SubPath, TypeContext> currentItem;
        while ((currentItem = queue.poll()) != null) {
            registerMapping(currentItem.key, currentItem.value);
        }
    }

    private ValueType registerMapping(PropertyPath path, TypeContext typeContext) {
        if (path == null) {
            return createValueType(null, typeContext);
        }
        Schema.Builder<ValueType> schema = schemaMappings.get(typeContext);
        if (schema == null) {
            schema = schemaRoot.getOrCreate(path);
            if (schema.getValue() == null) {
                ValueType valueType = createValueType(path, typeContext);
                schema.setValue(valueType);

                if (!valueType.isReference()) {
                    schemaMappings.put(typeContext, schema);
                }
            }
        } else {
            schemaRoot.connect((SubPath) path, schema);
        }
        return schema.getValue();
    }

    private synchronized ValueType createValueType(PropertyPath path, TypeContext typeContext) {
        for (TypeMapping typeMapping : typeMappings.getTypeMappings()) {
            Optional<ValueType> valueType = typeMapping.describe(path, typeContext, this);
            if (valueType.isPresent()) {
                return valueType.get();
            }
        }
        throw new IllegalArgumentException("ValueType not found for " + typeContext);
    }

    public MappingResolver getMappingResolver() {
        return typeMappings.getMappingResolver();
    }
}

@@ -1690,32 +1690,43 @@
    public Schema
+%3CValueType%3E
  describeSchema(
@@ -1743,24 +1743,24 @@
 ootClass) %7B%0A
-
         retu
@@ -1837,32 +1837,43 @@
    public Schema
+%3CValueType%3E
  describeSchema(
@@ -1966,32 +1966,32 @@
 tType));%0A    %7D%0A%0A
-
     public Schem
@@ -1991,16 +1991,27 @@
 c Schema
+%3CValueType%3E
  describ

Fix generics