package org.greenstone.applet.GsdlCollageApplet;

import org.webswing.toolkit.api.WebswingUtil;
import org.webswing.toolkit.api.action.WebActionEvent;
import org.webswing.toolkit.api.action.WebActionListener;

import org.webswing.toolkit.api.lifecycle.WebswingShutdownListener;
import org.webswing.toolkit.api.lifecycle.OnBeforeShutdownEvent;

import org.webswing.toolkit.api.url.*;

//import java.applet.Applet;
import javax.swing.JApplet;
import java.awt.event.ComponentListener;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.*;
import java.net.*;

    
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.util.Map;
import java.util.HashMap;

    
/**
 * 
 *  @author Katrina Edgar
 *  @author David Bainbridge
 * adjusted for GS3 in 2024 (Anu) - webswing application
 *
 * Main class for the GsdlCollageApplet<br>
 * Processes the parameters passed through the Applet<br>
 * Builds an appropriate starting url from these parameters<br>
 * Creates a storage class for downloaded images<br>
 * Starts thread to download images and their associated url<br>
 * Starts thread to display downloaded images on the applet screen<br>
 */
 
public class GsdlCollageApplet extends JApplet implements ComponentListener
{
    static final int EXTRA_HEIGHT = 30; // for status bar or any extra elements
    private int X_DIM = 600;
    private int Y_DIM = 300;

    boolean isWebswing = false; // if run as webswing
    // To run this GsdlCollageApplet as Application instead of as Applet
    boolean isRunAsApplet = true;
    // set only if JPhind object is run as an application
    URL docBaseURL = null;
    JLabel statusBar = null;    
    Map<String,String> appParams;
    
    // package access, used mainly for GS3
    int gsdlversion = 2;
    String baseURL = null;

    /** When asked to stop running, this variable will be set to true */
    private boolean stop_running = false;    
    
    /** Amount of error checking output produced <br>
     *  Ranges from 0 - no output to 3 - maximum output */ 
    protected int verbosity_     = 0;
    /** Indicates whether java2 functionality should be used <br>
     *  If true will allow advanced image processing techniques to occur, 
     *  such as fading and colouring using pixel manipulation <br>
     *  If false, images will maintain an alpha value of 1 (appear solid), 
     *  newer images will simply be pasted on top of existing images <br> */
    protected boolean isJava2_  = true;
    /** Number of nested links to follow When used with greenstone,
     *  controls to which level of the document links will be followed For
     *  example a maximum depth of 1 will mean only the top page of a
     *  document will be examined, while a depth of 2 will include sections
     *  within this document, 3 includes sections within sections, and so
     *  on */
    protected int maxDepth_     = 3;

    /** Maximum number of images to display on the canvas */
    protected int maxDisplay_ = 25;

    /** Maximum number of downloaded images to store <br> Prevents applet
     *  from using excessive amounts of bandwidth by downloading too many
     *  images simulataneously*/
    protected int maxDownloads_ = Integer.MAX_VALUE;

    /** Types of images permitted in the collage, for example gif, jpg,
     * png... */
    protected String imageType_ = ".jpg%.png";

    /** Caption of the collage */
    protected String caption_ = "";

    /** Background color of applet screen */
    protected Color bgcolor_ = new Color(150, 193, 155);

    /** Time lapse between repainting of the applet */
    protected int refreshDelay_ = 2000;
    
    /** Stores an image and url pair and provides associated methods */
    protected DownloadImages download_images_ = null;
    /** Downloads images from a starting url, recursively follows nested
     * links */
    protected DownloadUrls download_thread_   = null;
    /** Image processing and placement on applet screen */
    protected DisplayImages display_thread_   = null;

   
    /** Gets the set, calculated or default width (x dimension) of the applet/application */
    public int X_DIM() {
	return X_DIM;
    }
    /** Gets the set, calculated or default height (y dimension) of the applet/application */
    public int Y_DIM() {
	return Y_DIM;
    }
    
    /** Gets verbosity */
    public int verbosity()    { return verbosity_; }
    /** Gets maximum depth for nested links */
    public int maxDepth()     { return maxDepth_; }

    /** Gets maximum number of images to display */
    public int maxDisplay() { return maxDisplay_;}

    /** Gets maximum number of images to store during downloading */
    public int maxDownloads() { return maxDownloads_; }
    /** Gets the refresh delay used between repaint */
    public int refreshDelay() { return refreshDelay_; }

    /** Gets parameters from the applet code and stores locally.
     * Forms a starting url for image retrieval to begin from.
     * The starting url is formed either by:
     *  * Using the image_url parameter as provided, which is assumed to 
     *     be a complete url
     *  * If this parameter does not exist, the assumption is that the 
     *     collage is being incorporated with the Greenstone Digital Library 
     *     Software. The starting url is formed by concatenating
     *     the gwcgi, collection and classifier parameters as provided.
     * Then starts downloading and displaying images */

    Thread paint ;

