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

import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import org.apache.wicket.util.collections.MostRecentlyUsedMap;

/**
 * A map that contains the buffered responses. It has a constraint on the maximum entries that it
 * can contain, and a constraint on the duration of time an entry is considered valid/non-expired
 */
class StoredResponsesMap extends MostRecentlyUsedMap<String, Object>
{
	private static final long serialVersionUID = 1L;

	/**
	 * The actual object that is stored as a value of the map. It wraps the buffered response and
	 * assigns it a creation time.
	 */
	private static class Value
	{
		/** the original response to store */
		private BufferedWebResponse response;

		/** the time when this response is stored */
		private Instant creationTime;
	}

	/**
	 * The duration of time before a {@link Value} is considered as expired
	 */
	private final Duration lifetime;

	/**
	 * Construct.
	 * 
	 * @param maxEntries
	 *            how much entries this map can contain
	 * @param lifetime
	 *            the duration of time to keep an entry in the map before considering it expired
	 */
	public StoredResponsesMap(int maxEntries, Duration lifetime)
	{
		super(maxEntries);

		this.lifetime = lifetime;
	}

	@Override
	protected synchronized boolean removeEldestEntry(java.util.Map.Entry<String, Object> eldest)
	{
		boolean removed = super.removeEldestEntry(eldest);
		if (removed == false)
		{
			Value value = (Value)eldest.getValue();
			if (value != null)
			{
				Duration elapsedTime = Duration.between(value.creationTime, Instant.now());
				if (lifetime.compareTo(elapsedTime) <= 0)
				{
					removedValue = value.response;
					removed = true;
				}
			}
		}
		return removed;
	}

	@Override
	public BufferedWebResponse put(String key, Object bufferedResponse)
	{
		if (!(bufferedResponse instanceof BufferedWebResponse))
		{
			throw new IllegalArgumentException(StoredResponsesMap.class.getSimpleName() +
				" can store only instances of " + BufferedWebResponse.class.getSimpleName());
		}

		Value value = new Value();
		value.creationTime = Instant.now();
		value.response = (BufferedWebResponse)bufferedResponse;

		Value oldValue;
		synchronized (this)
		{
			oldValue = (Value)super.put(key, value);
		}

		return oldValue != null ? oldValue.response : null;
	}

	@Override
	public BufferedWebResponse get(Object key)
	{
		BufferedWebResponse result = null;
		Value value;
		synchronized (this)
		{
			value = (Value)super.get(key);
		}
		if (value != null)
		{
			Duration elapsedTime =  Duration.between(value.creationTime, Instant.now());
			if (lifetime.compareTo(elapsedTime) > 0)
			{
				result = value.response;
			}
			else
			{
				// expired, remove it
				remove(key);
			}
		}
		return result;
	}

	@Override
	public BufferedWebResponse remove(Object key)
	{
		Value removedValue;
		synchronized (this)
		{
			removedValue = (Value)super.remove(key);
		}

		return removedValue != null ? removedValue.response : null;
	}

	@Override
	public void putAll(Map<? extends String, ?> m)
	{
		throw new UnsupportedOperationException();
	}
}
