/**
 * MediaSniper 3.0 (2008-08-02)
 * Copyright 2007 - 2008 Zach Scrivena
 * zachscrivena@gmail.com
 * http://mediasniper.sourceforge.net/
 *
 * Simple program for downloading media files from popular websites.
 *
 * TERMS AND CONDITIONS:
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.freeshell.zs.mediasniper;

import java.io.File;
import java.net.URL;
import java.util.Calendar;
import org.freeshell.zs.common.Debug;
import org.freeshell.zs.common.Downloader;
import org.freeshell.zs.common.HtmlManipulator;


/**
 * Represent a download.
 */
class Download
        implements Runnable
{
    /** refresh interval, in milliseconds */
    private static final long REFRESH_INTERVAL_MILLISECONDS = 100L;

    /** parent MediaSniper object */
    private final MediaSniper parent;

    /** title of this download */
    private final String title;

    /** URL of the remote resource to be downloaded */
    private final URL url;

    /** string representation of the field <code>url</code> */
    private final String urlString;

    /** local file to be populated (full canonical pathname) */
    volatile private File file;

    /** creation time of this download */
    private final String time;

    /** length of the remote resource, in number of bytes; -1 if unknown */
    volatile private int length = -1;

    /** string representation of the length of the remote resource; "unknown" if unknown" */
    volatile private String lengthString = "unknown";

    /** Downloader object corresponding to this download */
    volatile private Downloader downloader = null;

    /** is the download completed? */
    volatile private boolean isCompleted = false;


    /**
    * Constructor.
    *
    * @param title
    *     title of this download
    * @param url
    *     URL of the remote resource to be downloaded
    * @param file
    *     local file to be populated
    */
    Download(
            final MediaSniper parent,
            final String title,
            final URL url,
            final File file)
    {
        this.parent = parent;
        this.title = title;
        this.url = url;
        this.urlString = url.toString();
        this.file = file;
        this.time = String.format("%1$tF %1$tT", Calendar.getInstance());
    }


    /**
    * Get the title of this download.
    *
    * @return
    *     title of this download
    */
    String getTitle()
    {
        return title;
    }


    /**
    * Get the string representation of the URL of the remote resource to be downloaded.
    *
    * @return
    *     string representation of the URL
    */
    String getUrlString()
    {
        return urlString;
    }


    /**
    * Get the local file to be populated.
    *
    * @return
    *     local file to be populated
    */
    File getFile()
    {
        return file;
    }


    /**
    * Get the name of the local file to be populated.
    * A relative pathname is returned if the local file is in the download directory;
    * otherwise an absolute pathname is returned.
    *
    * @return
    *     name of local file
    */
    String getFilename()
    {
        final String p = file.getPath();
        String downloadDirectory = parent.properties.getFile("download.directory").getPath();

        if (!downloadDirectory.endsWith(File.separator))
        {
            downloadDirectory += File.separator;
        }

        if (p.startsWith(downloadDirectory))
        {
            return p.substring(downloadDirectory.length());
        }
        else
        {
            return p;
        }
    }


    /**
    * Set the name of the local file to be populated.
    *
    * @param f
    *     name of local file
    * @return
    *     true if the specified name is accepted; false otherwise
    */
    boolean setFilename(
            final String newFilename)
    {
        final File newFile = parent.localFilenameManager.replaceFilename(
            file,
            parent.properties.getFile("download.directory"),
            newFilename);

        if (newFile == null)
        {
            /* user-specified new filename is NOT accepted */
            return false;
        }
        else
        {
            /* update local filename */
            file = newFile;
            return true;
        }
    }


    /**
    * Get the length of this download, in number of bytes.
    *
    * @return
    *     length of this download; -1 if unknown
    */
    int getLength()
    {
        if (length == -1)
        {
            if (downloader == null)
            {
                length = -1;
            }
            else
            {
                length = downloader.getLength();
            }
        }

        return length;
    }


    /**
    * Get a string representation of the length of this download.
    *
    * @return
    *     string representation of the length of this download; "unknown" if unknown
    */
    String getLengthString()
    {
        if ("unknown".equals(lengthString))
        {
            final int len = getLength();

            if (len < 0)
            {
                lengthString = "unknown";
            }
            else if (len < 1024)
            {
                lengthString = String.format("%d b", len);
            }
            else if (len < 1048576)
            {
                lengthString =  String.format("%.1f kb", len / 1024.0);
            }
            else if (len < 1073741824)
            {
                lengthString =  String.format("%.1f Mb", len / 1048576.0);
            }
            else
            {
                lengthString =  String.format("%.1f Gb", len / 1073741824.0);
            }
        }

        return lengthString;
    }


    /**
    * Get a string describing the current progress.
    *
    * @return
    *     string describing the current progress
    */
    String getProgressString()
    {
        if (downloader == null)
        {
            return "Waiting to start";
        }
        else
        {
            return downloader.getProgressString();
        }
    }


    /**
    * Get the percentage describing the current progress.
    *
    * @return
    *     percentage describing the current progress; -1 if unknown
    */
    int getProgressPercent()
    {
        if (downloader == null)
        {
            return -1;
        }
        else
        {
            return downloader.getProgressPercent();
        }
    }


    /**
    * Is the download completed?
    *
    * @return
    *     true if this download is completed; false otherwise
    */
    boolean isCompleted()
    {
        return isCompleted;
    }


    /**
    * Get the tooltip text for this download.
    *
    * @return
    *     tooltip text for this download
    */
    String getToolTipText()
    {
        return "<html>Title: <b>" + title + "</b><br />" +
                "URL: " + HtmlManipulator.quoteHtml(urlString) + "<br />" +
                "Size: " + getLengthString() + "<br />" +
                "Local filename: <b>" + HtmlManipulator.quoteHtml(getFilename()) + "</b><br />" +
                "Time added: " + time + "</html>";
    }


    /**
    * Start downloading the remote resource to the local file.
    * This method should run on a dedicated worker thread, not the EDT.
    */
    public void run()
    {
        downloader = new Downloader(url, file);
        new Thread(downloader).start();

        while (true)
        {
            if (downloader.isProgressUpdated())
            {
                parent.refreshDownloadOnTable(this);
            }

            if (downloader.isCompleted())
            {
                try
                {
                    downloader.waitUntilCompleted();
                }
                catch (Exception e)
                {
                    /* ignore */
                }

                parent.refreshDownloadOnTable(this);
                isCompleted = true;
                return;
            }

            Debug.sleep(REFRESH_INTERVAL_MILLISECONDS);
        }
    }
}