    public GsdlCollageApplet() {

	super();
	
	// status bar if run as application or as webswing applet,
	// mimicing running as regular applet which gives us a status bar by default
	// Useful for Phind, not for Collage where there are layout issues even if hidden
	//setLayout(new BorderLayout());
	//if(!isRunAsApplet) {
	statusBar = null; //new JLabel();
	
	//this.add(statusBar, BorderLayout.SOUTH);
	
	///Window win = SwingUtilities.getWindowAncestor(this);
	///if(win instanceof JFrame) {
	///    JFrame topFrame = (JFrame)win;
	///    topFrame.add(statusBar, BorderLayout.SOUTH);
	///} else { //AppletViewer or browser
	///    this.add(statusBar, BorderLayout.SOUTH);
	///}
	
	//Dimension d = statusBar.getSize();
	//d.height = EXTRA_HEIGHT;
	//statusBar.setPreferredSize(d);
	//}

	this.addMouseListener(new CollageMouseAdapter());
    }

    /**
     * This method is called (by main()) only when this class is run
     * as an application, not as applet. It prepares the variables and
     * commandline parameters to be set up, so the rest of the
     * initialisation can be done in the usual way (with the
     * Applet.getParameters() method) in the init() method called
     * hereafter for both application and applet mode.
     */
    public void applicationPreInit(String[] args) {
	// I copied the contents of this contructor for my code for the JPhind.java constructor
	// This constructor will be used when this JApplet is run as an application
	// instead of as applet
	this.isRunAsApplet = false;

	appParams = new HashMap<String,String>((args.length+1)/2);
	// For GS2, gwcgi = param gwcgi
	// For GS3, gwcgi = param gwcgi + param library
	//          docBaseURL = starting_url/image_url = param gwcgi

	String key = null;
	String value = null;
	for(int i = 0; i < args.length; i++) {
	    if(i%2==0) {
		key = args[i].substring(2); // remove -- prefix of paramname
		//System.err.println("got key: " + key);
	    } else {
		value = args[i];
		appParams.put(key, value);
		//System.err.println("got value: " + value);


		// HttpUtils.parseQueryString() deprecated, so hacking decode xtra key-value pairs
		// https://stackoverflow.com/questions/13592236/parse-a-uri-string-into-name-value-collection?page=1&tab=scoredesc#tab-top
		
		if(key.equals("xtraParams")) {
		    value = value.replace("&amp;", "&"); // just in case we have html entities
		    parseXtraParams(value, "=", "&", appParams);		    
		}
		
		key = value = null;
	    }
	}
	
	// Need these parameters set before init(), so that the display works
	int w = this.getWidth();
	if(appParams.get("width") != null) {
	    w = Integer.parseInt(appParams.get("width"));
	}
	int h = this.getHeight();
	if(appParams.get("height") != null) {
	    h = Integer.parseInt(appParams.get("height"));
	}
	// For applet, w and h may now be set to a positive value. But if GsdlCollageApplet
	// is run as an application, it won't yet be visible and not have a non-zero dimension.
	// So use defaults here:
	this.X_DIM = (w <= 0) ? X_DIM : w;
	this.Y_DIM = (h <= 0) ? Y_DIM : h;

	// Attempting hack to circumvent division by zero error in MyAffineTransform line 39
	//DisplayImages.app_x_dim_ = X_DIM;
	//DisplayImages.app_y_dim_ = Y_DIM;
	
	try {
	    this.docBaseURL = new URL(appParams.get("baseurl"));
	} catch(MalformedURLException mue) {	    
	    mue.printStackTrace();
	    System.err.println("*** Unable to instantiate URL from parameter baseurl: " + appParams.get("baseurl"));
	    System.exit(-1);
	}
	
    }
    
    /**
     * Overriding (J)Applet method getParameter to first check appParams map
     * if GsdlCollage was run as an application. 
     * If run as an applet, we still check the appParams first for if the param-name
     * exists in any xtraParams manually parsed into appParams, before finally
     * checking the Applet method getParameter().
     * https://stackoverflow.com/questions/15905127/overridden-methods-in-javadoc
    */
    @Override    
    public String getParameter(String name) {
	if(!isRunAsApplet) {
	    return appParams.get(name);
	}
	else {
	    if(appParams != null) {
		String value = appParams.get(name);
		if(value != null) {
		    return value;
		}
	    }
	    return super.getParameter(name);
	}
    }
    
    @Override
    public URL getDocumentBase() {
	if(!isRunAsApplet) { // launched as application
	    //System.err.println("*** docBaseURL: " + docBaseURL);
	    return this.docBaseURL;
	} else {
	    return super.getDocumentBase();
	}
    }
    
    @Override
    public void showStatus(String msg) {
	// Either firefox doesn't provide a status bar window for applets any more or webswing
	// doesn't provide a status window, so we don't see Applet.showStatus() output appear
	// in webswing-phind and in fact don't even see any Applet statusBar in webswing.
	// However, since we print useful and interesting information to the status bar,
	// we'll now always show and print to our manually added statusBar now
	// not only if(!isRunAsApplet) when we needed to manually create a statusBar.
	if(this.statusBar != null) {
	    this.statusBar.setText(msg);
	}
	if(isRunAsApplet) {
	    super.showStatus(msg);
	}
    }

