/*
 * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com
 *
 * 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/LICENSE2.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.sebastian_daschner.jaxrs_analyzer.model.methods;

import com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Arrays.asList;

/**
 * The method type signature with which a method can be identified.
 *
 * @author Sebastian Daschner
 */
public class MethodIdentifier {

    /**
     * The containing class as JVM class name, e.g. {@code java/lang/String}.
     */
    private final String containingClass;
    private final String methodName;

    /**
     * The return type as JVM type descriptor, e.g. {@code Ljava/lang/String;}.
     */
    private final String returnType;
    private final boolean staticMethod;
    private final List<String> parameters;

    private MethodIdentifier(final String containingClass, final String methodName, final List<String> parameters, final String returnType, final boolean staticMethod) {
        Objects.requireNonNull(containingClass);
        Objects.requireNonNull(methodName);
        Objects.requireNonNull(returnType);

        this.containingClass = containingClass;
        this.methodName = methodName;
        this.returnType = returnType;
        this.staticMethod = staticMethod;
        this.parameters = parameters;
    }

    /**
     * Returns the method signature, e.g. {@code (Ljava/lang/String;)V}.
     */
    public String getSignature() {
        final StringBuilder builder = new StringBuilder("(");
        parameters.forEach(builder::append);
        return builder.append(')').append(returnType).toString();
    }

    public String getContainingClass() {
        return containingClass;
    }

    public String getMethodName() {
        return methodName;
    }

    public List<String> getParameters() {
        return parameters;
    }

    public String getReturnType() {
        return returnType;
    }

    public boolean isStaticMethod() {
        return staticMethod;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        MethodIdentifier that = (MethodIdentifier) o;

        if (staticMethod != that.staticMethod) return false;
        if (!containingClass.equals(that.containingClass)) return false;
        if (!methodName.equals(that.methodName)) return false;

        if (parameters.equals(that.parameters))
            return true;

        // fallback if signature matches after type erasure -> everything matches except signature
        if (parameters.size() == that.parameters.size() && returnType.equals(that.returnType)) {
            final List<String> types = parameters.stream().map(JavaUtils::toClassName).collect(Collectors.toList());
            final List<String> thatTypes = that.parameters.stream().map(JavaUtils::toClassName).collect(Collectors.toList());
            return types.equals(thatTypes);
        }

        return false;
    }

    @Override
    public int hashCode() {
        int result = containingClass.hashCode();
        result = 31 * result + methodName.hashCode();
        result = 31 * result + returnType.hashCode();
        result = 31 * result + (staticMethod ? 1 : 0);
        result = 31 * result + parameters.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "MethodIdentifier{" +
                "containingClass='" + containingClass + '\'' +
                ", methodName='" + methodName + '\'' +
                ", returnType='" + returnType + '\'' +
                ", staticMethod=" + staticMethod +
                ", parameters=" + parameters +
                '}';
    }

    /**
     * Creates an identifier of the given parameters.
     *
     * @param containingClass The class name
     * @param methodName      The method name
     * @param signature       The method signature
     * @param staticMethod    If the method is static
     * @return The method identifier
     */
    public static MethodIdentifier of(final String containingClass, final String methodName, final String signature, final boolean staticMethod) {
        final String returnType = JavaUtils.getReturnType(signature);
        final List<String> parameters = JavaUtils.getParameters(signature);
        return new MethodIdentifier(containingClass, methodName, parameters, returnType, staticMethod);
    }

    /**
     * Creates an identifier of a non-static method.
     *
     * @param containingClass The class name
     * @param methodName      The method name
     * @param returnType      The return type
     * @param parameterTypes  The parameter types
     * @return The method identifier
     */
    public static MethodIdentifier ofNonStatic(final String containingClass, final String methodName, final String returnType, final String... parameterTypes) {
        return of(containingClass, methodName, returnType, false, parameterTypes);
    }

    /**
     * Creates an identifier of a static method.
     *
     * @param containingClass The class name
     * @param methodName      The method name
     * @param returnType      The return type
     * @param parameterTypes  The parameter types
     * @return The method identifier
     */
    public static MethodIdentifier ofStatic(final String containingClass, final String methodName, final String returnType, final String... parameterTypes) {
        return of(containingClass, methodName, returnType, true, parameterTypes);
    }

    private static MethodIdentifier of(String containingClass, String methodName, String returnType, final boolean staticMethod, String[] parameterTypes) {
        Objects.requireNonNull(parameterTypes);

        return new MethodIdentifier(containingClass, methodName, asList(parameterTypes), returnType, staticMethod);
    }

}

@@ -3179,47 +3179,8 @@
 sure
- -%3E everything matches except signature
 %0A   
@@ -3235,46 +3235,8 @@
 ze()
- && returnType.equals(that.returnType)
 ) %7B%0A
@@ -3266,17 +3266,23 @@
 String%3E 
-t
+erasedT
 ypes = p
@@ -3389,17 +3389,23 @@
 String%3E 
-t
+erasedT
 hatTypes
@@ -3502,24 +3502,197 @@
         
-return t
+final String erasedReturnType = JavaUtils.toClassName(returnType);%0A            final String erasedThatReturnType = JavaUtils.toClassName(that.returnType);%0A            return erasedT
 ypes.equ
@@ -3695,17 +3695,23 @@
 .equals(
-t
+erasedT
 hatTypes
@@ -3707,24 +3707,73 @@
 edThatTypes)
+ && erasedReturnType.equals(erasedThatReturnType)
 ;%0A        %7D%0A

Allow for type erased return types since JavaDoc returns type erased return types