/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.modules.masterfs;

import org.openide.filesystems.FileObject;

import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.*;

/**
 * Provides caching management for MasterFileObjects, that should be never
 * constructed directly (instead should be used method getOrCreate).
 *
 * Neither children references nor parent reference are kept.
 *
 * @author Radek Matous
 */
final class Cache {
    /** maps <String resourceName, FileSystem fs>*/
    private Map res2DfoMap;
    private static Cache instance;

    /***
     * @return one shared instance of Cache
     */
    static Cache getDefault() {
        synchronized (Cache.class) {
            if (instance == null) {
                instance = new Cache();
                instance.res2DfoMap = Collections.synchronizedMap(new WeakHashMap());
            }
        }
        return instance;
    }

    private Cache() {
    }

    /**
     * Looks up in cache MasterFileObject identified with resPath
     * @param resPath identifies MasterFileObject
     * @return instance of MasterFileObject if exists in cache or null
     */
    MasterFileObject get(final ResourcePath resPath) {
        //resPath = getNormalizedPath(resPath);
        MasterFileObject retVal = getValidOrInvalid(resPath);

        if (retVal != null)
            retVal = (retVal.isValid()) ? retVal : null;

        return retVal;
    }

    MasterFileObject getValidOrInvalid(final ResourcePath resPath) {
        final Reference ref = (Reference) res2DfoMap.get(resPath.getNormalizedPath());
        MasterFileObject retVal = null;
        if (ref != null)
            retVal = (MasterFileObject) ref.get();
        return retVal;
    }

    /**
     * Looks up in cache MasterFileObject identified with resPath. If there
     * is no appropriate object in cache, then new one is created, but only if
     * delegate can be found
     * @param resPath
     * @return MasterFileObject or null, if delegate can't be resolved
     */
    MasterFileObject getOrCreate(final ResourcePath resPath) {
        final MasterFileObject retVal = get(resPath);
        if (retVal != null) return retVal;

        FileObject delegate = Delegate.resolve(resPath);
        return (delegate == null) ? null : getOrCreate(resPath, delegate);
    }

    /**
     * Looks up in cache MasterFileObject identified with resPath. If there
     * is no appropriate object in cache, then new one is created, but only if
     * delegate can be found
     * @param resPath identification of MasterFileObject
     * @param delegate
     * @return MasterFileObject
     */
    MasterFileObject getOrCreate(final ResourcePath resPath, final FileObject delegate) {
        MasterFileObject retVal = getValidOrInvalid(resPath);
        boolean isRetValValid = (retVal != null && !retVal.isValid()) ? false : true;            
                        
        if (retVal != null && isRetValValid) {
            return retVal;
        }

        MasterFileObject nRetVal = new MasterFileObject(resPath, delegate);
        if (nRetVal.isValid()) {
            retVal = nRetVal;
            put(retVal);
        }
        
        return retVal;
    }

    /**
     * @return all objects from cache as enumeration
     */
    Enumeration getAll() {
        final ArrayList arrayList = new ArrayList();
        synchronized (res2DfoMap) {
            final Iterator it = res2DfoMap.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry entry = (Map.Entry) it.next();
                Reference ref = (Reference) entry.getValue();
                if (ref != null) {
                    MasterFileObject hfo = (MasterFileObject) ref.get();
                    if (hfo != null && hfo.getDelegate().isValid())
                        arrayList.add(hfo);
                }
            }
        }
        final Object[] array = new Object[arrayList.size()];
        arrayList.toArray(array);
        return org.openide.util.Enumerations.array (array);
    }

    void clear() {
        res2DfoMap.clear();
    }

    private MasterFileObject put(final MasterFileObject hfo) {
        MasterFileObject retVal = hfo;
        synchronized (res2DfoMap) {
            final MasterFileObject test = get(hfo.getResource());
            if (test == null || !test.isValid()) {
                res2DfoMap.remove(hfo.getResource().getNormalizedPath());
                res2DfoMap.put(hfo.getResource().getNormalizedPath(), createReference(hfo));
            } else {
                retVal = test;
            }
        }
        return retVal;
    }

    private static Reference createReference(final MasterFileObject hfo) {
        return new SoftReference(hfo);
        //return new WeakReference(hfo);
    }

    /**
     *  Replaces original MasterFileObject in cache with new one
     * @param oldResPath identifies the old one MasterFileObject in cache
     * @param newObject that replaces the original one
     */
    void replace(final String oldResPath, final MasterFileObject newObject) {
        synchronized (res2DfoMap) {
            final MasterFileObject test = getValidOrInvalid(new ResourcePath(oldResPath));
            if (test != null) res2DfoMap.remove(oldResPath);
            put(newObject);
        }
    }


}