    // Given a string xtraParams of key-value pairs separatod by pairSeparators,
    // parses out each (key, value) and puts them into the given map, allocating it if
    // necessary, and returns this map.
    Map parseXtraParams(String xtraParams, String kvSeparator, String kvPairSeparator, Map map) {
	
	// String.split() is preferred over Tokenizer but 2nd parameter behaves differently
	// than I expected.
	// https://docs.oracle.com/javase/6/docs/api/java/lang/String.html#split%28java.lang.String,%20int%29
	String[] param_list = xtraParams.split(kvPairSeparator, 0);// 0 means for all occurrences
	
	if(map == null) {
	    map = new HashMap<String,String>(param_list.length);
	}
	
	for(String key_val : param_list) {
	    String[] paramPair = key_val.split(kvSeparator, 2); // get first 2 strings, key and val
	    //System.err.println("key_val: " + key_val);
	    if(paramPair.length == 2) {
		String xtraParamsKey = paramPair[0];
		String xtraParamsVal = paramPair[1];
		// Let's remove any bookending quotes from value, this is necessary for some
		// values when run as a webswing applet
		if(xtraParamsVal.startsWith("\"") && xtraParamsVal.endsWith("\"")) {
		    xtraParamsVal = xtraParamsVal.substring(1, xtraParamsVal.length()-1);
		}

		if (verbosity_ >= 4) {
		    System.err.println("*** xtraParams key - val: " + xtraParamsKey + " - " + xtraParamsVal);
		}
		map.put(xtraParamsKey, xtraParamsVal);
	    }
	}

	return map;
    }
    
