/*
 *  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.trinidadinternal.skin;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;

import org.apache.myfaces.trinidad.context.LocaleContext;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.skin.Icon;
import org.apache.myfaces.trinidad.skin.Skin;
import org.apache.myfaces.trinidad.skin.SkinAddition;
import org.apache.myfaces.trinidadinternal.renderkit.core.CoreRenderingContext;
import org.apache.myfaces.trinidadinternal.skin.icon.NullIcon;
import org.apache.myfaces.trinidadinternal.skin.icon.ReferenceIcon;
import org.apache.myfaces.trinidadinternal.style.StyleContext;
import org.apache.myfaces.trinidadinternal.style.StyleProvider;
import org.apache.myfaces.trinidadinternal.style.xml.parse.StyleSheetDocument;

/**
 * This is a Skin decorator which is used to store request-specific
 * skin state.  For example, the set of icons to use varies from
 * request to request, eg. based on the browser/platform.  We cannot
 * store such request-specific state on our shared Skin instances.
 * Instead, SkinFactoryImpl wraps shared Skin instances in RequestSkinWrappers
 * so that request-specific state can be stored locally in the wrapper.
 *
 * Currently, the request-specific state for a skin is the icon map and the skin property map.
 * They are retrieved from the StyleProvider one time per request and stored here.
 * @see org.apache.myfaces.trinidadinternal.style.cache.FileSystemStyleCache
 */
public class RequestSkinWrapper extends Skin implements DocumentProviderSkin
{
  // An alternate implementation strategy would be to enhance SkinImpl.getIcon()
  // to serve up the request/context-specific Icons directly, rather than do so
  // via a wrapper Skin class.  There are two issues with the SkinImpl-based
  // solution:
  //
  // 1. SkinImpl.getIcon() needs access to the StyleContext in order to retrieve
  // the request/context-specific Icon map.  However, Skin.getIcon() does not
  // get take a StyleContext.  We could change the Skin.getIcon() API to take
  // a StyleContext, but since this is a public API we would prefer a solution
  // which does not involve an API change.  Alternatively, SkinImpl.getIcon()
  // could get at the StyleContext by retrieving the RenderingContext, casting
  // to CoreRenderingContext, and calling CoreRenderingContext.getStyleContext().
  // However, that adds overhead to the SkinImpl.getIcon() implementation
  // (mainly just a ThreadLocal look up, which isn't the end of the world,
  // but nonetheless would prefer to avoid if possible).
  //
  // 2. Calls to registerIcon() need to behave differently depending on whether
  // the Icon is being registered at Skin initialization time, or at
  // request-time.
  //
  // At Skin initialization time, we simply want to register the Icon on the
  // Skin instance itself, since such Icons are global to the Skin.  However,
  // we also use registerIcon() to register request-specific icon - for example,
  // see CoreRenderingContext.getIcon()'s handling of rtl icons.  We cannot
  // register these request-specific Icons on the Skin as the Skin is reused
  // across requests.  Instead we want to register request-specific Icons on
  // the request-specific Icon map.
  //
  // In order to deal with #2, we could have SkinImpl behave differently
  // depending on whether it is at initialization time or request time.  However,
  // this is awkward and more cleanly solved via the request-specific wrapper
  // approach.

  public RequestSkinWrapper(Skin wrappedSkin)
  {
    _skin = wrappedSkin;
  }

  /**
   * Returns the Skin that is wrapped by this request-specific
   * wrapper skin.
   */
  public Skin getWrappedSkin()
  {
    return _skin;
  }

  @Override
  public String getId()
  {
    return _skin.getId();
  }

  @Override
  public String getFamily()
  {
    return _skin.getFamily();
  }

  @Override
  public String getRenderKitId()
  {
    return _skin.getRenderKitId();
  }

  @Override
  public String getStyleSheetDocumentId(RenderingContext arc)
  {
    return _skin.getStyleSheetDocumentId(arc);
  }

  @Override
  public Map<String, String> getStyleClassMap(RenderingContext arc)
  {
    return _skin.getStyleClassMap(arc);
  }

  @Override
  public String getStyleSheetName()
  {
    return _skin.getStyleSheetName();
  }

  @Override
  public String getTranslatedString(
    LocaleContext lContext,
    String        key
    ) throws MissingResourceException
  {
    return _skin.getTranslatedString(lContext, key);
  }

