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

import java.util.regex.Pattern;

import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.http.WebResponse;
import org.apache.wicket.request.resource.AbstractResource;
import org.apache.wicket.request.resource.caching.version.CachingResourceVersion;
import org.apache.wicket.request.resource.caching.version.IResourceVersion;
import org.apache.wicket.util.lang.Args;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * resource caching strategy that adds a version for the 
 * requested resource to the filename.
 * <p/>
 * versioned_filename := [basename][version-prefix][version](.extension)
 * <p/>
 * the <code>version</code> must not contain the <code>version-prefix</code> so
 * please use an unambiguous value for the <code>version-prefix</code>. The default
 * <code>version-prefix</code> is <code>{@value #DEFAULT_VERSION_PREFIX}</code>.
 * <p/> 
 * Since browsers and proxies use the versioned filename of the resource 
 * as a cache key a change to the version will also change the filename and 
 * cause a reliable cache miss. This enables us to set the caching duration
 * of the resource to a maximum and get best network performance.
 * <p/>
 * 
 * @author Peter Ertl
 * 
 * @since 1.5
 */
public class FilenameWithVersionResourceCachingStrategy implements IResourceCachingStrategy
{
	private static final Logger LOG = LoggerFactory.getLogger(FilenameWithVersionResourceCachingStrategy.class);

	private static final String DEFAULT_VERSION_PREFIX = "-ver-";
	
	/** string that marks the beginning the of the version in the decorated filename */
	private final String versionPrefix;

	/** resource version provider */
	private final IResourceVersion resourceVersion;

	/**
	 * create filename caching strategy with given version provider and 
	 * <code>version-prefix = '{@value #DEFAULT_VERSION_PREFIX}'</code>
	 * 
	 * @param resourceVersion
	 *            version provider
	 *            
	 * @see #FilenameWithVersionResourceCachingStrategy(String, org.apache.wicket.request.resource.caching.version.IResourceVersion) 
	 */
	public FilenameWithVersionResourceCachingStrategy(IResourceVersion resourceVersion)
	{
		this(DEFAULT_VERSION_PREFIX, resourceVersion);
	}

	/**
	 * Constructor
	 * 
	 * @param versionPrefix
	 *            string that marks the beginning the of the version in the decorated filename 
	 * @param resourceVersion
	 *            resource version object
	 * 
	 * @see #FilenameWithVersionResourceCachingStrategy(org.apache.wicket.request.resource.caching.version.IResourceVersion) 
	 */
	public FilenameWithVersionResourceCachingStrategy(String versionPrefix,
	                                                  IResourceVersion resourceVersion)
	{
		this.resourceVersion = Args.notNull(resourceVersion, "resourceVersion");
		this.versionPrefix = Args.notEmpty(versionPrefix, "versionPrefix");
	}

	/**
	 * @return string appended to the filename before the version string
	 */
	public final String getVersionPrefix()
	{
		return versionPrefix;
	}

	@Override
	public void decorateUrl(ResourceUrl url, IStaticCacheableResource resource)
	{
		// get version string for requested resource
		final String version = this.resourceVersion.getVersion(resource);

		// ignore resource if no version information is available
		if (version == null)
		{
			return;
		}

		// get undecorated filename
		final String filename = url.getFileName();

		// check if resource name has extension
		final int extensionAt = filename.lastIndexOf('.');

		// create filename with version:
		//
		// filename :=
		// [basename][version-prefix][version](.extension)
		//
		final StringBuilder versionedFilename = new StringBuilder();
		
		// add filename
		if (extensionAt == -1)
		{
			versionedFilename.append(filename);
		}
		else
		{
			versionedFilename.append(filename.substring(0, extensionAt));
		}

		int pos = versionedFilename.indexOf(getVersionPrefix());
		if (pos != -1 && isVersion(versionedFilename.substring(pos + versionPrefix.length())))
		{
			LOG.error("A resource with name '{}' contains the version prefix '{}' so the un-decoration will not work." +
					" Either use a different version prefix or rename this resource.", filename, getVersionPrefix());
		}

		// add version suffix
		versionedFilename.append(versionPrefix);
		
		// add version
		versionedFilename.append(version);

		// add extension if present
		if (extensionAt != -1)
		{
			versionedFilename.append(filename.substring(extensionAt));
		}
		// set versioned filename
		url.setFileName(versionedFilename.toString());
	}

	@Override
	public void undecorateUrl(ResourceUrl url)
	{
		final String filename = url.getFileName();
		
		// check for extension
		int pos = filename.lastIndexOf('.');

		// get name of file without extension (but with version string)
		final String fullname = pos == -1 ? filename : filename.substring(0, pos);
		
		// get extension of file if present
		final String extension = pos == -1 ? null : filename.substring(pos);

		// get position of version string
		pos = fullname.lastIndexOf(versionPrefix);

		// remove version string if it exists
		if (pos != -1 && isVersion(fullname.substring(pos + versionPrefix.length())))
		{
			// get filename before version string
			final String basename = fullname.substring(0, pos);

			// create filename without version string 
			// (required for working resource lookup)
			url.setFileName(extension == null? basename : basename + extension);

			// store the version in the request cycle
			RequestCycle requestCycle = RequestCycle.get();
			if (requestCycle != null)
			{
				int idx = fullname.indexOf(versionPrefix);
				String urlVersion = fullname.substring(idx + versionPrefix.length());
				requestCycle.setMetaData(URL_VERSION, urlVersion);
			}
		}
	}

	private boolean isVersion(String substring)
	{
		Pattern versionPattern = resourceVersion.getVersionPattern();
		return versionPattern == null || versionPattern.matcher(substring).matches();
	}

	/**
	 * set resource caching to maximum and set cache-visibility to 'public'
	 * 
	 * @param response
	 */
	@Override
	public void decorateResponse(AbstractResource.ResourceResponse response, IStaticCacheableResource resource)
	{
		String requestedVersion = RequestCycle.get().getMetaData(URL_VERSION);
		String calculatedVersion = this.resourceVersion.getVersion(resource);
		if (calculatedVersion != null && calculatedVersion.equals(requestedVersion))
		{
			response.setCacheDurationToMaximum();
			response.setCacheScope(WebResponse.CacheScope.PUBLIC);
		}
	}

	@Override
	public void clearCache()
	{
		if (resourceVersion instanceof CachingResourceVersion)
		{
			((CachingResourceVersion) resourceVersion).invalidateAll();
		}
	}
}