    public void init() 
    {	    
	if (verbosity_ >= 4) { 
	    System.err.println("Attempting to retrieve parameters...");
	}

	// This section is to help *webswing* *applet* use dynamic params in the config file.
	// Extract anything that's in xtraParams (key=value&k2=v2&... string) into
	// appParams, so all is ready to call getParameter and get expected params out by name
	// thereafter.
	// Basically, this section allows us to support any dynamic parameters getting added
	// by Javascript to the existing applet parameters defined in webswing.config.in
	// Here we unpack the xtraParams sent  and set them up as regular applet parameters, so
	// that the rest of the applet init() function can read them in as regular applet params.
	String xtraParams           = getParameter("xtraParams");
	if(xtraParams != null) {
	    // will optionally create appParams and add the key,value pairs in xtraParams into it
	    // after splitting key::value;;k2::v2 etc
	    appParams = parseXtraParams(xtraParams, "::", ";;", appParams);
	}

	
	// gets the parameters specified
	String showStatusBar        = getParameter("statusbar");
	if(statusBar == null) {
	    System.err.println("### Status bar code turned off. Ignoring statusbar settings.");
	} else {
	    if(showStatusBar == null || showStatusBar.equals("0")) {
		System.err.println("### Hiding status bar (default)");
		statusBar.setVisible(false);
	    }
	}
	String verbosity_param      = getParameter("verbosity");
	String is_java2_param       = getParameter("isJava2");
	String max_depth_param      = getParameter("maxDepth");
	String max_downloads_param  = getParameter("maxDownloads");
	String max_display_param    = getParameter("maxDisplay");
	String refresh_delay_param  = getParameter("refreshDelay");
	String image_type_param     = getParameter("imageType");
	String bgcolor_param        = getParameter("bgcolor");
	String caption_param        = getParameter("caption");
        String document_root        = getParameter("documentroot");
	String gwcgi                = getParameter("gwcgi");   // for GS2
	String baseurl              = getParameter("baseurl"); // for GS3, URL before /library

	String webswing              = getParameter("webswing");
	isWebswing = (webswing == null) ? false : webswing.equals("1");	
	
	String gsdlVersionStr = getParameter("gsdlversion");
	if(gsdlVersionStr != null) {
	    System.err.println("*** gsdl version: " + gsdlVersionStr);
	    this.gsdlversion = Integer.parseInt(gsdlVersionStr);
	}	

	// Check it isn't null
	if ((verbosity_param!=null) && (!verbosity_param.startsWith("_"))) {
	    verbosity_  = Integer.parseInt(verbosity_param);
	}
	else {
	    verbosity_ = 1;
	}


	if (verbosity_ >= 4) { 
	    System.err.println("Got parameters.");
	}

	if (caption_param != null && !caption_param.startsWith("_")) {
	    caption_ = caption_param;
	}
	else {
	    if (verbosity_ >= 4) { 
		System.err.println("No Caption: setting to a space.");
	    }
	    caption_ = " ";
	}

	if ((bgcolor_param != null) && (!bgcolor_param.startsWith("_"))) {
	    if (bgcolor_param.startsWith("#")) {
		bgcolor_ = Color.decode(bgcolor_param);   
	    }
	    else {
		String [] c = bgcolor_param.split(",");
		if (c.length == 3)
		    bgcolor_ = new Color(Integer.parseInt(c[0]), Integer.parseInt(c[1]), Integer.parseInt(c[2]));
	    }
	    if (verbosity_ >= 4){ 
		System.err.println("Set BG to be " + bgcolor_.toString());
	    }
	}
	else {
	    if (verbosity_ >= 4) { 
		System.err.println("No BG: setting to NZDL green.");
	    }	   
	    bgcolor_ = new Color(150, 193, 155);
	}

	if ((image_type_param != null) && (!image_type_param.startsWith("_"))) {
	    imageType_ = image_type_param;
	}

	if ((is_java2_param == null) || (is_java2_param.equals("auto")) || (is_java2_param.startsWith("_"))) {
	    String version = System.getProperty("java.version");
	    //version = version.substring(0, version.lastIndexOf("."));
	    version = version.substring(0, version.indexOf("."));
	    System.err.println("VERSION: " + version);
	    float ver = (new Float(version)).floatValue();
	    isJava2_ = (ver >= 1.2) ? true : false;
	}
	else {
	    isJava2_ = (is_java2_param.equals("true")) ? true : false;
	}

	if ((max_depth_param != null) && (!max_depth_param.startsWith("_"))) {
	    // System.err.println("maxDepth = " + max_depth_param);
	    maxDepth_ = Integer.parseInt(max_depth_param);
	}

	if ((max_downloads_param!=null) && (!max_downloads_param.startsWith("_"))) {
	    maxDownloads_ = Integer.parseInt(max_downloads_param);
            System.out.println(" maxDownloads " + maxDownloads_ );
	    maxDownloads_=50;
	}

	if ((max_display_param!=null) && (!max_display_param.startsWith("_"))) {
	    maxDisplay_ = Integer.parseInt(max_display_param);
              
	}

	if ((refresh_delay_param!=null) && (!refresh_delay_param.startsWith("_"))) {
	    refreshDelay_ = Integer.parseInt(refresh_delay_param);
	}

       
        if (document_root !=null){  // e.g. "/greenstone/web/images" for GS2
	    if(document_root.indexOf("/") == 0) {
		document_root = document_root.substring(1); // takes off first slash
	    }
	    if (document_root.indexOf("/") > 0 ){ // e.g we end up with "greenstone" for GS2, "greenstone3" for GS3
		document_root = document_root.substring(0, document_root.indexOf("/"));
	    }	    
	}

	String image_url  = getParameter("imageURL"); 
	String image_ignore  = getParameter("imageIgnorePrefix"); 
	String href_musthave  = getParameter("hrefMustHave"); 
	String image_mustnothave = getParameter("imageMustNotHave");

	// builds starting url when incorporated with Greenstone
	if (image_url==null)
	{
	    String collection_param  = getParameter("collection");
	    String classifier_param  = getParameter("classifier");

	    if(gsdlversion == 2) {
		// GS2 way
		String gwcgi_param  = gwcgi;
		gwcgi_param = tidy_URL(gwcgi_param, true); //true to append ? to URL
		image_url = gwcgi_param + "a=d";
		image_url += "&c=" + collection_param;
		image_url += "&cl=" + classifier_param;
	    } else {
		// Try GS3 way
		String library = getParameter("library");
		//String site = getParameter("sitename");
		
		// starting URL (image_url) might not be base_url. We need to store base_url separately
		baseURL = tidy_URL(baseurl, false);
		if(!baseURL.endsWith("/")) {
		    baseURL += "/";
		}
		
		// building up to baseURL/LIBNAME/collection/COLNAME/browse/
		image_url = baseURL + library + "/collection/" + collection_param + "/browse/";
		
		int index = classifier_param.indexOf(".");
		if(index == -1) {
		    image_url += classifier_param;
		} else {
		    
		    //"CL2#" + classifier_param;
		    //String superClassifier = classifier_param.substring(0, index);
		    //image_url = image_url + superClassifier + "#" + classifier_param;
		    
		    // I'm guessing we want something like
		    //http://localhost:8383/greenstone3/library/collection/smallbea/browse/CL2/3, when classifier_param is CL2.3
		    // to get the classifier page containing the images that are to be turned into a collage?
		    String classifierSuffix = classifier_param.replace(".", "/");
		    image_url = image_url + classifierSuffix;
		}
	    }
	}

	// This block was a nice idea had it worked, but it doesn't
	// work. It's here as sample syntax.  Handle getting called by
	// webswing JavaScript (but only if run in webswing mode):
	// when the user navigates away from the webswing app's web
	// page, do cleanup
	// https://www.webswing.org/docs/23.2/integrate/jslink.html#invoking-java-from-javascript
	// https://vaadin.com/directory/component/webswing-vaadin1
	if(isWebswing) {

	    //System.err.println("#### isWebswing, about to add listeners");
	    
	    WebswingUtil.getWebswingApi().addBrowserActionListener(new WebActionListener() {
		    //@Override
		    public void actionPerformed(WebActionEvent actionEvent) {

			System.err.println("***JavaScript to Java comms: actionPerformed called");
			//System.err.println("actionEvent = ");
			
			switch (actionEvent.getActionName()) {
			case "navigatingAway":
			    // do cleanup
			    System.err.println("@@@@ In Java collage addBrowserActionListener - on navigatingAway called");
			    GsdlCollageApplet.this.stopRunning();
			    break;
			case "testJavaCall":
			    System.err.println("@@@@ In GsdlCollageApplet's addBrowserActionListener - testJavaCall called");
			    WebswingUtil.getWebswingApi().sendActionEvent("javaToWebswingJSConsoleLog", "GsdlCollageApplet got TEST call !!", null);
			    //WebswingUtil.getWebswingApi().sendActionEvent("javaToWebswingJSConsoleLog", "GsdlCollageApplet - testjavaCall handler got your message", null);
			}			
		    }
		});
	}

	if(isWebswing && isRunAsApplet) {

	    // When webswing runs Collage in Applet mode, it's not
	    // shutdown the way we'd like on the javascript kill()
	    // command when the user navigates away from the collage
	    // page, as that only generates a Java WindowClosingEvent
	    // by default if no shutdown handlers registered. And a
	    // WindowClosingEvent only has any effect on applications,
	    // not applets. But we can write our own custom handler to
	    // mimic the applet shutdown sequence of stop() then
	    // destroy() see
	    // https://www.webswing.org/docs/20.2/integrate/api.html
	    // This code also written with the help of webswing's
	    // source code to know the methods in interface
	    // WebswingShutdownListener and the import statements
	    // required for these methods.
	    // After stop() and destroy(), we call System.exit(0) on
	    // top of usual applet behaviour to make sure we get rid
	    // of the applet "application" that webswing launched for
	    // us (it took the applet code and ran it, presumably like
	    // a java program, so a System.exit call may be warranted,
	    // whereas System.exit() isn't called on a regular applet).
	    WebswingUtil.getWebswingApi().addShutdownListener(new WebswingShutdownListener() {
		    // These are official webswing API comments for info
		    /**
		     * Invoked before Webswing requests application to exit.
		     * Do not execute long operations in this listener - listener execution will be interrupted if blocking for &gt; 3 seconds and the application will exit without delay.
		     * Connection to server is still open when this callback is triggered.
		     * This method can delay the shutdown. Calling {@link WebswingApi#resetInactivityTimeout()} within the delay period will cancel the shutdown sequence.
		     *
		     * This method is not called on the event dispatch thread.
		     *
		     * @param event Event contains the reason of this shutdown - either triggered from Admin console's rest interface or by inactivity
		     * @return number of seconds to delay the shutdown, returning 0 will cause shutdown without delay (even if {@link WebswingApi#resetInactivityTimeout()} has been called)
		     */
		    public int onBeforeShutdown(OnBeforeShutdownEvent event) {
			return 0; // seconds to delay before starting shutdown procedure
		    }
		    
		    /**
		     * Invoked when Webswing requests swing application to exit. 
		     * This method should cause this process to exit (not necessarily in the same thread).
		     * When this method is called, connection to server is already closed
		     *
		     * This method is not called on the event dispatch thread.
		     */
		    public void onShutdown() {
			// https://docs.oracle.com/javase%2F7%2Fdocs%2Fapi%2F%2F/java/applet/Applet.html#destroy()
			//   destroy(): "Called by the browser or
			//   applet viewer to inform this applet that
			//   it is being reclaimed and that it should
			//   destroy any resources that it has
			//   allocated. The stop method will always be
			//   called before destroy."
			// We're like the browser now and so we should
			// honour the above contract.
			if (verbosity_ >= 3) {
			    System.err.println("------- GsdlCollageApplet WebswingShutdownListener.onShutdown() - our custom behaviour");
			    
			    // This message is sent to JavaScript too late: webpage has already
			    // finished unloading and the JS webswingInstance can't receive msgs.
			    // if(isWebswing && isRunAsApplet) {
			    //    WebswingUtil.getWebswingApi().sendActionEvent("javaToWebswingJSConsoleLog", "GsdlCollageApplet running as webswing applet - quitting now", null);
			    // }
			}
			stop();
			destroy();
			// Even though we're an applet and applets
			// shouldn't system.exit(0) we're running our
			// applet version of the code through
			// webswing.  And this handler/webswing stands
			// in place of a browser and controls what
			// *ought* to happen if a browser doesn't take
			// care of the applet life cycle and we ought
			// to do it (here in the webswing handler).
			System.exit(0);
		    }
		});
	}
	
	    
	MediaTracker trk = new  MediaTracker(this);

	// creates a class to store the image and its associated url
	download_images_ = new DownloadImages( this, verbosity_,isJava2_ );
	// starts the download image thread with the starting url

        download_thread_ = new DownloadUrls(this, download_images_, 
					    image_url, href_musthave, image_mustnothave, 
					    image_ignore, imageType_,document_root,verbosity_,trk);
	// If GsdlCollageApplet is launched as a *webswing applet*, it
	// doesn't have dimensions, isn't showing nor is it even
	// displayable at this point in the code. Only when webswing
	// resizes the GsdlCollageApplet Component, does it have
	// dimensions and do we know for sure it is showing.
	if(isShowing() && isDisplayable()) { // case of: if(!(isRunAsApplet && isWebswing)) {
	    if (verbosity_ >= 4) {
		System.err.println("**** Creating display thread");
	    }
	    // starts the display image thread with the currently downloaded images
	    display_thread_ = new DisplayImages(this, download_images_,isJava2_, bgcolor_);
	} else {
	    if (verbosity_ >= 4) {
		System.err.println("**** GsdlCollageApplet.init() - display_thread_ not yet showing.");
		System.err.println("**** Will create and start display_thread_ later, in ComponentListener.resize()");
	    }
	}
	// The display_thread_ needs the component showing before it can be started up.
	// It was showing already by this point for the applet in non-webswing mode (when
	// using the appletviewer),
	// and I got it showing by this point when run as a cmdline or webswing application.
	// But when the applet is run in webswing mode, it's not yet showing
	// at this point! A componentListener allows us to handle componentShown events on
	// the JComponent of this JApplet. However, the applet in webswing mode doesn't trigger
	// that, it only triggers componentResized. So maybe webswing starts it off as 0 size?
	this.addComponentListener(this);

	
    }

