/*
 *  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.directory.server.core.api.subtree;


import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.subtree.Subentry;


/**
 * A cache for subtree specifications. It associates a Subentry with a Dn,
 * representing its position in the DIT.<br>
 * This cache has a size limit set to 1000 at the moment. We should add a configuration
 * parameter to manage its size.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class SubentryCache implements Iterable<Dn>
{
    /** The default cache size limit */
    private static final int DEFAULT_CACHE_MAX_SIZE = 1000;

    /** The cache size limit */
    private int cacheMaxSize = DEFAULT_CACHE_MAX_SIZE;

    /** The current cache size */
    private AtomicInteger cacheSize;

    /** The Subentry cache */
    private final Map<Dn, Subentry> cache;


    /**
     * Creates a new instance of SubentryCache with a default maximum size.
     */
    public SubentryCache()
    {
        cache = new ConcurrentHashMap<>();
        cacheSize = new AtomicInteger( 0 );
    }


    /**
     * Creates a new instance of SubentryCache with a specific maximum size.
     * 
     * @param maxSize The max number of elements in teh cache
     */
    public SubentryCache( int maxSize )
    {
        cache = new ConcurrentHashMap<>();
        cacheSize = new AtomicInteger( 0 );
        cacheMaxSize = maxSize;
    }


    /**
     * Retrieve a Subentry given a Dn. If there is none, null will be returned.
     *
     * @param dn The Dn we want to get the Subentry for
     * @return The found Subentry, or null
     */
    public final Subentry getSubentry( Dn dn )
    {
        return cache.get( dn );
    }


    /**
     * Remove a Subentry for a given Dn
     *
     * @param dn The Dn for which we want to remove the
     * associated Subentry
     * @return The removed Subentry, if any
     */
    public final Subentry removeSubentry( Dn dn )
    {
        Subentry oldSubentry = cache.remove( dn );

        if ( oldSubentry != null )
        {
            cacheSize.decrementAndGet();
        }

        return oldSubentry;
    }


    /**
     * Stores a new Subentry into the cache, associated with a Dn
     *
     * @param dn The Subentry Dn
     * @param subentry The SubtreeSpecification
     * @return The old Subentry, if any
     */
    public Subentry addSubentry( Dn dn, Subentry subentry )
    {
        if ( cacheSize.get() > cacheMaxSize )
        {
            throw new IllegalStateException( "Cache is full: size=" + cacheSize.get() + ", max=" + cacheMaxSize );
        }

        Subentry oldSubentry = cache.put( dn, subentry );

        if ( oldSubentry == null )
        {
            cacheSize.getAndIncrement();
        }

        return oldSubentry;
    }


    /**
     * Tells if there is a Subentry associated with a Dn
     * @param dn The Dn
     * @return True if a Subentry is found
     */
    public boolean hasSubentry( Dn dn )
    {
        return cache.containsKey( dn );
    }


    /**
     * @return An Iterator over the Subentry's DNs 
     */
    public Iterator<Dn> iterator()
    {
        return cache.keySet().iterator();
    }


    /**
     * @return The number of elements in the cache
     */
    public int getCacheSize()
    {
        return cacheSize.get();
    }
}
