/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * <BR><BR>
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * <BR><BR>
 *
 * Copyright (C) 1999 New Zealand Digital Library Project
 *
 * <BR><BR>
 *
 * 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 2 of the License, or
 * (at your option) any later version.
 *
 * <BR><BR>
 *
 * 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.
 *
 * <BR><BR>
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */
package org.greenstone.gatherer.download;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
//import org.greenstone.gatherer.collection.DownloadJob;
import org.greenstone.gatherer.util.AppendLineOnlyFileDocument;
import org.greenstone.gatherer.util.Utility;
import org.greenstone.gatherer.gui.*;

public class DownloadProgressBar
    extends JPanel 
    implements ActionListener {

    private boolean simple = false;

    private Dimension bar_size = new Dimension(520, 25);

    private Dimension MINIMUM_BUTTON_SIZE = new Dimension(100, 25);
    private  Dimension PROGRESS_BAR_SIZE = new Dimension(580,75);
    
    private int current_action;
    private int err_count;
    private int file_count;
    private int total_count;
    private int warning_count;

    private JLabel current_status;
    private JLabel main_status;
    private JLabel results_status;
	 
    private JPanel center_pane;
    private JPanel inner_pane;

    private JProgressBar progress;

    private long file_size;
    private long total_size;

    private String current_url;
    private String initial_url;

    private DownloadJob owner;

    public JButton stop_start_button;
    public JButton log_button;
    public JButton close_button;

    public DownloadProgressBar(DownloadJob owner, String initial_url, boolean simple) {
	this.owner = owner;
	this.current_url = null;
	this.err_count = 0;
	this.initial_url = initial_url;
	this.file_count = 0;
	this.file_size = 0;
	this.simple = simple;
	this.total_count = 0;
	this.total_size = 0;
	this.warning_count = 0;

	this.setLayout(new BorderLayout());
	this.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));

	this.current_action = DownloadJob.STOPPED;

	inner_pane = new JPanel(new BorderLayout());
	inner_pane.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));

	center_pane = new JPanel(new GridLayout(3,1));

	main_status = new JLabel();

	current_status = new JLabel();
		  
	results_status = new JLabel();

	progress = new JProgressBar();
	progress.setStringPainted(true);
	progress.setMinimum(0);
	progress.setMaximum(0);
	progress.setEnabled(false);
	progress.setString(Dictionary.get("Mirroring.DownloadJob.Waiting"));
	inner_pane.add(progress, BorderLayout.CENTER);

	center_pane.add(main_status);
	center_pane.add(current_status);
	center_pane.add(results_status);

	JPanel button_pane = new JPanel();

	// our "pause" button never paused before, it always did a process.destroy() on being pressed.
	// See http://trac.greenstone.org/browser/trunk/gli/src/org/greenstone/gatherer/download/DownloadJob.java?rev=13594
	// However, now we additionally ensure that the wget launched by the perl is stopped before 
	// process.destroy(), so at least it cleans up better. I'm therefore changing it to a "Stop" button.
	stop_start_button = new GLIButton(Dictionary.get("Mirroring.DownloadJob.Stop"),Dictionary.get("Mirroring.DownloadJob.Stop_Tooltip"));
	stop_start_button.addActionListener(this);
	stop_start_button.addActionListener(owner);
	stop_start_button.setMinimumSize(MINIMUM_BUTTON_SIZE);
	stop_start_button.setMnemonic(KeyEvent.VK_P);
	stop_start_button.setEnabled(true);
	

	log_button = new GLIButton(Dictionary.get("Mirroring.DownloadJob.Log"),Dictionary.get("Mirroring.DownloadJob.Log_Tooltip"));
	log_button.addActionListener(owner);
	log_button.addActionListener(this);
	log_button.setEnabled(false);
	log_button.setMinimumSize(MINIMUM_BUTTON_SIZE);
	log_button.setMnemonic(KeyEvent.VK_L);


	close_button =  new GLIButton(Dictionary.get("Mirroring.DownloadJob.Close"),Dictionary.get("Mirroring.DownloadJob.Close_Tooltip"));
	close_button.addActionListener(owner);
	close_button.addActionListener(this);
	close_button.setMinimumSize(MINIMUM_BUTTON_SIZE);
	close_button.setMnemonic(KeyEvent.VK_C);
	close_button.setEnabled(true);


	// Layout - or at least some of it

	inner_pane.add(center_pane, BorderLayout.NORTH);

	button_pane.setLayout(new GridLayout(3,1));
	button_pane.add(stop_start_button);
	button_pane.add(log_button);
	button_pane.add(close_button);

	this.add(inner_pane, BorderLayout.CENTER);
	this.add(button_pane, BorderLayout.EAST);

	// Make the labels, etc update.
	refresh();
	
	TestingPreparation.setIndividualSubcomponentNames(
		  this, current_status, main_status, results_status);
    }

    // internally thread-safe
    public void enableCancelJob(boolean isEnabled) {	
	Gatherer.invokeInEDT_replacesProceedInCurrThread(
	     "DownloadProgressBar.enableCancelJob() - stop-start and close buttons",
	     Gatherer.SYNC,
	     new Runnable() {
		 public void run() {
		     synchronized(stop_start_button) {
			 stop_start_button.setEnabled(isEnabled);
		     }
		     synchronized(close_button) {
			 close_button.setEnabled(isEnabled);
		     }
		 }
	     });
    }

    public void actionPerformed(ActionEvent event) {
	Object source = event.getSource();
	if(source == stop_start_button) {
	    // If we are running, stop.
	    if (current_action == DownloadJob.RUNNING) {
		current_action = DownloadJob.STOPPED;
                stop_start_button.setText(Dictionary.get("Mirroring.DownloadJob.Stopped"));
                stop_start_button.setToolTipText(Dictionary.get("Mirroring.DownloadJob.Stopped_Tooltip"));
	        
		progress.setString(Dictionary.get("Mirroring.DownloadJob.Download_Stopped"));
		progress.setIndeterminate(false);
		stop_start_button.setEnabled(false);
	    }
	}
	else if(source == close_button) {
	    // If we are running, stop.
	    if (current_action == DownloadJob.RUNNING) {
		current_action = DownloadJob.STOPPED;
             	progress.setString(Dictionary.get("Mirroring.DownloadJob.Download_Stopped"));
	
		progress.setIndeterminate(false);
	    }
	}
	else if(source == log_button) {
           
	    LogDialog dialog = new LogDialog(owner.getLogDocument());
	    dialog.setVisible(true);
	    dialog = null;
	}

	refresh();
    }

    /** This method is called when a new download is begun. The
     * details of the download are updated and a new JProgressBar
     * assigned to track the download.
     * @param url The url String of the file that is being downloaded.
     */
    public synchronized void addDownload(String url) {
	current_url = url;
	file_size = 0;
	refresh();
    }

    /** When the download of the current url is completed, this method
     * is called to enlighten the DownloadProgressBar of this fact.
     */
    public synchronized void downloadComplete() {
	current_url = null;
	file_count++;
	if(total_count < (file_count + err_count + warning_count)) {
	    total_count = (file_count + err_count + warning_count);
	}
	progress.setValue(progress.getMaximum());
	//Dictionary.setText(progress, "Mirroring.DownloadJob.Download_Complete");
	refresh();
    }

    public synchronized void downloadFailed() {
	err_count++;
	if(total_count < (file_count + err_count + warning_count)) {
	    total_count = (file_count + err_count + warning_count);
	}
	refresh();
    }

    public synchronized void downloadWarning() {
	warning_count++;
	if(total_count < (file_count + err_count + warning_count)) {
	    total_count = (file_count + err_count + warning_count);
	}
	refresh();
    }

    // need to make these methods synchronized too, as they modify variables
    // that other synchronized methods work with. And if any methods that modify
    // such variables were to remain unsynchronized, can end up with race conditions
    // http://stackoverflow.com/questions/574240/is-there-an-advantage-to-use-a-synchronized-method-instead-of-a-synchronized-blo
    // "Not only do synchronized methods not lock the whole class, but they don't lock the whole instance either. Unsynchronized methods in the class may still proceed on the instance."
    // "Only the syncronized methods are locked. If there are fields you use within synced methods that are accessed by unsynced methods, you can run into race conditions."
    public synchronized void setTotalDownload(int total_download) {
	total_count = total_download;
	refresh();
    }

    public Dimension getPreferredSize() {
	return PROGRESS_BAR_SIZE;
    }

    public synchronized void increaseFileCount() {
	file_count++;
	refresh();
    }

    public synchronized void increaseFileCount(int amount) {
	file_count += amount;
	refresh();
    }

    public synchronized void resetFileCount() {
	file_count = 0;
	refresh();
    }

    /** When a mirroring task is first initiated this function is called
     * to set initial values for the variables if necessary and to 
     * fiddle visual components such as the tool tip etc.
     * @param reset A Boolean specifying whether the variables should be
     * reset to zero.
     */
    public synchronized void mirrorBegun(boolean reset, boolean simple) {
	if(reset) {
	    this.file_count = 0;
	    this.file_size = 0;
	    this.total_count = 0;
	    this.total_size = 0;
	    this.err_count = 0;
	    this.warning_count = 0;
	}
	current_action = DownloadJob.RUNNING;
	Gatherer.invokeInEDT_replacesProceedInCurrThread(
	 "DownloadProgressBar.mirrorBegun()",
	 Gatherer.SYNC,
	 new Runnable() {
 		public void run() {
		    stop_start_button.setEnabled(true);
		    log_button.setEnabled(true);
		    if(simple) {
			progress.setIndeterminate(true);
			progress.setString(Dictionary.get("Mirroring.DownloadJob.Download_Progress"));
		    }
		}
	 });
    }	

    /** Once a mirroring task is complete, if the DownloadJob returns from the
     * native call but the status is still running, then this method
     * is called to once again tinker with the pretty visual 
     * components.
     */
    public synchronized void mirrorComplete() {
	current_action = DownloadJob.COMPLETE;
	current_url = null;
	Gatherer.invokeInEDT_replacesProceedInCurrThread(
	 "DownloadProgressBar.mirrorComplete()",
	 Gatherer.SYNC,
	 new Runnable() {
 		public void run() {
		    if(simple) {
			progress.setIndeterminate(false);
		    }
		    progress.setValue(progress.getMaximum());
		    progress.setString(Dictionary.get("Mirroring.DownloadJob.Download_Complete"));
		    current_status.setText(Dictionary.get("Mirroring.DownloadJob.Download_Complete")); 
		    // will display "Download Complete" once an OAI download session has finished running
		    
		    stop_start_button.setEnabled(false);
		    DownloadProgressBar.this.updateUI();
		}
	 });
    }

    /** When called this method updates the DownloadProgressBar to reflect
     * the amount of the current file downloaded.
     */
    public synchronized void updateProgress(long current, long expected) {
	file_size = file_size + current;
	if(!progress.isIndeterminate()) {
	    // If current is zero, then this is the 'precall' before the
	    // downloading actually starts.
	    if(current == 0) {
		// Remove the old progress bar, then deallocate it.
		inner_pane.remove(progress);
		progress = null;
		if(expected == 0) {
		    // We don't have a content length. This bar will go from 0 to 100 only!
		    progress = new JProgressBar(0, 100);
		}
		else {
		    // Assign a new progress bar of length expected content length.
		    progress = new JProgressBar(0, (Long.valueOf(expected)).intValue());
		}
		progress.setEnabled(true);
		// Add the new progress bar.
		inner_pane.add(progress, BorderLayout.CENTER);
		inner_pane.updateUI();
	    }
	    // Otherwise if expected is not zero move the progress bar and
	    // update percent complete.
	    else if (expected != 0) {
		progress.setValue((Long.valueOf(file_size)).intValue());
		int p_c = (Double.valueOf(progress.getPercentComplete() * 100)).intValue();
		progress.setString(p_c + "%");
		progress.setStringPainted(true);
	    }
	    // Finally, in the case we have no content length, we'll instead
	    // write the current number of bytes downloaded again.
	    else {
		progress.setString(file_size + " b");
		progress.setStringPainted(true);
	    }
	}
	refresh();
    }

    /** Causes the two labels associated with this DownloadProgressBar object to
     * update, thus reflecting the progression of the download. This 
     * method is called by any of the other public setter methods in this 
     * class.
     */
    private void refresh() {
	Gatherer.invokeInEDT_replacesProceedInCurrThread(
	 "DownloadProgressBar.refresh()",
	 Gatherer.SYNC,
	 new Runnable() {
	     public void run() {
		 // Refresh the contents of main label.
		 String args1[] = new String[1];
		 args1[0] = initial_url.toString();
		 main_status.setText(Dictionary.get("Mirroring.DownloadJob.Downloading", args1));
		 
		 if (current_url != null) {
		     // Refresh the current label contents.
		     String args2[] = new String[1];
		     args2[0] = current_url;
		     current_status.setText(Dictionary.get("Mirroring.DownloadJob.Downloading", args2));
		 }
		 else if (current_action == DownloadJob.STOPPED || current_action == DownloadJob.PAUSED) {
		     current_status.setText(Dictionary.get("Mirroring.DownloadJob.Waiting_User"));
		 }
		 else {
		     current_status.setText(Dictionary.get("Mirroring.DownloadJob.Download_Complete"));
		 }
		 
		 // Refresh the contents of results label
		 String args3[] = new String[4]; 
		 args3[0] = file_count + "";
		 args3[1] = total_count + "";
		 args3[2] = warning_count + "";
		 args3[3] = err_count + "";
		 results_status.setText(Dictionary.get("Mirroring.DownloadJob.Status", args3));
		 
		 DownloadProgressBar.this.updateUI();
	     }
	 });
    }

    static final private Dimension DIALOG_SIZE = new Dimension(640,480);

    private class LogDialog
	extends JDialog {
	
	public LogDialog(AppendLineOnlyFileDocument document) {
	    super(Gatherer.g_man, Dictionary.get("Mirroring.DownloadJob.Log_Title"));
	    setSize(DIALOG_SIZE);
	    JPanel content_pane = (JPanel) getContentPane();
	    JTextArea text_area = new JTextArea(document);
	    JButton button = new GLIButton(Dictionary.get("General.Close"));
	    // Connection
	    button.addActionListener(new CloseActionListener());
	    // Layout
	    content_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
	    content_pane.setLayout(new BorderLayout());
	    content_pane.add(new JScrollPane(text_area), BorderLayout.CENTER);
	    content_pane.add(button, BorderLayout.SOUTH);
	}

	private class CloseActionListener
	    implements ActionListener {
	    public void actionPerformed(ActionEvent event) {
		LogDialog.this.dispose();
	    }
	}
    }
}