    public void componentHidden(ComponentEvent e) {
	if (verbosity_ >= 4) {
	    System.err.println(e.getComponent().getClass().getName() + " --- Hidden");
	}
    }

    public void componentMoved(ComponentEvent e) {
	if (verbosity_ >= 4) {
	    System.err.println(e.getComponent().getClass().getName() + " --- Moved");
	}
    }

    public void componentResized(ComponentEvent e) {
	if(display_thread_ == null) {
	    if (verbosity_ >= 4) {
		System.err.println(e.getComponent().getClass().getName() + " --- Resized");
		System.err.println("Ready to instantiate display thread");
	    }
	    display_thread_ = new DisplayImages(this, download_images_,isJava2_, bgcolor_);
	    display_thread_.start();
	}
    }

    public void componentShown(ComponentEvent e) {	
	if(display_thread_ == null) {
	    if (verbosity_ >= 4) {
		System.err.println("@@@ " + e.getComponent().getClass().getName() + " --- Shown");
		System.err.println("GsdlCollageApplet ready to instantiate display thread");
	    }
	    display_thread_ = new DisplayImages(this, download_images_,isJava2_, bgcolor_);
	    display_thread_.start();
	}
    }

    public JLabel getStatusBar() {
	return this.statusBar;
    }

    // TODO: Do I need to follow
    // https://stackoverflow.com/questions/5861894/how-to-synchronize-or-lock-upon-variables-in-java
    // I just followed the Java API's recommendations about what to do
    // as replacement around Thread.stop() and Thread.destroy() being
    // deprecated (note: not Applet.stop() and
    // Applet.destroy(). https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#stop--
    public void stopRunning() {
	if(verbosity_ >= 3) {
	    System.err.println("**** GsdlCollageApplet.stopRunning() called");
	}
	
	stop_running = true;
	if(download_thread_ != null) {
	    download_thread_.stopRunning();
	}
	if(display_thread_ != null) {
	    display_thread_.stopRunning();
	}

	// The Display Thread?
	//if(!Thread.currentThread().isInterrupted()) {
	//    Thread.currentThread().interrupt();
	//}
    }
    
