/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 io.undertow.websockets.jsr.annotated;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jakarta.websocket.DeploymentException;

import io.undertow.websockets.jsr.JsrWebSocketMessages;

/**
 * A method with bound parameters
 */
final class BoundMethod {

    private final Method method;
    private final List<BoundParameter> parameters = new ArrayList<>();
    private final Set<Class> paramTypes = new HashSet<>();
    private final Class<?> messageType;
    private final boolean decoderRequired;
    private final long maxMessageSize;

    BoundMethod(final Method method, final Class<?> messageType, final boolean decoderRequired, long maxMessageSize, BoundParameter... params) throws DeploymentException {
        this.method = method;
        this.messageType = messageType;
        this.decoderRequired = decoderRequired;
        this.maxMessageSize = maxMessageSize;
        final Set<Integer> allParams = new HashSet<>();
        for (int i = 0; i < method.getParameterCount(); ++i) {
            allParams.add(i);
            paramTypes.add(method.getParameterTypes()[i]);
        }
        for (BoundParameter param : params) {
            parameters.add(param);
            allParams.removeAll(param.positions());
        }
        if (!allParams.isEmpty()) {
            //first check to see if the user has accidentally used the wrong PathParam annotation
            //and if so throw a more informative error message
            boolean wrongAnnotation = false;
            for (int i = 0; i < method.getParameterAnnotations().length; ++i) {
                for (int j = 0; j < method.getParameterAnnotations()[i].length; ++j) {
                    Annotation annotation = method.getParameterAnnotations()[i][j];
                    if (annotation.annotationType().getName().equals("jakarta.ws.rs.PathParam")) {
                        wrongAnnotation = true;
                    }
                }
            }
            if (wrongAnnotation) {
                throw JsrWebSocketMessages.MESSAGES.invalidParametersWithWrongAnnotation(method, allParams);
            } else {
                throw JsrWebSocketMessages.MESSAGES.invalidParameters(method, allParams);
            }
        }
        method.setAccessible(true);
    }

    public Object invoke(final Object instance, final Map<Class<?>, Object> values) throws Exception {
        final Object[] params = new Object[method.getParameterCount()];
        for (BoundParameter param : parameters) {
            param.populate(params, values);
        }
        try {
            return method.invoke(instance, params);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            if(e.getCause() instanceof Exception) {
                throw (Exception)e.getCause();
            } else {
                throw new RuntimeException(e.getCause());
            }
        }
    }

    public boolean hasParameterType(final Class<?> type) {
        return paramTypes.contains(type);
    }

    public Class<?> getMessageType() {
        return messageType;
    }

    public boolean isDecoderRequired() {
        return decoderRequired;
    }

    public long getMaxMessageSize() {
        return maxMessageSize;
    }

    public boolean overrides(Method method) {
        if(!method.getName().equals(this.method.getName())) {
            return false;
        }
        if(!method.getReturnType().isAssignableFrom(this.method.getReturnType())) {
            return false;
        }
        if(method.getParameterCount() != this.method.getParameterCount()) {
            return false;
        }
        if(method.getParameterCount() == 0) {
            return true;
        }
        Class<?>[] otherParameterTypes = this.method.getParameterTypes();
        Class<?>[] parameterTypes = method.getParameterTypes();
        for(int i = 0; i < parameterTypes.length; ++i) {
            if(parameterTypes[i] != otherParameterTypes[i]) {
                return false;
            }
        }
        return true;
    }
}
