/*
 * 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.isis.viewer.wicket.ui.pages.accmngt;

import java.util.Map;

import org.apache.wicket.MetaDataKey;
import org.apache.wicket.util.collections.MostRecentlyUsedMap;
import org.apache.wicket.util.time.Duration;
import org.apache.wicket.util.time.Time;

/**
 * A map that contains the emails to be verified. 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
 */
public class AccountConfirmationMap extends MostRecentlyUsedMap<String, Object>
{
    private static final long serialVersionUID = 1L;

    public static final MetaDataKey<AccountConfirmationMap> KEY = new MetaDataKey<AccountConfirmationMap>() {
    };

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

        /** the time when this email is stored */
        private Time 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 AccountConfirmationMap(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 = Time.now().subtract(value.creationTime);
                if (lifetime.lessThanOrEqual(elapsedTime))
                {
                    removedValue = value.email;
                    removed = true;
                }
            }
        }
        return removed;
    }

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

        Value value = new Value();
        value.creationTime = Time.now();
        value.email = (String)email;

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

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

    @Override
    public String get(Object key)
    {
        String result = null;
        Value value;
        synchronized (this)
        {
            value = (Value)super.get(key);
        }
        if (value != null)
        {
            Duration elapsedTime = Time.now().subtract(value.creationTime);
            if (lifetime.greaterThan(elapsedTime))
            {
                result = value.email;
            }
            else
            {
                // expired, remove it
                remove(key);
            }
        }
        return result;
    }

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

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

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