package org.greenstone.applet.GsdlCollageApplet;

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.util.*;

/** 
 *  @author Katrina Edgar
 *  @author David Bainbridge
 *
 *  Retrieves images from the download thread, determines an appropriate position
 *  for thes  images onscreen ensuring maximum whitespace coverage and minimal overlap
 *  when possible. Draws images using advanced image processing techniques when applicable,
 *  such as fading the edges of an image and discolouring backgrounds. Also re-uses images
 *  from those that have previously been displayed, or are aging onscreen, in order to
 *  to create movement when no new images have been downloaded. */
public class DisplayImages extends Thread {
    
    /** Will fire a removal operation when 4 images in the collage overlap a single area */
    static final int NO_IMAGES_OF_OVERLAP = 4;

    private boolean stop_running = false;
    
    /** To determine if there are several images overlapped, can remove the
     *  layers underneath to save time in repainting */
    boolean overlap = false;

    /** Refers to download thread */
    DownloadImages download_images_ = null;

    /** Refers to applet */
    GsdlCollageApplet app_ = null;

    /** Applets width on screen */
    public static int app_x_dim_  = 0;

    /** Applets height on screen */
    public static int app_y_dim_  = 0;
    
    /** Background colour of the applet screen */
    protected Color bgcolor_        = null;

    /** 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 is_java2_     = false;

  
    /** Holds images currently on display in the applet */
    protected Vector inuse_    = null;
    
     /** Holds images ready to be used */
    protected Vector inuse_ready_    = null;

    /** Holds previously displayed images for re-use */
    protected Vector notinuse_ = null;
   
    protected Image    screen_buffer_    = null;
    protected Graphics screen_graphic_   = null;
    protected Image    finished_buffer_  = null;
    protected Graphics finished_graphic_ = null;
 
    Thread next_frame_ ;

    String dots[] = new String[]{" ",".","..","...","....",".....","......"};
    int dots_nums=0;
    boolean start_paint = false;
    /** Used to determine where to place new images on the applet to optimize
     *  whitespace coverage <br>
     *  A co-ordinate in the applet has a value of zero if it is whitespace,
     *  one if it is covered by one image, two if it is covered by two images,
     *  and so on. New images will be placed such that they cover the largest
     *  area of whitespace (zero valued co-ordinates) */
    public static int [] [] used_space = null;
    
  
    /**
     *  Starts the display images thread. Initialises variables using parameters and
     *  static values. Creates the blank applet screen, initialises the used space
     *  2D array to indicate that there are no images onscreen
     *
     *  @param app the applet to display the images on
     *  @param download_images thread from which to retrieve downloaded images
     *  @param is_java2 whether advanced image processing techniques should be used
     *  @param bgcolor the background colour of the applet screen */
    public DisplayImages(GsdlCollageApplet app, DownloadImages download_images,
			 boolean is_java2, Color bgcolor)
    {
	super("DisplayImages");

	// parameters saved locally
	app_             = app;
	is_java2_        = is_java2;
	bgcolor_         = bgcolor;

	inuse_    = new Vector();
      	notinuse_ = new Vector();
        inuse_ready_ = new Vector();
	download_images_ = download_images;
	// sets width and height
	if (is_java2_)
	{
	    //app_x_dim_  = app_.getWidth();
	    //app_y_dim_  = app_.getHeight();
	    app_x_dim_  = (app_.getWidth() <= 0) ? app_.X_DIM() : app_.getWidth();
	    app_y_dim_  = (app_.getHeight() <= 0) ? app_.Y_DIM() : app_.getHeight();

	    System.err.println("@@@ xDim: " + app_x_dim_);
	    System.err.println("@@@ yDim: " + app_y_dim_);     
	}
	else
	{
	    app_x_dim_  = app_.X_DIM();
	    app_y_dim_  = app_.Y_DIM();
	}

	// creates initial screen
	screen_buffer_ = app_.createImage(app_x_dim_, app_y_dim_);
	if(screen_buffer_ == null) {
	    System.err.println("### why is screen_buffer_ null?");
	    if(java.awt.GraphicsEnvironment.isHeadless()) {
		System.err.println("### It's running headless so screen_buffer_ is null");
	    }
	    if(!app_.isDisplayable()) {
		System.err.println("### Collage app is not displayable, that's why screen_buffer_ is null");
	    }
	}
	
	screen_graphic_ = screen_buffer_.getGraphics();
	screen_graphic_.setColor(bgcolor_);
	screen_graphic_.fillRect(0,0,app_x_dim_,app_y_dim_);
	
	finished_buffer_  = app_.createImage(app_x_dim_, app_y_dim_);
	finished_graphic_ = finished_buffer_.getGraphics();
    
	// initialises used space array to indicate an empty screen
	used_space = new int [app_x_dim_][app_y_dim_];	
	for (int n=0; n < app_x_dim_; n++)
	    for (int m=0; m < app_y_dim_; m++)
		used_space[n][m] = 0;

        next_frame_ =  new Thread(new Runnable(){
                public void run() {
		  
		    Thread curr_thread = Thread.currentThread();
		    
		    while (!isStopping() && curr_thread == next_frame_) {
			try {	    
			    Runtime rt = Runtime.getRuntime();
			    //System.out.println("total: "+rt.totalMemory());
			    //System.out.println("free:  "+rt.freeMemory());
			    rt.gc();             
               		    //System.out.println("**************next frame...");
			    next_frame();
			    Thread.sleep(app_. refreshDelay_);
			   
			    curr_thread = Thread.currentThread();
			} catch (Exception e) {
                            e.printStackTrace(); 
			    break;
			}
		    }
		    	    
		    if(isStopping() && DisplayImages.this.app_.verbosity() >= 3) {
			System.err.println("*** DisplayImages inner thread next_frame has been told to stop.");
		    }
		}


	    });

	 next_frame_.start();


    }

