/*
 * 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.myfaces.view.facelets.el;

import java.util.HashMap;
import java.util.Map;

import jakarta.el.ValueExpression;
import jakarta.el.VariableMapper;

import org.apache.myfaces.view.facelets.PageContext;
import org.apache.myfaces.view.facelets.TemplateContext;

/**
 * Default instance of a VariableMapper backed by a Map
 * 
 * @see jakarta.el.VariableMapper
 * @see jakarta.el.ValueExpression
 * @see java.util.Map
 * 
 * @author Jacob Hookom
 * @version $Id$
 */
public final class DefaultVariableMapper extends VariableMapperBase
{
    private Map<String, ValueExpression> _vars;
    private PageContext _pageContext;
    private TemplateContext _templateContext;
    private VariableMapper _delegate;
    public boolean _trackResolveVariables;
    public boolean _variableResolved;

    public DefaultVariableMapper()
    {
        super();
        _trackResolveVariables = false;
        _variableResolved = false;
    }

    public DefaultVariableMapper(VariableMapper delegate)
    {
        super();
        this._delegate = delegate;
    }
    
    /**
     * @see jakarta.el.VariableMapper#resolveVariable(java.lang.String)
     */
    @Override
    public ValueExpression resolveVariable(String name)
    {
        ValueExpression returnValue = null;
        
        if (_vars != null)
        {
            returnValue = _vars.get(name);
        }

        //If the variable is not on the VariableMapper
        if (returnValue == null)
        {
            //Check on page and template context
            if (_pageContext != null && _pageContext.getAttributeCount() > 0)
            {
                if (_pageContext.getAttributes().containsKey(name))
                {
                    returnValue = _pageContext.getAttributes().get(name);
                    if (_trackResolveVariables)
                    {
                        if (returnValue instanceof CacheableValueExpression)
                        {
                            if (returnValue instanceof CacheableValueExpressionWrapper)
                            {
                                returnValue = ((CacheableValueExpressionWrapper)returnValue).getWrapped();
                            }
                        }
                        else
                        {
                            _variableResolved = true;
                        }
                    }
                    return returnValue;
                }
            }
            
            if (_templateContext != null)
            {
                if (!_templateContext.isParameterEmpty() &&_templateContext.containsParameter(name))
                {
                    returnValue = _templateContext.getParameter(name);
                    if (_trackResolveVariables && !(returnValue instanceof CacheableValueExpression))
                    {
                        _variableResolved = true;
                    }
                    return returnValue;
                }
                else if (!_templateContext.isKnownParametersEmpty() && _templateContext.containsKnownParameter(name))
                {
                    // This part is the most important in alwaysRecompile EL cache hack.
                    // The idea is maintain a list of the parameters used in a template,
                    // and if the name to be resolved match one of the list, even if the
                    // param was not set it is necessary to track it as variable resolved.
                    // This will force create a new EL expression each time the view is
                    // built, preserving ui:param and VariableMapper behavior, but allow
                    // cache all expressions that are not related.
                    if (_trackResolveVariables)
                    {
                        _variableResolved = true;
                    }
                }
            }
            
            if (_delegate != null)
            {
                returnValue = _delegate.resolveVariable(name);
            }
        }
        else if (_trackResolveVariables)
        {
            // Is this code in a block that wants to cache 
            // the resulting expression(s) and variable has been resolved?
            _variableResolved = true;
        }
        
        return returnValue;
    }

    /**
     * @see jakarta.el.VariableMapper#setVariable(java.lang.String, jakarta.el.ValueExpression)
     */
    @Override
    public ValueExpression setVariable(String name, ValueExpression expression)
    {
        if (_vars == null)
        {
            _vars = new HashMap<>();
        }
        
        return _vars.put(name, expression);
    }
    
    /**
     * Set the current page context this variable mapper should resolve against.
     * 
     * @param pageContext
     */
    public void setPageContext(PageContext pageContext)
    {
        this._pageContext = pageContext;
    }
    
    /**
     * Set the current template context this variable mapper should resolve against.
     * 
     * @param templateContext
     */
    public void setTemplateContext(TemplateContext templateContext)
    {
        this._templateContext = templateContext;
    }

    @Override
    public boolean isAnyFaceletsVariableResolved()
    {
        if (_trackResolveVariables)
        {
            return _variableResolved;
        }
        else
        {
            //Force expression creation
            return true;
        }
    }

    @Override
    public void beforeConstructELExpression()
    {
        _trackResolveVariables = true;
        _variableResolved = false;
    }

    @Override
    public void afterConstructELExpression()
    {
        _trackResolveVariables = false;
        _variableResolved = false;
    }
}