  @Override
  public Object getTranslatedValue(
    LocaleContext lContext,
    String        key
    ) throws MissingResourceException
  {
    return _skin.getTranslatedValue(lContext, key);
  }

  @Override
  public Icon getIcon(String  iconName)
  {
    return this.getIcon(iconName, true);
  }

  @Override
  public Icon getIcon(
    String  iconName,
    boolean resolveIcon)
  {
    // We look for the icon in two places:
    //
    // 1. In the icon map provided by the StyleProvider.
    //    The StyleProvider serves up icons that are
    //    specific to this particular request, including
    //    agent-specific icons.
    // 2. In the wrapped Skin instance.  Any Icons that are
    //    manually registered via a call to registerIcon()
    //    will be stored in the wrapped skin.

    // Note: no synchronization needed since we are in a
    // a request-specific object.
    Map<String, Icon> icons = _getRequestIcons();
    assert(icons != null);

    Icon icon = icons.get(iconName);

    if (icon == null)
    {
      icon = _skin.getIcon(iconName, false);

      // Resolve ReferenceIcons if necessary
      if (resolveIcon)
      {
        if (icon instanceof ReferenceIcon)
          icon = SkinUtils.resolveReferenceIcon(this, (ReferenceIcon)icon);

        // We ended up having to do two lookups for icons
        // which are not available in the StyleProvider
        // icon map.  To avoid having to do two lookups
        // the next time this icon is requested, we promote
        // the icon up from the wrapped skin into the
        // StyleProvider icon map.  Note that we only cache
        // resolved icons in this way - we don't want to
        // pollute our cache with unresolved ReferenceIcons.
        registerIcon(iconName, icon);
      }
    }

    return (icon == _NULL_ICON) ? null : icon;
  }

  @Override
  public void registerIcon(
    String  iconName,
    Icon    icon)
  {
    // registerIcon() is called on a RequestSkinWrapper
    // in two cases:
    //
    // 1. CoreRenderingContext.getIcon() contains special handling
    // for icons when the reading direction is right-to-left.  If a
    // rtl variant of the icon is not available, CoreRenderingContext
    // will look up the non-directional version of the icon and register
    // that under the rtl name to avoid repeated lookups.
    //
    // 2. RequestSkinWrapper.getIcon() calls registerIcon() to register
    // icons which were not found in the StyleProvider's icon map.  This
    // also is done as a performance optimization - to repeatedly looking
    // up icons first in the StyleProvider icon map and then in the
    // wrapped skin.
    //
    // In both of these cases storing the missing icon in the StyleProvider
    // icon map is safe/appropriate, since lookups with the same set of
    // StyleContext properties would return the same results.  By storing
    // the result in the StyleProvider icon map, we short-circuit, avoiding
    // redundant work.

    Map<String, Icon> icons = _getRequestIcons();
    if (icons != _NULL_ICONS)
      icons.put(iconName, (icon == null) ? _NULL_ICON : icon);
  }

  @Override
  public Object getProperty(Object key)
  {
    // We look for the skin properties in two places:
    //
    // 1. In the skin property map provided by the StyleProvider (see _getRequestSkinProperties).
    //    The StyleProvider serves up skin properties that are
    //    specific to this particular request, including
    //    agent-specific skin properties.
    // 2. In the wrapped Skin instance.  Any skin properties that are
    //    manually registered via a call to setProperty(Object, Object)
    //    will be stored in the wrapped skin.

    // Note: no synchronization needed since we are in a
    // a request-specific object.
    Map<Object, Object> properties = _getRequestSkinProperties();
    assert(properties != null);
    Object propertyValue = properties.get(key);

    if (propertyValue == null)
    {
      propertyValue = _skin.getProperty(key);
    }

    return propertyValue;
  }

  @Override
  public void setProperty(
    Object key,
    Object value)
  {
    Map<Object, Object> properties = _getRequestSkinProperties();
    if (properties != _NULL_PROPERTIES)
      properties.put(key, value);
  }




  /**
  * @deprecated Use addSkinAddition instead
  * */
  @Override
  public void registerStyleSheet(
    String styleSheetName
    )
  {
    _skin.registerStyleSheet(styleSheetName);
  }

  @Override
  public void addSkinAddition (
    SkinAddition skinAddition
    )
  {
    _skin.addSkinAddition(skinAddition);
  }

