/*
 * 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.wicket.request.resource;

import static org.apache.wicket.util.resource.ResourceUtils.MIN_POSTFIX_DEFAULT_AS_EXTENSION;

import java.util.Locale;
import java.util.concurrent.ConcurrentMap;

import org.apache.wicket.Application;
import org.apache.wicket.Session;
import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.resource.ResourceUtil;
import org.apache.wicket.util.lang.Generics;
import org.apache.wicket.util.lang.Packages;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This is a ResourceReference that knows how to find and serve resources located in the Java
 * package (i.e. next to the class files).
 * 
 * @author Tobias Soloschenko
 */
public class PackageResourceReference extends ResourceReference
{
	private static final long serialVersionUID = 1L;

	private static final Logger log = LoggerFactory.getLogger(PackageResourceReference.class);

	private static final String CSS_EXTENSION = "css";
	private static final String JAVASCRIPT_EXTENSION = "js";

	private transient ConcurrentMap<UrlAttributes, UrlAttributes> urlAttributesCacheMap;

	/**
	 * Reads the resource buffered - the content is copied into memory
	 */
	private boolean readBuffered = true;

	/**
	 * Construct.
	 * 
	 * @param key
	 */
	public PackageResourceReference(final ResourceReference.Key key)
	{
		super(key);
	}

	/**
	 * Construct.
	 * 
	 * @param scope
	 * @param name
	 * @param locale
	 * @param style
	 * @param variation
	 */
	public PackageResourceReference(final Class<?> scope, final String name, final Locale locale,
		final String style, String variation)
	{
		super(scope, name, locale, style, variation);
	}

	/**
	 * Construct.
	 * 
	 * @param scope
	 * @param name
	 */
	public PackageResourceReference(final Class<?> scope, final String name)
	{
		super(scope, name);
	}

	/**
	 * Construct.
	 * 
	 * @param name
	 */
	public PackageResourceReference(final String name)
	{
		super(name);
	}

	/**
	 * @see org.apache.wicket.request.resource.ResourceReference#getResource()
	 */
	@Override
	public PackageResource getResource()
	{
		final String extension = getExtension();

		final PackageResource resource;

		RequestCycle requestCycle = RequestCycle.get();
		UrlAttributes urlAttributes = null;
		if (requestCycle != null)
		{
			//resource attributes (locale, style, variation) might be encoded in the URL
			final Url url = requestCycle.getRequest().getUrl();
			urlAttributes = ResourceUtil.decodeResourceReferenceAttributes(url);
		}

		final String currentVariation = getCurrentVariation(urlAttributes);
		final String currentStyle = getCurrentStyle(urlAttributes);
		final Locale currentLocale = getCurrentLocale(urlAttributes);
		final Class<?> scope = getScope();
		final String name = getName();

		if (CSS_EXTENSION.equals(extension))
		{
			resource = new CssPackageResource(scope, name, currentLocale,
					currentStyle, currentVariation);
		}
		else if (JAVASCRIPT_EXTENSION.equals(extension))
		{
			resource = new JavaScriptPackageResource(scope, name, currentLocale,
					currentStyle, currentVariation);
		}
		else
		{
			resource = new PackageResource(scope, name, currentLocale,
					currentStyle, currentVariation);
		}
		resource.readBuffered(readBuffered);

		removeCompressFlagIfUnnecessary(resource);

		return resource;
	}

	/**
	 * Method allowing to remove the compress flag if the resource has been detected as a minified
	 * one (i.e. ending with .min.EXT) This method is to be called by subclasses overriding
	 * <code>getResource</code> if they want to rely on default minification detection handling
	 *
	 * see WICKET-5250 for further explanation
	 * 
	 * @param resource
	 *            resource to check
	 */
	protected final void removeCompressFlagIfUnnecessary(final PackageResource resource)
	{
		String minifiedName = getName();
		if (minifiedName != null && minifiedName.contains(MIN_POSTFIX_DEFAULT_AS_EXTENSION))
		{
			resource.setCompress(false);
		}
	}

	private ResourceReference.UrlAttributes getUrlAttributes(Locale locale, String style,
		String variation)
	{
		IResourceStreamLocator locator = Application.get()
			.getResourceSettings()
			.getResourceStreamLocator();

		String absolutePath = Packages.absolutePath(getScope(), getName());

		IResourceStream stream = locator.locate(getScope(), absolutePath, style, variation, locale,
			null, false);

		if (stream == null)
			return new ResourceReference.UrlAttributes(null, null, null);

		return new ResourceReference.UrlAttributes(stream.getLocale(), stream.getStyle(),
			stream.getVariation());
	}

	private Locale getCurrentLocale(UrlAttributes attributes)
	{
		Locale currentLocale = getCurrentLocale();

		return currentLocale != null
				? currentLocale
				: attributes != null
					? attributes.getLocale()
					: null;
	}

	private Locale getCurrentLocale()
	{
		final Locale locale = getLocale();

		if (locale != null)
		{
			return locale;
		}

		if (Session.exists())
		{
			return Session.get().getLocale();
		}

		return locale;
	}

	private String getCurrentStyle(UrlAttributes attributes)
	{
		String currentStyle = getCurrentStyle();

		return currentStyle != null
				? currentStyle
				: attributes != null
					? attributes.getStyle()
					: null;
	}
	
	private String getCurrentStyle()
	{
		final String style = getStyle();

		if (style != null)
		{
			return style;
		}

		if (Session.exists())
		{
			return Session.get().getStyle();
		}

		return style;
	}
	
	private String getCurrentVariation(UrlAttributes attributes)
	{
		final String variation = getVariation();

		return variation != null
				? variation
				: attributes != null
					? attributes.getVariation()
					: null;
	}

	/**
	 * @return How the minified file should be named.
	 */
	protected String getMinifiedName()
	{
		String name = super.getName();
		return ResourceUtils.getMinifiedName(name, ResourceUtils.MIN_POSTFIX_DEFAULT);
	}

	@Override
	public ResourceReference.UrlAttributes getUrlAttributes()
	{
		Locale locale = getCurrentLocale();
		String style = getCurrentStyle();
		String variation = getVariation();

		ResourceReference.UrlAttributes key = new ResourceReference.UrlAttributes(locale, style,
			variation);

		if (urlAttributesCacheMap == null)
		{
			urlAttributesCacheMap = Generics.newConcurrentHashMap();
		}
		ResourceReference.UrlAttributes value = urlAttributesCacheMap.get(key);
		if (value == null)
		{
			value = getUrlAttributes(locale, style, variation);
			UrlAttributes tmpValue = urlAttributesCacheMap.putIfAbsent(key, value);
			if (tmpValue != null)
			{
				value = tmpValue;
			}
		}

		return value;
	}

	/**
	 * If the package resource should be read buffered.<br>
	 * <br>
	 * WARNING - if the stream is not read buffered compressors will not work, because they require the
	 * whole content to be read into memory.<br>
	 * ({@link org.apache.wicket.javascript.IJavaScriptCompressor}, <br>
	 * {@link org.apache.wicket.css.ICssCompressor}, <br>
	 * {@link org.apache.wicket.resource.IScopeAwareTextResourceProcessor})
	 * 
	 * @param readBuffered
	 *            if the package resource should be read buffered
	 * @return the current package resource
	 */
	public PackageResourceReference readBuffered(boolean readBuffered)
	{
		this.readBuffered = readBuffered;
		return this;
	}
}
