//
// $Id: Downloader.java,v 1.2 2003/08/05 07:03:34 mdb Exp $

package com.threerings.resource;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;

import com.threerings.resource.DownloadManager.DownloadDescriptor;
import com.threerings.resource.DownloadManager.DownloadObserver;

/**
 * Does something extraordinary.
 */
public abstract class Downloader
{
    /**
     * Provides this downloader with a reference to the download
     * descriptor it needs to do its business.
     */
    public void init (DownloadDescriptor ddesc)
    {
        _desc = ddesc;
        _desc.destFileSize = _desc.destFile.length();
        _desc.destLastModified = _desc.destFile.lastModified();
    }

    /**
     * Returns a reference to the descriptor we are downloading.
     */
    public DownloadDescriptor getDescriptor ()
    {
        return _desc;
    }

    /**
     * Requests that this downloader determine whether it needs to be
     * updated and if so, it should add its total download requirement to
     * {@link ProgressInfo#totalSize} the supplied {@link ProgressInfo}
     * record.
     *
     * @return true if this downloader needs be updated, false if not.
     */
    public abstract boolean checkUpdate (ProgressInfo info)
        throws IOException;

    /**
     * Compares the supplied size and last modified time with the size and
     * last modified time of our local copy of the file.
     */
    protected boolean compareWithLocal (long fileSize, long lastModified)
    {
        // check to see whether we have a local copy at all, and if so,
        // whether its file size or last-modified time differs from the
        // source file modulo a delta to account for inexactness on the
        // part of the file-system (thanks Microsoft)
        return ((!_desc.destFile.exists()) ||
                (_desc.destFileSize != fileSize) ||
                (_desc.destLastModified < (lastModified - DELTA)) ||
                (_desc.destLastModified > (lastModified + DELTA)));
    }

    /**
     * Processes a single download descriptor.
     */
    public void processDownload (DownloadManager dmgr, DownloadObserver obs,
                                 ProgressInfo pinfo, byte[] buffer)
        throws IOException
    {
        // download the resource bundle from the specified URL
        URLConnection ucon = _desc.sourceURL.openConnection();

        // prepare to read the data from the URL into the cache file
        Log.info("Downloading file [url=" + _desc.sourceURL + "].");
        downloadContent(dmgr, obs, pinfo, buffer, ucon, _desc.destFile);

        // if we have a last modified time, we want to adjust our cache
        // file accordingly
        if (_desc.lastModified != 0) {
            if (!_desc.destFile.setLastModified(_desc.lastModified)) {
                Log.warning("Failed to set last-modified date " +
                            "[file=" + _desc.destFile + "].");
            }
        }
    }

    /**
     * Called after the download phase has completed to allow patching or
     * other post-download activities.
     */
    public void postDownload (DownloadManager dmgr, DownloadObserver obs,
                              ProgressInfo pinfo)
        throws IOException
    {
        // nothing to do by default
    }

    /**
     * Downloads the content from the supplied URL connection into the
     * specified destination file, informing the supplied download manager
     * and observer of our progress along the way.
     */
    protected void downloadContent (DownloadManager dmgr, DownloadObserver obs,
                                    ProgressInfo pinfo, byte[] buffer,
                                    URLConnection ucon, File destFile)
        throws IOException
    {
        InputStream in = ucon.getInputStream();
        FileOutputStream out = new FileOutputStream(destFile);
        int read;

        // read in the file data
        while ((read = in.read(buffer)) != -1) {
            // write it out to our local copy
            out.write(buffer, 0, read);

            // report our progress to the download observer as a
            // percentage of the total file data to be transferred
            pinfo.currentSize += read;

            // update our percent completion; if we're totally done, hold
            // off on notifying the observer until the download manager
            // finishes fiddling to ensure that any action the observer
            // may take on the downloaded files can be safely undertaken
            int pctdone = pinfo.getPercentDone();
            pinfo.complete = (pctdone >= 100);
            if (pinfo.complete) {
                continue;
            }

            // update the transfer rate to reflect the bit of data we just
            // transferred
            long now = System.currentTimeMillis();
            pinfo.updateXferRate(now);

            // notify the progress observer if it's been sufficiently long
            // since our last notification
            if ((now - pinfo.lastUpdate) >= UPDATE_DELAY) {
                pinfo.lastUpdate = now;
                long remaining = pinfo.getXferTimeRemaining();
                dmgr.notifyProgress(obs, pctdone, remaining);
            }
        }

        // close the streams
        in.close();
        out.close();
    }

    /** The descriptor that we're downloading. */
    protected DownloadDescriptor _desc;

    /** The last-modified difference in milliseconds allowed between the
     * source and destination file without considering the destination
     * file to be out of date. */
    protected static final long DELTA = 5000L;

    /** The delay in milliseconds between notifying progress observers of
     * file download progress. */
    protected static final long UPDATE_DELAY = 2500L;
}
