/*
 * Copyright 2016-2018 Talsma ICT
 *
 * 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 nl.talsmasoftware.umldoclet.javadoc;

import nl.talsmasoftware.umldoclet.uml.TypeName;

import javax.lang.model.element.Element;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.type.*;
import javax.lang.model.util.SimpleTypeVisitor9;
import java.util.EnumSet;

/**
 * The UML type name implemented as {@link TypeVisitor}.
 *
 * @author Sjoerd Talsma
 */
final class TypeNameVisitor extends SimpleTypeVisitor9<TypeName, Void> {
    private static final EnumSet<TypeKind> NO_KNOWN_TYPES = EnumSet.of(
            TypeKind.VOID, TypeKind.NONE, TypeKind.NULL, TypeKind.ERROR, TypeKind.OTHER);

    static final TypeNameVisitor INSTANCE = new TypeNameVisitor();

    private TypeNameVisitor() {
    }

    @Override
    public TypeName visitPrimitive(PrimitiveType primitiveType, Void parameter) {
        // "byte", "char", "short", "int", "long", "float", "double", "boolean"
        final String primitive = primitiveType.getKind().name().toLowerCase();
        return new TypeName(primitive, primitive);
    }

    @Override
    public TypeName visitNoType(NoType noType, Void parameter) {
        // "void", "package", "module", "none"
        final String none = noType.getKind().name().toLowerCase();
        return new TypeName(none, none);
    }

    @Override
    public TypeName visitDeclared(DeclaredType declaredType, Void parameter) {
        final Element el = declaredType.asElement();
        final String simpleName = el.getSimpleName().toString();
        final String qualifiedName = el instanceof QualifiedNameable
                ? ((QualifiedNameable) el).getQualifiedName().toString() : simpleName;
        final TypeName[] generics = declaredType.getTypeArguments().stream()
                .map(generic -> visit(generic, parameter))
                .toArray(TypeName[]::new);
        return new TypeName(simpleName, qualifiedName, generics);
    }

    @Override
    public TypeName visitArray(ArrayType arrayType, Void parameter) {
        return TypeName.Array.of(visit(arrayType.getComponentType(), parameter));
    }

    @Override
    public TypeName visitTypeVariable(TypeVariable typeVariable, Void parameter) {
        TypeMirror upperBound = typeVariable.getUpperBound();
        if (upperBound != null && !NO_KNOWN_TYPES.contains(upperBound.getKind())) try {
            // Fix for #64: Avoid redundant <T extends Object> (which is obviously true for all T's)
            TypeName upperBoundName = visit(upperBound, parameter);
            if (!Object.class.getName().equals(upperBoundName.qualified)) {
                return TypeName.Variable.extendsBound(typeVariable.toString(), upperBoundName);
            }
        } catch (StackOverflowError soe) {
            System.out.println("WARNING: Caught stack overflow error for type " + typeVariable + " with upper bound " + upperBound + "!");
        }
        TypeMirror lowerBound = typeVariable.getLowerBound();
        if (lowerBound != null && !NO_KNOWN_TYPES.contains(lowerBound.getKind())) try {
            return TypeName.Variable.superBound(typeVariable.toString(), visit(lowerBound, parameter));
        } catch (StackOverflowError soe) {
            System.out.println("WARNING: Caught stack overflow error for type " + typeVariable + " with lower bound " + lowerBound + "!");
        }

        return defaultAction(typeVariable, parameter);
    }

    @Override
    public TypeName visitWildcard(WildcardType wildcardType, Void parameter) {
        TypeMirror extendsBound = wildcardType.getExtendsBound();
        if (extendsBound != null) return TypeName.Variable.extendsBound("?", visit(extendsBound, parameter));
        TypeMirror superBound = wildcardType.getSuperBound();
        if (superBound != null) return TypeName.Variable.superBound("?", visit(superBound, parameter));

        return defaultAction(wildcardType, parameter);
    }

    @Override
    protected TypeName defaultAction(TypeMirror tp, Void parameter) {
        // TODO handle unknown type variables better!
        String stringValue = tp.toString();
        return new TypeName(stringValue, stringValue);
    }

    // TODO Figure out how the following should be represented in UML

//    @Override
//    public TypeName visitIntersection(IntersectionType intersectionType, Void parameter) {
////        return intersectionType.getBounds().stream()
////                .map(bound -> visit(bound, parameter))
////                .collect(joining(" & "));
//        return defaultAction(intersectionType, parameter);
//    }

//    @Override
//    public TypeName visitUnion(UnionType unionType, Void parameter) {
////        return unionType.getAlternatives().stream()
////                .map(alternative -> visit(alternative, parameter))
////                .collect(joining(" | "));
//        return defaultAction(unionType, parameter);
//    }

}

@@ -2905,36 +2905,32 @@
 ound.getKind()))
- try
  %7B%0A            /
@@ -3275,190 +3275,8 @@
   %7D%0A
-        %7D catch (StackOverflowError soe) %7B%0A            System.out.println(%22WARNING: Caught stack overflow error for type %22 + typeVariable + %22 with upper bound %22 + upperBound + %22!%22);%0A
     
@@ -3428,12 +3428,8 @@
 ()))
- try
  %7B%0A 
@@ -3535,189 +3535,8 @@
 ));%0A
-        %7D catch (StackOverflowError soe) %7B%0A            System.out.println(%22WARNING: Caught stack overflow error for type %22 + typeVariable + %22 with lower bound %22 + lowerBound + %22!%22);
 %0A   
@@ -4253,96 +4253,56 @@
     
-String stringValue = tp.toString();%0A        return new TypeName(stringValue, stringValue
+return new TypeName(tp.toString(), tp.toString()
 );%0A 

Revert "Debugging issue #75: print warning and swallow StackOverflowError"