    /** Determines what the user has clicked on in the applet, either an image or whitespace
     *  @param x the x co-ordinate of the mouse
     *  @param y the y co-ordinate of the mouse
     *  @return the image that the user has clicked on or null if the user has clicked whitespace */
   public CollageImage clickedOnImage(int x, int y)
    {
	if (app_.verbosity() > 1)
	{
	    System.err.print("Checking for clicked on image: ("+x+","+y+")");
	}

	
	// checks from last image down, as last images place will be at the top of the collage
	for (int i= inuse_.size() - 1; i >= 0; i--)
	{
	    CollageImage collage_image = (CollageImage)inuse_.elementAt(i);
	    if (collage_image.inside(x,y))
	    {
		return collage_image;
	    }
	}
	// no image at these co-ordinates, just whitespace
	return null;
    }

    /** Generates the images onscreen.
     *  Loops through the vector of currently displayed images and draws each one in turn.
     *  If the image is being drawn for the first time, its position will be determined
     *  and its alpha channel set as solid prior to drawing. <br>
     *  Drawing of the image will depend on whether or not java2 is enabled. If so then
     *  the image will be processed so that fading occurs with age, so that the edges 
     *  fade faster than the rest of the image and so that white images are slightly 
     *  discoloured to allow distinction. If java2 is not enabled, new images will simply
     *  be pasted on top of existing images without any special effects occuring<br>
     *  This function also deletes old images. If java2 is enabled then images are deleted
     *  once they have faded to a suitably low alpha value. If java2 is not enabled then
     *  images will be removed once there are greater than 25 images onscreen, to ensure
     *  the applet cannot be overloaded. */
    public  void display_collage() 
    {
	
	// if there is nothing new to add
	if (inuse_.size()==0) return;
	
	// otherwise need to re-generate images
	if (app_.verbosity() >= 2)
	    {
		//system.err.println("Regenerating images");
	    }
	
	// get each image currently onscreen inturn
	for (int i=0; i<inuse_.size();)
	    {
		CollageImage collage_image = (CollageImage)inuse_.elementAt(i);
  
                if (!collage_image.isValid()){
		    continue;
		}	    

                int[] pixels = null;
		// the first time the image is painted, determine its position and size
		if (collage_image.fresh == true)
		    {
			collage_image.process();
		    }
		
		// advanced image processing may be used
		if (is_java2_)
		    {
			// conducts fading and colouring of image
			pixels = collage_image.handlepixels(inuse_.size()-i); 
		    }
		
		if (collage_image.fresh) {
		    collage_image.fresh = false;
		}
		
		
		// if we remove when images have faded too much
		if (is_java2_) {
		    if (collage_image.checkFaded(pixels)|| overlap)
			{
			    overlap = false;
			    
			    CollageImage removed_image = (CollageImage)inuse_.elementAt(i);
			    //System.out.println(removed_image.name_ + "is removed");
			    inuse_.remove(i);
			    // indicates space for this image is no longer used
			    for (int n = removed_image.xl_; n < (removed_image.xl_ + removed_image.image_x_dim_); n++)
				for (int m = removed_image.yt_; m < (removed_image.yt_ + removed_image.image_y_dim_); m++)
				    if(used_space[n][m] != 0) {
					used_space[n][m]--;
					if (used_space[n][m] > NO_IMAGES_OF_OVERLAP)
					    overlap = true;
				    }
			    
			    // sets the image as fresh and adds it to the vector of images previously seen
			    removed_image.fresh = true;
			    notinuse_.addElement(removed_image);
			}
		    else{
			i++;
		    }
		}
		else 
		    if (inuse_.size() > 25 || overlap) {
			overlap = false;
			
			CollageImage removed_image = (CollageImage)inuse_.elementAt(i);
			inuse_.remove(i);
			
			// indicates space for this image is no longer used
			for (int n = removed_image.xl_; n < (removed_image.xl_ + removed_image.image_x_dim_); n++)
			    for (int m = removed_image.yt_; m < (removed_image.yt_ + removed_image.image_y_dim_); m++)
				if(used_space[n][m] != 0) {
				    used_space[n][m]--;
				if (used_space[n][m] > NO_IMAGES_OF_OVERLAP)
				    overlap = true;
				}
			
			// sets the image as fresh and adds it to the vector of images previously seen
			removed_image.fresh = true;
			notinuse_.addElement(removed_image);
		    }
		    else{
			i++;
		    }    
		
	    }
    }
    
  
    /** Alters the images currently on display in the applet <br>
     *  Changes are prioritised in the following way:
     *  1. If there are images that have been downloaded and are yet to be displayed 
     *  they will be added to the list of images in use. <br>
     *  2. Otherwise if there are images that have previously been displayed but
     *  are not currently on the applet screen, re-use one of these images. <br>
     *  3. Otherwise if there are any images on screen at all, take the oldest and
     *  repaint it as the newest. <br>
     *  Then the collage is asked to display and repaint with the new image set. */

