package com.limegroup.gnutella.downloader;

import java.io.File;
import java.io.Serializable;

import com.limegroup.gnutella.DownloadCallback;
import com.limegroup.gnutella.DownloadManager;
import com.limegroup.gnutella.FileManager;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.util.StringUtils;

/**
 * A ManagedDownloader that tries to resume a specific incomplete file.  The
 * ResumeDownloader initially has no locations to download from.  Instead it
 * immediately requeries--by hash if possible--and only accepts results that
 * would result in resumes from the specified incomplete file.  Do not be
 * confused by the name; ManagedDownloader CAN resume from incomplete files,
 * but it is less strict about its choice of download.
 */
public class ResumeDownloader extends ManagedDownloader 
        implements Serializable {
    /** Ensures backwards compatibility of the downloads.dat file. */
    static final long serialVersionUID = -4535935715006098724L;

    /** The temporary file to resume to. */
    private final File _incompleteFile;
    /** The name and size of the completed file, extracted from
     *  _incompleteFile. */
    private final String _name;
    private final int _size;
    
    /**
     * The hash of the completed file.  This field was not included in the LW
     * 2.7.0/2.7.1 beta, so it may be null when reading downloads.dat files
     * from these rare versions.  That's no big deal; it is like not having the
     * hash in the first place. 
     *
     * This is not used as much anymore, since ManagedDownloader stores the
     * SHA1 anyway.  It is still used, however, to keep the sha1 between
     * sessions, since it is serialized.
     */
    private final URN _hash;
    

    /** 
     * Creates a RequeryDownloader to finish downloading incompleteFile.  This
     * constructor has preconditions on several parameters; putting the burden
     * on the caller makes the method easier to implement, since the superclass
     * constructor immediately starts a download thread.
     *
     * @param incompleteFile the incomplete file to resume to, which
     *  MUST be the result of IncompleteFileManager.getFile.
     * @param name the name of the completed file, which MUST be the result of
     *  IncompleteFileManager.getCompletedName(incompleteFile)
     * @param size the size of the completed file, which MUST be the result of
     *  IncompleteFileManager.getCompletedSize(incompleteFile) */
    public ResumeDownloader(IncompleteFileManager incompleteFileManager,
                            File incompleteFile,
                            String name,
                            int size) {
        super( new RemoteFileDesc[0], incompleteFileManager, null);
        if( incompleteFile == null )
            throw new NullPointerException("null incompleteFile");
        this._incompleteFile=incompleteFile;
        if(name==null || name.equals(""))
            throw new IllegalArgumentException("Bad name in ResumeDownloader");
        this._name=name;
        this._size=size;
        this._hash=incompleteFileManager.getCompletedHash(incompleteFile);
    }

    /** Overrides ManagedDownloader to ensure that progress is initially
     *  non-zero and file previewing works. */
    public void initialize(DownloadManager manager, 
                           FileManager fileManager, 
                           DownloadCallback callback) {
        if(_hash != null)
            downloadSHA1 = _hash;
        incompleteFile = _incompleteFile;
        super.initialize(manager, fileManager, callback);
    }

    /**
     * Overrides ManagedDownloader to reserve _incompleteFile for this download.
     * That is, any download that would use the same incomplete file is 
     * rejected, even if this is not currently downloading.
     */
    public boolean conflictsWithIncompleteFile(File incompleteFile) {
        return incompleteFile.equals(_incompleteFile);
    }

    /**
     * Overrides ManagedDownloader to allow any RemoteFileDesc that would
     * resume from _incompleteFile.
     */
    protected boolean allowAddition(RemoteFileDesc other) {
        //Like "_incompleteFile.equals(_incompleteFileManager.getFile(other))"
        //but more efficient since no allocations in IncompleteFileManager.
        return IncompleteFileManager.same(
            _name, _size, downloadSHA1,     
            other.getFileName(), other.getSize(), other.getSHA1Urn());
    }


    /**
     * Overrides ManagedDownloader to display a reasonable file size even
     * when no locations have been found.
     */
    public synchronized int getContentLength() {
        return _size;
    }

    protected synchronized String getDefaultFileName() {
        return _name;
    }
    
    /**
     * Overriden to unset deserializedFromDisk too.
     */
    public synchronized boolean resume() {
        boolean ret = super.resume();
        // unset deserialized once we clicked resume
        if(ret)
            deserializedFromDisk = false;
        return ret;
    }

    /*
     * @param numRequeries The number of requeries sent so far.
     */
    protected boolean shouldSendRequeryImmediately(int numRequeries) {
        // created from starting up LimeWire.
        if(deserializedFromDisk)
            return false;
        // clicked Find More Sources?
        else if(numRequeries > 0)
            return super.shouldSendRequeryImmediately(numRequeries);
        // created from clicking 'Resume' in the library
        else
            return true;
    }
 
    protected boolean shouldInitAltLocs(boolean deserializedFromDisk) {
        // we shoudl only initialize alt locs when we are started from the
        // library, not when we are resumed from startup.
        return !deserializedFromDisk;
    }

    /** Overrides ManagedDownloader to use the filename and hash (if present) of
     *  the incomplete file. */
    protected QueryRequest newRequery(int numRequeries) {
        // Extract a query string from our filename.
        String queryName = StringUtils.createQueryString(getDefaultFileName());

        if (downloadSHA1 != null)
            // TODO: we should be sending the URN with the query, but
            // we don't because URN queries are summarily dropped, though
            // this may change
            return QueryRequest.createQuery(queryName);
        else
            return QueryRequest.createQuery(queryName);
    }

}