    public boolean isStopping() {
	return stop_running;
    }

    // Applet's mouseDown() is deprecated, so we implement mousePressed
    protected class CollageMouseAdapter extends MouseAdapter {
 
    /** Goes to the url associated with the image that is clicked on screen<br>
     *  Displays the url containing the image in a new window */
    //public boolean mouseDown(Event event, int x, int y)
    public void mousePressed(MouseEvent e)
    {
	int x = e.getX();
	int y = e.getY();
	
	// determines which image was clicked on
	CollageImage cimage = GsdlCollageApplet.this.display_thread_.clickedOnImage(x,y);
	// if they were clicking on an image (as opposed to background)
	if (cimage != null)
	{
	    System.err.println("Click on image: from url = " + cimage.from_url_);
	    try {
		// displays the associated url in a new window
                URL from_url = null;		
		
		java.util.regex.Pattern p = java.util.regex.Pattern.compile("cl=CL\\d(\\.\\d)*\\s");
		java.util.regex.Matcher m = p.matcher(cimage.from_url_.trim()+" ");

                if (m.find() ){
		    from_url = cimage.url_;
		}
		else{
		    from_url =  new URL(cimage.from_url_  + "#" + cimage.name_);
		}

		if(isRunAsApplet) {
		    GsdlCollageApplet.this.getAppletContext().showDocument(from_url,"gsdlDoc");
		} else if(isWebswing) {
		    WebswingUtil.getWebswingApi().sendActionEvent("openURL",
					  from_url.toString() + " - " +"gsdlDoc",
					  null); // window name is gsdlDoc		    
		} else {
		    System.err.println("@@@GsdlCollageApplet.CollageMouseAdapter.mousePressed()"
		       + "\n\topening url " + from_url + "\n\t"
		       + "for non-applets and non-webswing applications is not yet implemented.");
		}
	    } 
	    catch (MalformedURLException ex) { 
		ex.printStackTrace();
	    }

	}
    }
    }
    