     public  void next_frame()
    {
	double random = Math.random();
	
     
	 if (inuse_ready_.size() > 0){
	    int pos = (int) ((inuse_ready_.size() - 1) * random);
	    CollageImage collage_image = (CollageImage) inuse_ready_.remove(pos);
	    inuse_.addElement(collage_image);
 	}
	else if (notinuse_.size()>0)
		{
		    int position = (int) ((notinuse_.size() - 1) * random);
		    
		    CollageImage collage_image = (CollageImage)notinuse_.elementAt(position);
		    notinuse_.remove(position);
		    inuse_.addElement(collage_image);
		    
		    if (app_.verbosity()>3)
		    {
			System.err.println("Re-using an image not on screen");
		    }
		}
	    else{
		if (app_.verbosity()>=4)
		    System.err.println("No images in download area");
	    }

	display_collage();
	
    }

    // some other thread can call this method to tell this thread to stop running
    public void stopRunning() {
	stop_running = true;
	if (app_.verbosity()>=3) {
	    System.err.println("**** DisplayImages.stopRunning() called");
	}
    }
  
    public boolean isStopping() {
	return stop_running;
    }

    /** Paints the applet on the screen */
    public void paint(Graphics g)
    {
	if(isStopping()) {
	    return;
	}
	screen_graphic_.setColor(bgcolor_);
	screen_graphic_.fillRect(0,0,app_x_dim_,app_y_dim_);

	// get each image currently onscreen inturn
	
	for (int i=0; i < inuse_.size() && !isStopping(); i++)
	    {
          
            CollageImage collage_image =null;
           try{
		   collage_image = (CollageImage)inuse_.elementAt(i);
		   start_paint = true;
		}
            catch(Exception e){
		  System.out.println("exception in paint");					
              break;
	     }
	   // don't paint new images yet
		if (collage_image.fresh) {
		  if (app_.verbosity()>=1) {
			System.err.println("doing nothing...  "+ collage_image.name_ +" is fresh.");
		   }
		}

		// advanced image processing may be used
		else if (is_java2_) {
		  
			//the alphacomposite controls the blending of these two images.
			Graphics2D sg2d = (Graphics2D)screen_graphic_;
			sg2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
			sg2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
					      RenderingHints.VALUE_ANTIALIAS_ON); 
			collage_image.expand();
			sg2d.drawImage(collage_image.image_, collage_image.af_, app_);
		   }
		// simple image collage
		else
		    {			// add the image to the collage
			
			screen_graphic_.drawImage(collage_image.image_, 
						  collage_image.xl_, collage_image.yt_,
						  collage_image.xr_, collage_image.yb_,
						  0,0,collage_image.image_x_dim_-1,
						  collage_image.image_y_dim_-1,  app_);
	 	    }
	
	    }

