/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.apache.deltaspike.core.impl.message;

import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Typed;
import jakarta.inject.Inject;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.apache.deltaspike.core.api.message.LocaleResolver;
import org.apache.deltaspike.core.api.message.Message;
import org.apache.deltaspike.core.api.message.MessageContext;
import org.apache.deltaspike.core.api.message.MessageInterpolator;
import org.apache.deltaspike.core.api.message.MessageResolver;
import org.apache.deltaspike.core.api.message.MessageContextConfig;
import org.apache.deltaspike.core.api.message.MessageTemplate;
import org.apache.deltaspike.core.api.provider.BeanProvider;
import org.apache.deltaspike.core.util.ClassUtils;

/**
 * This Proxy InvocationHandler implements the handling for all our
 * {@link org.apache.deltaspike.core.api.message.MessageBundle}s.
 */
@Dependent
@Typed(MessageBundleInvocationHandler.class)
public class MessageBundleInvocationHandler implements InvocationHandler, Serializable
{
    private static final long serialVersionUID = -8980912335543392357L;

    @Inject
    private MessageContext baseMessageContext = null;

    /**
     * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
     *      java.lang.reflect.Method, java.lang.Object[])
     */
    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable
    {
        if (method.getDeclaringClass().equals(Object.class))
        {
            // this sometimes gets invoked directly by the container
            // there is no perfect solution for those methods,
            // so we try to use the best info we have atm.

            if ("hashCode".equals(method.getName()))
            {
                return proxy.getClass().hashCode();
            }

            if ("toString".equals(method.getName()))
            {
                return proxy.getClass().toString();
            }

            if ("equals".equals(method.getName()))
            {
                return proxy.getClass().equals(args[0].getClass());
            }

            return null;
        }

        final MessageTemplate messageTemplate = method.getAnnotation(MessageTemplate.class);

        String messageTemplateValue;

        if (messageTemplate != null)
        {
            messageTemplateValue = messageTemplate.value();
        }
        else
        {
            messageTemplateValue = "{" + method.getName() + "}";
        }

        MessageContext messageContext = resolveMessageContextFromArguments(args);
        List<Serializable> arguments = resolveMessageArguments(args);

        if (messageContext == null)
        {
            messageContext = baseMessageContext.clone();

            MessageContextConfig messageContextConfig =
                method.getDeclaringClass().getAnnotation(MessageContextConfig.class);

            if (messageContextConfig != null)
            {
                applyMessageContextConfig(messageContext, messageContextConfig);
            }
        }

        String messageBundleName = method.getDeclaringClass().getName();

        Message message =  messageContext
                .messageSource(messageBundleName).message()
                .template(messageTemplateValue)
                .argument(arguments.toArray(new Serializable[arguments.size()]));

        if (String.class.isAssignableFrom(method.getReturnType()))
        {
            return message.toString();
        }

        return message;
    }

    private void applyMessageContextConfig(MessageContext messageContext, MessageContextConfig messageContextConfig)
    {
        if (!MessageResolver.class.equals(messageContextConfig.messageResolver()))
        {
            Class<? extends MessageResolver> messageResolverClass =
                    ClassUtils.tryToLoadClassForName(messageContextConfig.messageResolver().getName());

            messageContext.messageResolver(
                    BeanProvider.getContextualReference(messageResolverClass, Any.Literal.INSTANCE));
        }

        if (!MessageInterpolator.class.equals(messageContextConfig.messageInterpolator()))
        {
            Class<? extends MessageInterpolator> messageInterpolatorClass =
                    ClassUtils.tryToLoadClassForName(messageContextConfig.messageInterpolator().getName());

            messageContext.messageInterpolator(
                    BeanProvider.getContextualReference(messageInterpolatorClass, Any.Literal.INSTANCE));
        }

        if (!LocaleResolver.class.equals(messageContextConfig.localeResolver()))
        {
            Class<? extends LocaleResolver> localeResolverClass =
                    ClassUtils.tryToLoadClassForName(messageContextConfig.localeResolver().getName());

            messageContext.localeResolver(
                    BeanProvider.getContextualReference(localeResolverClass, Any.Literal.INSTANCE));
        }
        
        String[] messageSources = messageContextConfig.messageSource();
        messageContext.messageSource(messageSources);

    }

    private List<Serializable> resolveMessageArguments(Object[] args)
    {
        List<Serializable> arguments = new ArrayList<Serializable>();
        if (args != null && args.length > 0)
        {
            for (int i = 0; i < args.length; i++)
            {
                Object arg = args[i];

                if (i == 0 && arg != null && MessageContext.class.isAssignableFrom(arg.getClass()))
                {
                    continue;
                }

                if (arg instanceof Serializable)
                {
                    arguments.add((Serializable) arg);
                }
                else
                {
                    // for non-serializable objects we perform an immediate toString() instead
                    arguments.add(arg == null ? null : arg.toString());
                }
            }
        }

        return arguments;
    }

    private MessageContext resolveMessageContextFromArguments(Object[] args)
    {
        if (args != null && args.length > 0 &&  args[0] != null &&
            MessageContext.class.isAssignableFrom(args[0].getClass()))
        {
            return (MessageContext) args[0];
        }

        return null;
    }

}
