/*
 * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
 *
 * 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 com.querydsl.codegen;

import java.lang.annotation.Annotation;
import java.util.*;

import javax.annotation.Nullable;

import com.google.common.base.Function;
import com.mysema.codegen.StringUtils;
import com.mysema.codegen.model.Constructor;
import com.mysema.codegen.model.Type;
import com.mysema.codegen.model.TypeAdapter;
import com.mysema.codegen.model.TypeCategory;

/**
 * {@code EntityType} represents a model of a query domain type with properties
 *
 * @author tiwe
 */
public class EntityType extends TypeAdapter implements Comparable<EntityType> {

    private final Map<Class<?>,Annotation> annotations = new HashMap<Class<?>,Annotation>();

    private final Set<Constructor> constructors = new HashSet<Constructor>();

    private int escapeSuffix = 1;

    private final Set<Delegate> delegates = new HashSet<Delegate>();

    private final Set<Property> properties = new TreeSet<Property>();

    private final Set<String> propertyNames = new HashSet<String>();

    private final Set<String> escapedPropertyNames = new HashSet<String>();

    private final Set<Supertype> superTypes;

    private final Map<Object, Object> data = new HashMap<Object,Object>();

    private String modifiedSimpleName;

    /**
     * Create a new {@code EntityType} instance for the given type
     *
     * @param type
     */
    public EntityType(Type type) {
        this(type, new LinkedHashSet<Supertype>(), DefaultVariableNameFunction.INSTANCE);
    }

    /**
     * Create a new {@code EntityType} instance for the given type
     *
     * @param type the type to be used
     * @param variableNameFunction the variable name function to be used
     */
    public EntityType(Type type, Function<EntityType, String> variableNameFunction) {
        this(type, new LinkedHashSet<Supertype>(), variableNameFunction);
    }

    /**
     * Create a new {@code EntityType} instance for the given type and superTypes
     *
     * @param type
     * @param superTypes
     */
    public EntityType(Type type, Set<Supertype> superTypes) {
        this(type, superTypes, DefaultVariableNameFunction.INSTANCE);
    }

    /**
     * Create a new {@code EntityType} instance for the given type and superTypes
     *
     * @param type the type to be used
     * @param superTypes the super types to be used
     * @param variableNameFunction the variable name function to be used
     */
    private EntityType(Type type, Set<Supertype> superTypes, Function<EntityType, String> variableNameFunction) {
        super(type);
        this.superTypes = superTypes;
        this.modifiedSimpleName = variableNameFunction.apply(this);
    }

    public void addAnnotation(Annotation annotation) {
        annotations.put(annotation.annotationType(), annotation);
    }

    public void addConstructor(Constructor co) {
        constructors.add(co);
    }

    public void addDelegate(Delegate delegate) {
        delegates.add(delegate);
    }

    public void addProperty(Property field) {
        if (!propertyNames.contains(field.getName())) {
            propertyNames.add(field.getName());
            escapedPropertyNames.add(field.getEscapedName());
            properties.add(validateField(field));
        }
    }

    public void addSupertype(Supertype entityType) {
        superTypes.add(entityType);
    }

    @Override
    public int compareTo(EntityType o) {
        return getType().getSimpleName().compareTo(o.getType().getSimpleName());
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof Type) {
            return getFullName().equals(((Type) o).getFullName());
        } else {
            return false;
        }
    }

    @SuppressWarnings("unchecked")
    public <T extends Annotation> T getAnnotation(Class<T> type) {
        return (T) annotations.get(type);
    }

    public Collection<Annotation> getAnnotations() {
        return annotations.values();
    }

    @Override
    public Type as(TypeCategory category) {
        if (getCategory() == category) {
            return this;
        } else {
            return super.as(category);
        }
    }

    @Override
    public TypeCategory getCategory() {
        if (getType().getCategory() == TypeCategory.ENTITY || !properties.isEmpty()) {
            return TypeCategory.ENTITY;
        } else {
            return TypeCategory.CUSTOM;
        }
    }

    public Set<Constructor> getConstructors() {
        return constructors;
    }

    public Map<Object, Object> getData() {
        return data;
    }

    public Set<Delegate> getDelegates() {
        return delegates;
    }

    public TypeCategory getOriginalCategory() {
        return super.getCategory();
    }

    public Set<Property> getProperties() {
        return properties;
    }

    @Nullable
    public Supertype getSuperType() {
        return superTypes.size() == 1 ? superTypes.iterator().next() : null;
    }

    public Set<Supertype> getSuperTypes() {
        return superTypes;
    }

    /**
     * Use {@link #getModifiedSimpleName()}
     */
    @Deprecated
    public String getUncapSimpleName() {
        return modifiedSimpleName;
    }

    public String getModifiedSimpleName() {
        return modifiedSimpleName;
    }

    @Override
    public int hashCode() {
        return getFullName().hashCode();
    }

    public boolean hasArrays() {
        return hasPropertyWithType(TypeCategory.ARRAY);
    }

    public boolean hasEntityFields() {
        return hasPropertyWithType(TypeCategory.ENTITY);
    }

    public boolean hasInits() {
        for (Property property : properties) {
            if (!property.getInits().isEmpty()) {
                return true;
            }
        }
        return false;
    }

    public boolean hasLists() {
        return hasPropertyWithType(TypeCategory.LIST);
    }

    public boolean hasCollections() {
        return hasPropertyWithType(TypeCategory.COLLECTION);
    }

    public boolean hasSets() {
        return hasPropertyWithType(TypeCategory.SET);
    }

    public boolean hasMaps() {
        return hasPropertyWithType(TypeCategory.MAP);
    }

    private boolean hasPropertyWithType(TypeCategory category) {
        for (Property property : properties) {
            if (property.getType().getCategory() == category) {
                return true;
            }
        }
        return false;
    }

    public void include(Supertype supertype) {
        EntityType entityType = supertype.getEntityType();
        for (Delegate delegate : entityType.getDelegates()) {
            addDelegate(delegate);
        }
        for (Property property : entityType.getProperties()) {
            addProperty(property.createCopy(this));
        }
    }

    private Property validateField(Property field) {
        if (field.getName().equals(modifiedSimpleName) || field.getEscapedName().equals(modifiedSimpleName)) {
            do {
                modifiedSimpleName = StringUtils.uncapitalize(getType().getSimpleName()) + (escapeSuffix++);
            } while (propertyNames.contains(modifiedSimpleName));
        }
        return field;
    }

    public Set<String> getPropertyNames() {
        return propertyNames;
    }

    public Set<String> getEscapedPropertyNames() {
        return escapedPropertyNames;
    }

    public Type getInnerType() {
        return type;
    }

}

@@ -1325,32 +1325,38 @@
 structors = new 
+Linked
 HashSet%3CConstruc

use LinkedHashSet for deterministic order