/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is Protege-2000.
 *
 * The Initial Developer of the Original Code is Stanford University. Portions
 * created by Stanford University are Copyright (C) 2007.  All Rights Reserved.
 *
 * Protege was developed by Stanford Medical Informatics
 * (http://www.smi.stanford.edu) at the Stanford University School of Medicine
 * with support from the National Library of Medicine, the National Science
 * Foundation, and the Defense Advanced Research Projects Agency.  Current
 * information about Protege can be obtained at http://protege.stanford.edu.
 *
 */

package edu.stanford.smi.protege.model.framestore;

import java.lang.reflect.Method;
import java.util.Collection;

import edu.stanford.smi.protege.exception.TransactionException;
import edu.stanford.smi.protege.model.query.Query;
import edu.stanford.smi.protege.model.query.QueryCallback;
import edu.stanford.smi.protege.util.CacheMap;
import edu.stanford.smi.protege.util.transaction.TransactionIsolationLevel;
import edu.stanford.smi.protege.util.transaction.TransactionMonitor;

public class CallCachingFrameStore extends AbstractFrameStoreInvocationHandler {
    private static final int MAX_SIZE = 100 * 1000;
    private CacheMap _cache = new CacheMap(MAX_SIZE);
    private MethodCall _lookupMethodCall = new MethodCall();
    private TransactionMonitor transactionMonitor;

    protected Object handleInvoke(Method method, Object[] args) {
        Object result;
        /*
         * I think that the theory is that this first check (for events) will make the 
         * call caching work with the client-server but I don't think it is sufficient.  
         * For now we are disabling the call caching on the client.
         */
        if (isGetEvents(method)) {
            result = invoke(method, args);
            Collection c = (Collection) result;
            if (!c.isEmpty()) {
                clearCache();
            }
        } else if (isQuery(method)) {
            result = query(method, args);
        } else {
            _cache.clear();
            result = invoke(method, args);
        }
        return result;
    }
    
    public void executeQuery(Query q, QueryCallback qc) {
      getDelegate().executeQuery(q, qc);
    }

    private static boolean isGetEvents(Method m) {
        return m.getName().equals("getEvents");
    }

    private int hit = 0;
    private int miss = 0;

    private Object query(Method method, Object[] args) {
        /*
         * If the transaction isolation level is serializable then we must go to the database
         * to do the read to let it know how to the locking.
         */
        boolean mustRead = false;
        try {
          mustRead = transactionMonitor != null &&
                     transactionMonitor.inTransaction() && 
                     transactionMonitor.getTransationIsolationLevel() == TransactionIsolationLevel.SERIALIZABLE;
        } catch (TransactionException te) {
          mustRead = true;
        }
        _lookupMethodCall.set(method, args);
        Object o = _cache.get(_lookupMethodCall);
        if (o == null || mustRead) {
            ++miss;
            o = invoke(method, args);
            /*
             * Don't update the cache during a transaction.  Different clients see
             * different values.
             */
            if (transactionMonitor == null || !transactionMonitor.existsTransaction()) {
              _cache.put(new MethodCall(method, args), o);
            }
        } else {
            ++hit;
        }
        return o;
    }

    private void clearCache() {
        _cache.clear();
    }

    public void handleClose() {
        _cache.clear();
        _cache = null;
    }

    protected void handleReinitialize() {
        _cache.clear();
    }

    protected void setDelegate(FrameStore delegate) {
      super.setDelegate(delegate);
      if (delegate != null) {
        transactionMonitor = delegate.getTransactionStatusMonitor();
      }
    }
}