    /** Start threads for downloading and displaying images */
    public void start() 
    {
	download_thread_.start();	

	if(display_thread_ != null) {
	//if(!(isRunAsApplet && isWebswing)) { // then display_thread_ is instantiated at this point
	    // start it
	    display_thread_.start();
	}

         paint =  new Thread(new Runnable(){
                public void run() {
		    try {

			Thread curr_thread = Thread.currentThread();
			
			while (curr_thread == paint) {
			    
			    repaint();
			    
			    Thread.sleep(2000);
			    if(download_thread_.wasUnableToDownload()) {
				repaint();
				Thread.sleep(2000);
				String msg = "@@@ GsdlCollageApplet is unable to download. (No internet?) Stopping.";
				System.err.println(msg);
				if(isWebswing) {
				    WebswingUtil.getWebswingApi().sendActionEvent("javaToWebswingJSConsoleLog", msg, null);
				}			      
				if(!isRunAsApplet) {
				    System.exit(-1);
				} else {
				    stopRunning();
				    return;				   
				}
			    }
			    curr_thread = Thread.currentThread();
			}

	    
		    } catch (Exception e) {
			
		    }
		}


	    });

	    paint.start();
   
    }

    /** Stops threads for downloading and displaying images */    
    public void stop() 
    {
	if(verbosity_ >= 3) {
	    System.err.println("\n\n@@@@ GsdlCollageApplet.stop() called: stopping threads...");
	}
	//download_thread_.stop();
	//display_thread_.stop();
	stopRunning();
	// TODO: maybe we want to unset the interrupt variable on the threads to be able to
	// restart/resume on start() getting called? No: because we system.exit() in the
	// webswing shutdown handler we implemented.	
    }

    /** Destroys threads for downloading and displaying images.
     * Java API method specification for destroy(): "Called by the
     * browser or applet viewer to inform this applet that it is being
     * reclaimed and that it should destroy any resources that it has
     * allocated. The stop method will always be called before destroy."
     */
    public void destroy() 
    {
	if(verbosity_ >= 3) {
	    System.err.println("@@@@ GsdlCollageApplet.destroy() called");
	}
	download_thread_ = null;
	
	display_thread_ = null;
	
    }


    /** Repaints the applet */
    public void update(Graphics g) 
    { 
	// System.err.println("Update called");
	paint(g); 
    } 
    
    /** Repaints the applet using the paint method of the thread that is
     * currently displaying images */
    public void paint(Graphics g) {
	
	if (display_thread_!=null)
	    {	
	      
	    	//display_thread_.display_collage();
	    	  display_thread_.paint(g);
	    }
	else
	    {
		System.err.println("Applet still trying to paint!!");
	    }
	
    }
  
    
    /** Ensures a URL address (as string) has a protocol, host, and file.
     *
     * If the URL is a CGI script URL, it should be tidied up so that it is
     * appropriate to tag attrib=value pairs on the end.  This means it
     * must either end with a "?" or (if it contains a question-mark
     * internally) end with a "&". */
    String tidy_URL(String address, boolean isCGI) {

	// System.err.println("tidy URL: " + address);
	
	// make sure the URL has protocol, host, and file
	if (address.startsWith("http")) {
	    // the address has all the necessary components
	} else if (address.startsWith("/")) {
	    // there is not protocol and host
	    URL document = getDocumentBase();
	    String port = "";
	    if (document.getPort()!=-1) {
		port = ":" + document.getPort();
	    }
	    address = "http://" + document.getHost() + port  + address;
	} else {
	    // this URL is relative to the directory the document is in
	    URL document = getDocumentBase();
	    String directory = document.getFile();
	    int end = directory.lastIndexOf('/');
	    String port = "";
	    if (document.getPort()!=-1) {
		port = ":" + document.getPort();
	    }
	    directory = directory.substring(0,end + 1);
	    address = "http://" + document.getHost() + port + directory + address;

	}

	// if the URL is a cgi script, make sure it has a "?" in ti,
	// and that it ends with a "?" or "&"
	if (isCGI) {
	    if (address.indexOf((int) '?') == -1) { 
		address = address + "?";
	    } else if (!address.endsWith("?")) {
		address = address + "&";
	    }
	}

	return address;
    }


    public static void printUsage() {
	System.err.println("Params needed include: --gsdlversion <3/2> [--statusbar 0/1] [--baseurl <GS3 DL baseURL>/--gwcgi <library.cgi URL>] --library l --collection c --classifier cl --imageType \".jpg%.png\" --imageMustNotHave \"interfaces/\" [--hrefMustHave \"LIBRARYNAME/sites/SITENAME/collect/COLNAME\"] -documentroot greenstone3 [--webswing <1/0>] [--verbosity <v>] --maxDepth 500 --maxDisplay 25 --refreshDelay 1500 --isJava2 auto --bgcolor \"#96c29a\" [--xtraParams <key1=value1&key2=value2&...]");
	System.err.println("To run as webswing application, additionally pass in --webswing 1");
    }
    
