package org.springframework.webflow.expression.el;

import java.beans.FeatureDescriptor;
import java.util.Iterator;
import java.util.Locale;

import jakarta.el.ELContext;
import jakarta.el.ELResolver;
import jakarta.el.PropertyNotWritableException;

import org.springframework.context.MessageSource;
import org.springframework.util.StringUtils;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;

/**
 * Custom EL resolver that resolves the messages from the active flow's {@link MessageSource}. The message source is
 * indexed under the key <code>resourceBundle</code>. To access a message, specify its key e.g.
 * <code>resourceBundle.myMessage</code>.
 * 
 * @author Jeremy Grelle
 */
public class FlowResourceELResolver extends ELResolver {

	static final String RESOURCE_BUNDLE_KEY = "resourceBundle";

	private RequestContext requestContext;

	public FlowResourceELResolver() {
	}

	public FlowResourceELResolver(RequestContext requestContext) {
		this.requestContext = requestContext;
	}

	public Class<?> getCommonPropertyType(ELContext context, Object base) {
		if (base == null) {
			return MessageSource.class;
		} else if (base instanceof MessageSource) {
			return String.class;
		} else {
			return null;
		}
	}

	public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
		return null;
	}

	public Class<?> getType(ELContext context, Object base, Object property) {
		RequestContext requestContext = getRequestContext();
		if (requestContext == null) {
			return null;
		}
		if (base == null && RESOURCE_BUNDLE_KEY.equals(property)) {
			context.setPropertyResolved(true);
			return MessageSource.class;
		} else if (base instanceof MessageSource) {
			MessageSource messageSource = (MessageSource) base;
			String message = messageSource.getMessage(property.toString(), null, null, getLocale());
			if (StringUtils.hasText(message)) {
				context.setPropertyResolved(true);
				return String.class;
			}
		}
		return null;
	}

	public Object getValue(ELContext context, Object base, Object property) {
		RequestContext requestContext = getRequestContext();
		if (requestContext == null) {
			return null;
		}
		if (base == null && RESOURCE_BUNDLE_KEY.equals(property)) {
			context.setPropertyResolved(true);
			return getMessageSource();
		} else if (base instanceof MessageSource) {
			MessageSource messageSource = (MessageSource) base;
			String message = messageSource.getMessage(property.toString(), null, null, getLocale());
			if (StringUtils.hasText(message)) {
				context.setPropertyResolved(true);
				return message;
			}
		}
		return null;
	}

	public boolean isReadOnly(ELContext context, Object base, Object property) {
		RequestContext requestContext = getRequestContext();
		if (requestContext == null) {
			return false;
		}
		if (base == null && RESOURCE_BUNDLE_KEY.equals(property)) {
			context.setPropertyResolved(true);
			return true;
		} else if (base instanceof MessageSource) {
			context.setPropertyResolved(true);
			return true;
		}
		return false;
	}

	public void setValue(ELContext context, Object base, Object property, Object value) {
		RequestContext requestContext = getRequestContext();
		if (requestContext == null) {
			return;
		}
		if (base == null && RESOURCE_BUNDLE_KEY.equals(property)) {
			throw new PropertyNotWritableException("The '" + RESOURCE_BUNDLE_KEY
					+ "' implicit variable is not writable.");
		} else if (base instanceof MessageSource) {
			throw new PropertyNotWritableException("The flow's MessageSource is not writable.");
		}
	}

	protected Locale getLocale() {
		return getRequestContext().getExternalContext().getLocale();
	}

	protected MessageSource getMessageSource() {
		return getRequestContext().getActiveFlow().getApplicationContext();
	}

	protected RequestContext getRequestContext() {
		return requestContext != null ? requestContext : RequestContextHolder.getRequestContext();
	}

}