	if(isStopping()) {
	    return;
	}
        
	finished_graphic_.drawImage(screen_buffer_,0,0,app_);
	

	if (finished_buffer_ != null)
	    {
		g.drawImage(finished_buffer_, 0, 0, app_);
		if (!start_paint){
		    g.setColor(Color.white);
		    g.setFont(new Font("font",Font.PLAIN+Font.BOLD,30));

		    if(app_.download_thread_.wasUnableToDownload()) {
			//System.err.println("Display loop");
			g.drawString("Can't download.", app_x_dim_/5-20,app_y_dim_/2-20);
			g.drawString("Stopping.", app_x_dim_/5-20,app_y_dim_/2+20);
			// GsdlCollageApplet.start() will handle rest of termination of threads
		    } else {
			g.drawString("Downloading" + dots[(dots_nums++)%dots.length],app_x_dim_/3-20,app_y_dim_/2-2);
		    }
		}
	    }
	

    }

    /** Runs display thread by repeatedly calling the next frame,
     *  waiting for a specified delay period between calls */
    public void run() {
	try {
	    
	    Thread curr_thread = Thread.currentThread();
	    
	    while (!isStopping() && curr_thread == this) {
		if (inuse_ready_.size() >= 1){
		    Thread.sleep(1000);		    
		    continue;
		}
		//System.err.println("#### DisplayImages.run(): moving to next image");
		CollageImage collage_image = download_images_.getCollageImage();
 	
		if (collage_image !=null){
		    inuse_ready_.addElement(collage_image);	
 		}
		curr_thread = Thread.currentThread();
	    }
	        
	} catch (Exception e) {
	   e.printStackTrace();
           System.out.println("Display images thread is interrupted");
	}
	
	if(isStopping() && app_.verbosity() >= 3) {
	    System.err.println("*** DisplayImages thread has been told to stop.");
	}
	System.out.println("DisplayImages thread stopped");
          
    }

}
