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 org.limewire.io.IOUtils;
import org.limewire.service.ErrorService;
import org.limewire.util.CommonUtils;
import org.limewire.util.GenericsUtils;

import com.limegroup.gnutella.URN;


/**
 * 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<URN, ContentResponseData>();    
    
    /** 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 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();
            Map<URN, ContentResponseData> checked = 
                GenericsUtils.scanForMap(map, URN.class, ContentResponseData.class, GenericsUtils.ScanMode.REMOVE);
            if(checked.size() != map.size())
                dirty = true;
            
            for(Iterator<ContentResponseData> i = checked.values().iterator(); i.hasNext(); ) {
                ContentResponseData data = i.next();
                if(data.getCreationTime() < cutoff) {
                    if(LOG.isWarnEnabled())
                        LOG.warn("Removing old response [" + data + "]");
                    i.remove();
                    dirty = true;
                }
            }
            
            responses = checked;
        } catch(Throwable t) {
            dirty = true;
            LOG.error("Can't read responses", t);
        } finally {
            IOUtils.close(ois);
            if(responses == null)
                responses = new HashMap<URN, ContentResponseData>();
        }
    }

    /**
     * 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);
        }
    }
}