/*
 * Copyright (C) 1999-2006 <a href="mailto:paschke@in.tum.de">Adrian Paschke</a>
 * 
 * The Utils Library is part of the Rule Based Service Level Agreement (RBSLA)
 * framework, available at 
 * <a href=" http://ibis.in.tum.de/staff/paschke/rbsla/index.htm">RBSLA</a>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package ws.prova.reference;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.Enumeration;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.lang.Character;

import ws.prova.HelperPredicates;
import ws.prova.RList;
import ws.prova.reagent;
import ws.prova.RListUtils;
import ws.prova.reference.ComplexTermImpl;
import ws.prova.reference.FactImpl;
import ws.prova.reference.PrerequisiteImpl;
import ws.prova.reference.VariableTermImpl;
import ws.prova.reference.ConstantTermImpl;

import org.mandarax.kernel.Replacement;
import org.mandarax.kernel.ResultSet;
import org.mandarax.kernel.SimplePredicate;
import org.mandarax.kernel.VariableTerm;
import org.mandarax.kernel.ComplexTerm;
import org.mandarax.kernel.Term;
import org.mandarax.reference.TmpClause;


/**
 * ContractLog cache for goal memoization
 * 
 * 
 * @author <A HREF="mailto:adrian.paschke@gmx.de">Adrian Paschke</A>
 */
public class Cache {
	
	
	
	
	public final int NO_CACHE = 0; // no cache
	public final int UNLIMITED = -1; // unlimited number of cached goals
	public final long NO_TIMEOUT = -1; // no time out for cached goals = memorize globaly with no time out
	public final long LOCAL = 0; // cache subgoals localy, i.e. only for one query
	private long cacheTimeout = NO_TIMEOUT; // timeout in milliseconds / -1 = no timeout / 0 = locally
	private static int initialCapacity = 10000;
	private int cacheSize = UNLIMITED; // number of cached (sub) goals / 0 = no cache / -1 = unlimited cache
	private Hashtable cache; // empty cache;
	private reagent reagent;
	
	/*
	 * Helper method
	 */
	public static FactImpl cacheSize() {			
		Class[] struct = {Object.class,	RList.class};
		SimplePredicate p = new SimplePredicate("cacheSize", struct);
		Term[] tmpTerm = {
				new ConstantTermImpl("cacheSize",java.lang.String.class),
				RListUtils.rlist("[cacheSize,Size]")};
		return new FactImpl(p,tmpTerm);
	}
	
	/*
	 * Helper method
	 */
	public static FactImpl cacheTimeout() {
		Class[] struct = {Object.class,	RList.class};
		SimplePredicate p = new SimplePredicate("cacheTimeout", struct);
		Term[] tmpTerm = {
				new ConstantTermImpl("cacheTimeout",java.lang.String.class),
				RListUtils.rlist("[cacheTimeout,Value]")};
		return new FactImpl(p,tmpTerm);
	}
	
	public Cache (reagent _reagent) {
		reagent = _reagent;
		cache = new Hashtable(initialCapacity);		
	}
	
	/**
	 *  A cached goal with a timestamp and the results for this goal
	 */
	public class CachedGoal {
		
		private  List results;
		private long timestamp;		
		
		public CachedGoal(long timestamp) {
			results = new ArrayList(); // empty result list
			this.timestamp = timestamp;							
		}
		
		public long getTimestamp() {
			return timestamp;
		}
		
		public List getCachedResults() {return results;}
		
		public void addResult(Object value) {	
			results.add(value);
		}
		
		public void addResults(Collection values) {	
			results.addAll(values);
		}
		
	}
	
	
	/**
	 * Check whether the goal is memorized or not
	 * @param goal
	 * @return true/false
	 */
	public boolean isCached(ComplexTerm goal) {
		if (cacheSize==NO_CACHE) return false;	
				
		String key = getExtendedKey(goal);
		if (cache.containsKey(key)) return true;
		else return false;
	}
	
	

	
	/**
	 * Returns the cached values for a goal as a list.
	 * List empty = cached failed goal
	 * or null = not found in cache
	 * @return the cachedGoal or null
	 */
	public List get(ComplexTerm goal) {		
		if (cacheSize==NO_CACHE) return null;
		
		
						
		String key = getExtendedKey(goal);		
		CachedGoal cg = (CachedGoal) cache.get(key);			
				
		// Clean up cache if maximum size is reached
		
		if ((cache.size()>=cacheSize) && (cacheSize!=UNLIMITED)) {
			// cache full --> clean up cache

			// first remove all outdated goals
			if (cacheTimeout!=NO_TIMEOUT && cacheTimeout!=LOCAL)
			for (Iterator it = cache.keySet().iterator();it.hasNext();) {					
				Object key1 = it.next();
				CachedGoal tempCG = (CachedGoal) cache.get(key);				
				if ((System.currentTimeMillis() - tempCG.getTimestamp()) > cacheTimeout) {
					cache.remove(key1); // remove deprecated goal
				}
			}
			
			// cache still too full --> completely clear cache
			if (cache.size() > (cacheSize/2)) { 
				cache.clear(); 
			}
			
			// now, add empty goal to cache
			CachedGoal tempCG = new CachedGoal(System.currentTimeMillis());				
			cache.put(key,tempCG);		
						
		}
		
		// found in cache --> return goal
		
		if (cg!=null) {	
			
			// check whether cached goal is expired
			if (cacheTimeout!=NO_TIMEOUT && cacheTimeout!=LOCAL && (System.currentTimeMillis() - cg.getTimestamp()) > cacheTimeout) {
					// goal results are expired --> remove from cache and derive it again			
					cache.remove(key);					
			} else {
				return cg.getCachedResults(); // return cached results
			}
						 
		}
		
		// not found in cache --> add empty goal to cache
		
		CachedGoal ng = new CachedGoal(System.currentTimeMillis()); // empty cached goal
		cache.put(key,ng);
		
		return null;	
		
		
	}
	