    // To also be able to run this applet as an application, need a main method
    /**
     * After building the GS3 Multimedia collection, try running this Application as:
     * (Large Beatles must be built, with CL2 as Browse tab.)
     java -cp ./web/applet/GsdlCollageApplet.jar:./web/WEB-INF/lib/log4j-1.2.8.jar:./web/WEB-INF/classes:./data/ext/webswing/api/webswing-api.jar org.greenstone.applet.GsdlCollageApplet.GsdlCollageApplet --statusbar 0 --baseurl "http://localhost:8383/greenstone3/" --library library --collection largebea --gsdlversion 3 --hrefMustHave "library/collection/largebea/browse/CL2" --documentroot greenstone3 --verbosity 3 --imageType ".jpg%.png" --imageMustNotHave "interfaces/" --classifier "CL2.1" --maxDepth 500 --maxDisplay 25 --refreshDelay 1500 --isJava2 auto --bgcolor "#96c29a" --width 645 --height 780
     *
     * OLD WAY (small bea no longer has a collage classifier):
     java -cp ./web/applet/GsdlCollageApplet.jar:./web/WEB-INF/lib/log4j-1.2.8.jar:./web/WEB-INF/classes:./data/ext/webswing/api/webswing-api.jar org.greenstone.applet.GsdlCollageApplet.GsdlCollageApplet --statusbar 0 --baseurl "http://localhost:8383/greenstone3/" --library library --collection smallbea --gsdlversion 3 --hrefMustHave "library/collection/smallbea/browse/CL3" --documentroot greenstone3 --verbosity 3 --imageType ".jpg%.png" --imageMustNotHave "interfaces/" --classifier "CL3.1" --maxDepth 500 --maxDisplay 25 --refreshDelay 1500 --isJava2 auto --bgcolor "#96c29a" --width 645 --height 780
     *
     * If wanting to run this with the appletviewer, need to ensure a copy of webswing-api.jar exists in web/applet
     * as specified in classifier.xsl's applet element's tag (because of the webswing import statements I think).
     * Then ensure the GS3 server is running and you've built your collection with the collage classifier
     * and run the appletviewer with the collage classifier's URL embedded in quotes, e.g:
     *    appletviewer "http://localhost:8383/greenstone3/library/collection/smallbea/browse/CL3"
     * Note that appletviewer is being deprecated in Java and may not be available in future Java downloads.
     * Sadly, the appletviewer runs in a sort of sandbox that doesn't allow downloading from URLs, so the applet window
     * appears and shows an infinite message it's trying to download, but fails to do so because of security exceptions.
     * But at least it can confirm for us the applet is being loaded in and that the applet lifecycle has started.
     */
    public static void main(String[] args) {
	if(args.length == 0) {
	    printUsage();
	} else if(args[0].equals("--help") || args[0].equals("-h")) {
	    printUsage();
	} else {
	    JFrame frame = new JFrame("Collage Applet as Application");
	    GsdlCollageApplet collageApp = new GsdlCollageApplet();
	    frame.getContentPane().add(collageApp, BorderLayout.CENTER);
	    //frame.setSize(X_DIM, Y_DIM); //Y_DIM+EXTRA_HEIGHT);
	    frame.setVisible(true);
	    // https://stackoverflow.com/questions/19433358/difference-between-dispose-and-exit-on-close-in-java
	    // default: https://docs.oracle.com/javase/8/docs/api/javax/swing/JFrame.html#EXIT_ON_CLOSE
	    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Running as an application. But don't do EXIT_ON_CLOSE in Applet mode!

	    // prepare to run the collage applet as an application
	    collageApp.applicationPreInit(args);
	    // It's now figured out the dimensions based on anything specified, so set frame size
	    frame.setSize(collageApp.X_DIM(), collageApp.Y_DIM()); //Y_DIM+EXTRA_HEIGHT);
	    
	    // status bar code. Not ideal, but had to add it here to get relative dimensions right
	    JLabel statusBar = collageApp.getStatusBar();
	    if(statusBar != null) {
		collageApp.setLayout(new BorderLayout());
		
		frame.add(statusBar, BorderLayout.SOUTH);
		Dimension d = statusBar.getSize();
		d.height = EXTRA_HEIGHT;
		d.width = collageApp.getWidth();
		statusBar.setPreferredSize(d);
		//statusBar.setPreferredSize(new Dimension(collageApp.getWidth(), EXTRA_HEIGHT));
	    }
	    
	    // Run it at last: manually calling (J)Applet methods init() and start()
	    collageApp.init();

	    collageApp.showStatus("Collage application running");
	    collageApp.start();
	    
	    // When we terminate the application, need to manually call the applet method stop()
	    // except we're already doing the same things here.
	    //https://docs.oracle.com/javase/tutorial/uiswing/events/windowlistener.html#windowfocuslistener	    
	    frame.addWindowListener(new WindowAdapter() {
		public void windowClosing(WindowEvent e) {
		    if(collageApp.isWebswing) {			
			WebswingUtil.getWebswingApi().sendActionEvent("javaToWebswingJSConsoleLog", "GsdlCollageApplet run as application - quitting now", null);
		    }
		    collageApp.showStatus("Stopping threads");
		    System.err.println("\n\n@@@ Closing collage Application: stopping threads...");
		    collageApp.stopRunning();
		    
		    //collageApp.stop();
		    //collageApp.destroy();
		}
	    });
	    
	}
    }
}
