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

import java.time.Duration;
import java.time.Instant;
import java.util.Locale;
import java.util.Map;
import org.apache.wicket.Application;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.resource.IResource;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.request.resource.ResourceReferenceRegistry;
import org.apache.wicket.request.resource.ResourceStreamResource;
import org.apache.wicket.util.io.IClusterable;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.StringResourceStream;
import org.apache.wicket.util.template.PackageTextTemplate;
import org.apache.wicket.util.template.TextTemplate;

/**
 * A class which adapts a {@link PackageTextTemplate} to a {@link ResourceReference}.
 * 
 * See <a href="https://cwiki.apache.org/confluence/display/WICKET/Dynamically+Generate+a+CSS+Stylesheet">Dynamically generate a CSS stylesheet</a>
 * 
 * @author James Carman
 */
public class TextTemplateResourceReference extends ResourceReference implements IClusterable
{

	private static final long serialVersionUID = 1L;

	private final TextTemplate textTemplate;
	private final IModel<Map<String, Object>> variablesModel;
	private final ResourceStreamResource resource;

	/**
	 * Creates a resource reference to a {@link PackageTextTemplate}.
	 * 
	 * @param scope
	 *            the <code>Class</code> to be used for retrieving the classloader for loading the
	 *            <code>PackagedTextTemplate</code>
	 * @param fileName
	 *            the file name
	 * @param variablesModel
	 *            the template variables as a model
	 */
	public TextTemplateResourceReference(final Class<?> scope, final String fileName,
		IModel<Map<String, Object>> variablesModel)
	{
		this(scope, fileName, PackageTextTemplate.DEFAULT_CONTENT_TYPE,
			PackageTextTemplate.DEFAULT_ENCODING, variablesModel);
	}

	/**
	 * Creates a resource reference to a {@link PackageTextTemplate}.
	 * 
	 * @param scope
	 *            the <code>Class</code> to be used for retrieving the classloader for loading the
	 *            <code>PackagedTextTemplate</code>
	 * @param fileName
	 *            the file name
	 * @param contentType
	 *            the mime type of this resource, such as "<code>image/jpeg</code>" or "
	 *            <code>text/html</code>"
	 * @param variablesModel
	 *            the template variables as a model
	 */
	public TextTemplateResourceReference(final Class<?> scope, final String fileName,
		final String contentType, IModel<Map<String, Object>> variablesModel)
	{
		this(scope, fileName, contentType, PackageTextTemplate.DEFAULT_ENCODING, variablesModel);
	}

	/**
	 * Creates a resource reference to a {@link PackageTextTemplate}.
	 * 
	 * @param scope
	 *            the <code>Class</code> to be used for retrieving the classloader for loading the
	 *            <code>PackagedTextTemplate</code>
	 * @param fileName
	 *            the file name
	 * @param contentType
	 *            the mime type of this resource, such as "<code>image/jpeg</code>" or "
	 *            <code>text/html</code>"
	 * @param encoding
	 *            the file's encoding, for example, "<code>UTF-8</code>"
	 * @param variablesModel
	 *            the template variables as a model
	 */
	public TextTemplateResourceReference(final Class<?> scope, final String fileName,
		final String contentType, final String encoding, IModel<Map<String, Object>> variablesModel)
	{
		this(scope, fileName, contentType, encoding, variablesModel, null, null, null);
	}

	/**
	 * Construct.
	 * 
	 * @param scope
	 *            the <code>Class</code> to be used for retrieving the classloader for loading the
	 *            <code>PackagedTextTemplate</code>
	 * @param fileName
	 *            the file name
	 * @param contentType
	 *            the mime type of this resource, such as "<code>image/jpeg</code>" or "
	 *            <code>text/html</code>"
	 * @param encoding
	 *            the file's encoding, for example, "<code>UTF-8</code>"
	 * @param variablesModel
	 *            the template variables as a model
	 * @param locale
	 *            Preferred locale for the resource
	 * @param style
	 *            Preferred style for the resource
	 * @param variation
	 *            Preferred variation for the resource
	 */
	public TextTemplateResourceReference(final Class<?> scope, final String fileName,
		final String contentType, final String encoding,
		IModel<Map<String, Object>> variablesModel, Locale locale, String style, String variation)
	{
		super(scope, fileName, locale, style, variation);

		textTemplate = new PackageTextTemplate(scope, fileName, contentType, encoding);
		this.variablesModel = variablesModel;

		resource = new ResourceStreamResource(null)
		{
			@Override
			protected IResourceStream getResourceStream(Attributes attributes)
			{
				IModel<Map<String, Object>> variables = TextTemplateResourceReference.this.variablesModel;
				String stringValue = textTemplate.asString(variables.getObject());
				variables.detach(); // We're done with the model so detach it!

				StringResourceStream resourceStream = new StringResourceStream(stringValue,
						textTemplate.getContentType());
				resourceStream.setLastModified(Instant.now());

				return resourceStream;
			}
		};
		resource.setCacheDuration(Duration.ZERO);

		if (Application.exists())
		{
			// TextTemplateResourceReference should not be cached due to its dynamic nature
			// Old entry in the registry would keep wrong 'variablesModel'
			ResourceReferenceRegistry resourceReferenceRegistry = Application.get().getResourceReferenceRegistry();
			resourceReferenceRegistry.unregisterResourceReference(getKey());
			resourceReferenceRegistry.registerResourceReference(this);
		}
	}

	/**
	 * Creates a new resource which returns the interpolated value of the text template.
	 * 
	 * @return a new resource which returns the interpolated value of the text template
	 */
	@Override
	public IResource getResource()
	{
		return resource;
	}
}