	public void update(String key, ComplexTerm goal) {		
		

		String key2 = getExtendedKey(goal);
		CachedGoal cg = (CachedGoal) cache.get(key);
		
		if (cg==null) {
			cg = new CachedGoal(System.currentTimeMillis());
			cache.put(key,cg);
		}		
		 
		Class[] struct = {
				Object.class,
				RList.class};
		String pName = goal.getTerms()[1].toString();
		pName = pName.substring(1,pName.length()-1);		
		SimplePredicate p = new SimplePredicate(pName, struct);
		Term[] tmpTerm = {
				goal.getTerms()[1],
				goal};					
		FactImpl tmpPre = new FactImpl(p,tmpTerm);
		
		
		// add goal clause
		TmpClause subGoal = new TmpClause();		
        subGoal.positiveLiterals = new ArrayList();
		subGoal.positiveLiterals.add(tmpPre);
		subGoal.negativeLiterals = new ArrayList();
		cg.addResult(subGoal);
		
		if (!key.equals(key2)) {
			CachedGoal cg2 = (CachedGoal) cache.get(key2);
			if (cg2==null) {
				cg2 = new CachedGoal(System.currentTimeMillis());
				cg2.addResult(subGoal);
				cache.put(key2,cg2);
			} else {
				cg2.addResult(subGoal);
				cache.put(key2,cg2);
			}
		}
		
	}
	

	/**
	 * Clear cache
	 *
	 */
	public void clear() {
		initialCapacity = cache.size();		
		cache = null;
		cache = new Hashtable(initialCapacity);
		//cache.clear();
	}
	
	/**
	 * Set the timeout for the cache 
	 * NO_TIMEOUT = -1 no timeout for cached goals
	 * LOCAL = 0 memorize subgoals during refutation attempt of one query 
	 * @param timeout in milliseconds
	 */
	public void setTimeout(long timeout) {		
		cacheTimeout=timeout;
	}
	
	/**
	 * Get the timeout for the cache 
	 * NO_TIMEOUT = -1 no timeout for cached goals
	 * LOCAL = 0 memorize only for one query 
	 * @result timeout in milliseconds
	 */
	public long getTimeout() {		
		return cacheTimeout;
	}
	
	/**
	 * Set the size (= number of goals) of the cache.
	 * NO_CACHE = no cache should be used
	 * UNLIMITED = no limit
	 * @param size number of goals
	 */
	public void setSize(int size) {
		cacheSize=size;		
	}
	
	
	/**
	 * Get the size (= number of goals) of the cache.
	 * NO_CACHE = no cache should be used
	 * UNLIMITED = no limit
	 * @result  int size number of goals
	 */
	public int getSize() {
		 return cacheSize;		
	}
	
	/**
	 * Set the initial capacity of the cache 
	 * @param in capacity
	 */
	public static void setInitialCapacity(int capacity) {		
		initialCapacity=capacity;
	}
	
	/**
	 * Create an extended Key from a complex term
	 */
	public String getExtendedKey(ComplexTerm key) {		
		Term[] subTerm = key.getAllSubterms();
		
		ComplexTerm key2 = key;
		for (int i=0;i<subTerm.length;i++) {
			
			if (subTerm[i] instanceof VariableTerm) {
				VariableTerm vold = (VariableTerm) subTerm[i];
				VariableTerm vnew = new VariableTermImpl();
				vnew.setName("X");
				vnew.setType(vold.getType());
				Replacement r = new Replacement(vold,vnew);
				key2 = (ComplexTerm) key2.apply(r); // copy structure and replace variables 				
			}
		}
	
		return key2.toString();
		
	}

	
	/**
	* Dump the content. For testing only!
	*/
	public void dump() {
		System.out.println("");
		System.out.println("Dumping Cache");
		System.out.println("---------------------------------");
		
		
		for (Enumeration en = cache.keys(); en.hasMoreElements();) {
			Object key = en.nextElement();
			
			System.out.println("KEY IS : " + key.toString());
		
			for (Iterator it1 = ((CachedGoal)cache.get(key)).getCachedResults().iterator(); it1.hasNext();) {
				System.out.println("CACHED : "+ it1.next());
			}
		}
	}



}