  @Override
  public List<SkinAddition> getSkinAdditions ()
  {
    return _skin.getSkinAdditions();
  }

  /**
   * Implementation of DocumentProviderSkin.getStyleSheetDocument().
   */
  public StyleSheetDocument getStyleSheetDocument(StyleContext styleContext)
  {
    // If getStyleSheetDocument() is being called on us, this implies
    // that the wrapped Skin must also be a DocumentProviderSkin.
    assert(_skin instanceof DocumentProviderSkin);

    return ((DocumentProviderSkin)_skin).getStyleSheetDocument(styleContext);
  }

  // Returns request-specific map of icon names to Icons
  private Map<String, Icon> _getRequestIcons()
  {
    if (_icons == null)
    {
      // We get to the request-specific icons via the
      // StyleProvider.  We need a CoreRenderingContext
      // instance to get at the StyleProvider.  This
      // implementation assumes that the RenderingContext
      // is going to be an instanceof CoreRenderingContext.
      // This is a pretty good bet, since the only
      // RenderingContext provided by Trinidad is
      // CoreRenderingContext, and given the complexity of
      // the CoreRenderingContext implementation, it seems
      // unlikely that anyone would attempt to replace the
     // implementation.
      RenderingContext rc = RenderingContext.getCurrentInstance();
      assert(rc instanceof CoreRenderingContext);

      CoreRenderingContext crc = (CoreRenderingContext)rc;
      StyleContext styleContext = crc.getStyleContext();
      StyleProvider styleProvider = styleContext.getStyleProvider();
      _icons = styleProvider.getIcons(styleContext);

      // Under normal circumstances, the StyleProvider will return
      // a non-null, modifiable map.  If the skin/style subsystem
      // has failed to initialize, however, the map may be null.
      // Substitute an empty map so we don't need to check for null
      // later.
      if (_icons == null)
      {
        if (_LOG.isWarning())
          _LOG.warning("_icons is an emptyMap because the skin/style " +
            "subsystem has failed to initialize.");
        _icons = _NULL_ICONS;
      }
    }

    return _icons;
  }

  // Returns request-specific map of skin property key/values
  private Map<Object, Object> _getRequestSkinProperties()
  {
    if (_properties == null)
    {
      // We get to the request-specific properties via the
      // StyleProvider.  We need a CoreRenderingContext
      // instance to get at the StyleProvider.  This
      // implementation assumes that the RenderingContext
      // is going to be an instanceof CoreRenderingContext.
      // This is a pretty good bet, since the only
      // RenderingContext provided by Trinidad is
      // CoreRenderingContext, and given the complexity of
      // the CoreRenderingContext implementation, it seems
      // unlikely that anyone would attempt to replace the
     // implementation.
      RenderingContext rc = RenderingContext.getCurrentInstance();
      assert(rc instanceof CoreRenderingContext);

      CoreRenderingContext crc = (CoreRenderingContext)rc;
      StyleContext styleContext = crc.getStyleContext();
      StyleProvider styleProvider = styleContext.getStyleProvider();
      // skin properties are stored in an Map<Object, Object>
      _properties = styleProvider.getSkinProperties(styleContext);

      // Under normal circumstances, the StyleProvider will return
      // a non-null, modifiable map.  If the skin/style subsystem
      // has failed to initialize, however, the map may be null.
      // Substitute an empty map so we don't need to check for null
      // later.
      if (_properties == null)
      {
        if (_LOG.isWarning())
          _LOG.warning("_properties is an emptyMap because the skin/style " +
            "subsystem has failed to initialize.");
        _properties = _NULL_PROPERTIES;
      }
    }

    return _properties;
  }

  // The wrapped skin
  private final Skin _skin;

  // The icon map specific to this request as served
  // up by the StyleProvider.
  private Map<String, Icon> _icons;

  // The skin properties map specific to this request as served
  // up by the StyleProvider.
  private Map<Object, Object> _properties;

  // Marker used to cache nulls.
  private static final Icon _NULL_ICON = new NullIcon();

  // Empty map used when StyleProvider.getIcons() fails;
  private static final Map<String, Icon> _NULL_ICONS = Collections.emptyMap();

  // Empty map used when StyleProvider.getSkinProperties() fails;
  private static final Map<Object, Object> _NULL_PROPERTIES = Collections.emptyMap();
  static final private TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(RequestSkinWrapper.class);

}
