/**
 *#########################################################################
 *
 * A component of the Greenstone Librarian Interface application, part of 
 * the Greenstone digital library suite from the New Zealand Digital 
 * Library Project at the University of Waikato, New Zealand.
 *
 * Author: Michael Dewsnip, NZDL Project, University of Waikato
 *
 * Copyright (C) 2005 New Zealand Digital Library Project
 *
 * 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.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */

package org.greenstone.gatherer.remote;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.zip.*;
import javax.swing.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.FedoraInfo;
import org.greenstone.gatherer.GAuthenticator;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.collection.CollectionManager;
import org.greenstone.gatherer.shell.GShell;
import org.greenstone.gatherer.util.UnzipTools;
import org.greenstone.gatherer.util.Utility;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.*;
import org.apache.commons.httpclient.params.*;
import org.apache.commons.httpclient.HttpStatus;

// ----------------------------------------------------------------------------------------------------
//   Part of the RemoteGreenstoneServer's QUEUE LAYER
// ----------------------------------------------------------------------------------------------------

// Moved here. Previously called RemoteGreenstoneServerActionQueue
/** 
 * A Thread that maintains a queue of RemoteGreenstoneServer Actions
 * that are to be executed in FIFO fashion.
*/
class ActionQueue extends Thread
{
    /** The queue of waiting jobs. */
    private ArrayList queue = null;
    private boolean exited;

    public ActionQueue()
    {
	super("RemoteGreenstoneServerActionQueue");
	exited = false;
	if (Gatherer.isGsdlRemote) {
	    queue = new ArrayList();
	    start();
	}
    }
    
    
    synchronized public void addAction(RemoteGreenstoneServerAction remote_greenstone_server_action)
    {
	queue.add(remote_greenstone_server_action);
	notifyAll();
    }
    
    
    synchronized public int size()
    {
	return queue.size();
    }
    
    synchronized public RemoteGreenstoneServerAction getAction(int i)
    {
	return (RemoteGreenstoneServerAction)queue.get(i);
    }

    synchronized public boolean hasExited() { 
	return exited; 
    }
    
    synchronized public void clear() { 
	queue.clear(); 
    }

