package com.limegroup.gnutella.auth;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.util.CommonUtils;
import com.limegroup.gnutella.util.IOUtils;


/**
 * A repository of content responses.
 */
class ContentCache {
    
    private static final Log LOG = LogFactory.getLog(ContentCache.class);
    
    /** The amount of time to keep a response in the cache. */
    private static final long EXPIRY_TIME = 7 * 24 * 60 * 60 * 1000; // one week.    
    
    /** File where the licenses are serialized. */
    private final File CACHE_FILE =
        new File(CommonUtils.getUserSettingsDir(), "responses.cache");
    
    /** Map of SHA1 to Responses. */
    private Map /* URN -> ContentResponseData */ responses = new HashMap();    
    
    /** Whether or not data is dirty since the last time we wrote to disk. */
    private boolean dirty = false;
    
    /** Returns the number of items in the map. */
    synchronized int getSize() {
        return responses.size();
    }
    
    /** Determines if there is a response for the given URN. */
    synchronized boolean hasResponseFor(URN urn) {
        return responses.containsKey(urn);
    }
    
    /** Adds the given response for the given URN. */
    synchronized void addResponse(URN urn, ContentResponseData response) {
        responses.put(urn, response);
        dirty = true;
    }
    
    /** Gets the response for the given URN. */
    synchronized ContentResponseData getResponse(URN urn) {
        return (ContentResponseData)responses.get(urn);
    }
    
    /** Initializes this cache. */
    synchronized void initialize() {
        dirty = false;        
        deserialize();
    }
    
    /** Writes to disk. */
    synchronized void writeToDisk() {
        if(dirty)
            persistCache();
        dirty = false;
    }
    
   /**
     * Loads values from cache file, if available.
     */
    private void deserialize() {
        long cutoff = System.currentTimeMillis() - EXPIRY_TIME;
        
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(CACHE_FILE)));
            Map map = (Map)ois.readObject();
            if(map != null) {
                for(Iterator i = map.entrySet().iterator(); i.hasNext(); ) {
                    // Remove values that aren't correct.
                    Map.Entry next = (Map.Entry)i.next();
                    Object key = next.getKey();
                    Object value = next.getValue();
                    if( !(key instanceof URN) || !(value instanceof ContentResponseData) ) {
                        if(LOG.isWarnEnabled())
                            LOG.warn("Invalid k[" + key + "], v[" + value + "]");
                        i.remove();
                        dirty = true;
                    }
                    if(((ContentResponseData)value).getCreationTime() < cutoff) {
                        if(LOG.isWarnEnabled())
                            LOG.warn("Removing old response [" + value + "]");
                        i.remove();
                        dirty = true;
                    }
                }
            } else {
                map = new HashMap();
            }
            
            responses = map;
        } catch(Throwable t) {
            dirty = true;
            LOG.error("Can't read responses", t);
        } finally {
            IOUtils.close(ois);
            if(responses == null)
                responses = new HashMap();
        }
    }

    /**
     * Write cache so that we only have to calculate them once.
     */
    public void persistCache() {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(CACHE_FILE)));
            oos.writeObject(responses);
            oos.flush();
        } catch (IOException e) {
            ErrorService.error(e);
        } finally {
            IOUtils.close(oos);
        }
    }
}