    public void run()
    {
	boolean exit = false;

	while (!exit) {
	    RemoteGreenstoneServerAction remote_greenstone_server_action = null;
	    
	    // Wait until we are notify()ed by addAction that there is a new job on the queue
	    try {
		if(Gatherer.remoteGreenstoneServer != null) {
		    Gatherer.remoteGreenstoneServer.getProgressBar().setAction(null);
		}
		synchronized (this) {
		    while(queue.size() <= 0) {
			wait(); // wait for queue size to become > 0, which is done by external thread (performAction())
		    }
		    // Now there is at least one job on the queue, get the next in line and process it
		    remote_greenstone_server_action = (RemoteGreenstoneServerAction) queue.get(0); //getAction(0) is already synchronized 
		}
	    } catch (InterruptedException exception) {
		// It may be that GLI is exiting if we get here 
		exit = true;
	    }
	    
	    if(exit) {
		break;
	    }
	    
	    try {
		remote_greenstone_server_action.perform();
		
		// No exceptions were thrown, so the action was successful
		remote_greenstone_server_action.processed_successfully = true;
	    }
	    catch (RemoteGreenstoneServerAction.ActionCancelledException exception) {
		remote_greenstone_server_action.processed_successfully = false;
	    } 
	    catch(java.net.ConnectException exception) {
		if(exception.getMessage().trim().startsWith("Connection refused")) {
		    exit = true;
		} else {
		    DebugStream.printStackTrace(exception);
		}
		RemoteErrorPopup.invokeLater(exception.getMessage());
		remote_greenstone_server_action.processed_successfully = false;			
	    }
	    catch (FileNotFoundException exception) {
		// FileNotFoundException happens when there's no GS server at the user-provided
		// url (the address of gliserver.pl is wrong).
		exit = true;
		DebugStream.printStackTrace(exception);
		RemoteErrorPopup.invokeLater("No gliserver.pl found. " + exception.getMessage());
		remote_greenstone_server_action.processed_successfully = false;
	    }
	    catch (Exception exception) {
		DebugStream.printStackTrace(exception);
		RemoteErrorPopup.invokeLater(exception.getMessage());
		remote_greenstone_server_action.processed_successfully = false;
	    }
	    
	    // We're done with this action, for better or worse
	    try {
		remote_greenstone_server_action.processed = true;
		synchronized(remote_greenstone_server_action) {
		    remote_greenstone_server_action.notifyAll(); // notifies RemoteGreenstoneServer.performAction()
		}
	    } catch (Exception exception) {
		System.err.println("RemoteGreenstoneServerActionQueue.run() - exception: " + exception);
	    }
	    synchronized (this) { // remove the action just processed from the queue, since it's done.
		queue.remove(0);
	    }
	}

	// Out of while, means exit = true
	// Stop the gazillion annoying error messages when the connection was simply
	// refused or when the user pressed Cancel in the opening dialog, by clearing
	// the action queue of subsequent actions and setting exited to true.
	synchronized(this) {
	    queue.clear();
	    exited = true;
	}
    }

    
    // When deadlock, referred to in Kathy's commit comments as stalemate, occurs during a RemoteGS threaded
    // task run on a queue, and this task is a GUI one and on the GUI thread, such as launching GEMS from the
    // MetadataSetManager to create/edit a metadata set, any remote errors that then cause error dialogs to
    // pop up result in a deadlock between the widgets since there's waiting going on for the first widget.
    // This prevents the second widget from showing or working right, as both are on the same (GUI) thread.
    // As end result, client-GLI freezes.
    // An example of such an error that caused an error message to popup resulting in a GUI deadlock was
    // fixed in commit http://trac.greenstone.org/changeset/34232 (client-GLI > MetadataSetManager > GEMS)
    // But in theory such a GUI deadlock could happen for as yet unfixed errors. It prevents us from even
    // reading the error msg in the popup and delays debugging. What we need is to show the popup when
    // the GUI thread is free, i.e. SwingUtilities.invokeLater() stuff.
    // Private inner class RemoteErrorPopup deals with that. Refer to:
    // - https://stackoverflow.com/questions/1595744/is-joptionpane-showmessagedialog-thread-safe
    //   "JOptionPane does not document that it is thread safe, so you have to use invokeLater()."
    //   Also contains SwingWorker-based idea for the GUI thread clash between OpenCollDialog and
    // ProgressBar during automated Greenstone GUI testing.
    // - https://stackoverflow.com/questions/13863713/which-thread-to-launch-joptionpane
    // - https://www.codota.com/code/java/methods/javax.swing.JOptionPane/showMessageDialog
    // - https://www.programcreek.com/java-api-examples/?class=javax.swing.SwingUtilities&method=invokeLater
    private static class RemoteErrorPopup implements Runnable {

	/** Custom static method to make a safely threaded popup easier to call */
	public static void invokeLater(String msg) {
	    SwingUtilities.invokeLater(new RemoteErrorPopup(msg));
	}

	
	private String message;
	
	private RemoteErrorPopup(String msg) {
	    message = msg;
	}

		
	@Override
	public void run() {
	    
	    String msg = Dictionary.get("RemoteGreenstoneServer.Error", message);

	    // We often end up with giant bloated remote build error messages when things blow up
	    // These messages appear in pop ups that take up the entire screen and we can't even see the OK button
	    // The following therefore adds a scrollPane around error messages shown in showMessageDialog
	    // https://alvinalexander.com/java/joptionpane-showmessagedialog-example-scrolling/
	    JTextArea textArea = new JTextArea(20, 70);
	    textArea.setText(msg);
	    textArea.setEditable(false);
	    // wrap a scrollpane around message
	    JScrollPane scrollPane = new JScrollPane(textArea);	    
	    
	    JOptionPane.showMessageDialog(Gatherer.g_man, scrollPane, Dictionary.get("RemoteGreenstoneServer.Error_Title"), JOptionPane.ERROR_MESSAGE);
	}	
    }
    
}


