package org.greenstone.applet.GsdlCollageApplet;

import java.awt.*;
import java.awt.geom.*;
import java.io.*;
import java.net.*;
import java.awt.image.*;


/** 
 *  @author Katrina Edgar
 *  @author David Bainbridge
 *
 *  Data Structure to store an image once it is displayed in the applet.
 *  This structure remembers the images graphical representation, source url,
 *  name, width, height, position on the screen and whether or not it has
 *  previously been displayed. It provides methods to set the dimensions and
 *  position of the image. It also provides access methods to the translated
 *  x and y co-ordinates of the image */
public class CollageImage {

     /** Decrements the alpha value between adjacent rows on the edge of an image<br>
     *  Used to fade the edges faster than the rest of the image <br>
     *  Only used when java2 parameter is true and advanced image processing
     *  techniques may be applied */
    static final int FADEVALUE = 3;

    /** Defines the shades of white used to colour the backgrounds of white images <br>
     *  These colours are used because when several white images appear in the collage
     *  simultaneously it is difficult to distinguish their edges<br>
     *  Only used when java2 parameter is true and advanced image processing
     *  techniques may be applied */
    int colorMask [] = {0xF8FFFF, 0xFFF8FF, 0xFFFFF8, 0xF8F8FF, 0xFFF8F8, 0xF8FFF8, 0xFAFAFA};

      
    /** Defines the number of shades of white used to colour the backgrounds of white images <br>
     *  These colours are used because when several white images appear in the collage
     *  simultaneously it is difficult to distinguish their edges<br>
     *  Only used when java2 parameter is true and advanced image processing
     *  techniques may be applied */
    static final int NO_OF_COLORS = 7;

    /** Will fire a removal operation when 4 images in the collage overlap a single area */
    static final int NO_IMAGES_OF_OVERLAP = 4;


    /** Source url of the image */
    String from_url_    = null;
    /** Name of the image */
    String name_        = null;
    /** Width of the image */
    int image_x_dim_    = 0;
    /** Height of the image */
    int image_y_dim_    = 0;
    /** Indicates whether or not the image has been drawn on the applet previously */
    boolean fresh       = true;
    
    URL url_ = null;      
 
    /** Reflects the translation of the image from the origin to its position on the screen */
    AffineTransform af_ = null;
    /** Left x co-ordinate */
    protected int xl_ = 0;
    /** Top y co-ordinate */
    protected int yt_ = 0;
    /** Right x co-ordinate */
    protected int xr_ = 0;
    /** Bottom y co-ordinate */
    protected int yb_ = 0;
 
     /** Refers to applet */
    GsdlCollageApplet app_ = null;

    boolean isJava2_;
 
    Image image_; 
   
    /** Constructs an CollageImage from the three specified parameters
     *
     *  @param image The graphical representation of the image
     *  @param from_url The source url for the image
     *  @param name The file name of the image */
    public CollageImage(GsdlCollageApplet app, boolean isJava2, DownloadImages.ImageUrlTriple iutriple)
    {
         
         image_ = iutriple.image();
	 from_url_ = iutriple.urlString();
	 name_ = iutriple.name();
	 url_ =  iutriple.url();
         
         isJava2_=isJava2;
         app_=app;  

	 process();
    }

    /** Sets the translation for the image from the origin
     *  And regenerate the image as the scaled version
     *
     *  @param af The AffineTransform translation that has been calculated */
    public void setAffineTransform(MyAffineTransform af, boolean is_java2_)
    {	
	image_x_dim_ = (int) (image_x_dim_ * af.scaleX);
	image_y_dim_ = (int) (image_y_dim_ * af.scaleY);
        
	image_ = image_.getScaledInstance(image_x_dim_, image_y_dim_, Image.SCALE_DEFAULT);
	
	if (is_java2_) {
	    af_ = new AffineTransform();
	    af_.translate(af.translateX, af.translateY);
	    af_.scale(1.0, 1.0);
	
	    calculate_rect();
	}
	else {
	    double trans_x = af.translateX;
	    double trans_y = af.translateY;
	    
	    xl_ = Math.round((float)trans_x);
	    xr_ = Math.round((float)(trans_x + image_x_dim_)) -1;
	    yt_ = Math.round((float)trans_y);
	    yb_ = Math.round((float)(trans_y + image_y_dim_)) -1; 
	}

    }

    public void expand () {
	
	// still expands a little too fast... how to fix this?
	magnify(new Rectangle((int) (xl_ + (image_x_dim_ * 0.000001)),
			      (int) (yt_ + (image_y_dim_ * 0.000001)),
			      (int) (image_x_dim_ * 0.999998),
			      (int) (image_y_dim_ * 0.999998)));
	
    }
    
    public void magnify(Rectangle border) {
	
	double magX = image_x_dim_/(double)border.width;
	double magY = image_y_dim_/(double)border.height;
	int x = border.x + border.width/2;
	int y = border.y + border.height/2;
	paintImage(x,y,magX, magY);
    }
    
    public void paintImage(int magCenterX, int magCenterY, double magX, double magY){
	
	try {
	    //Point2D mgp = null;
	    //mgp = af_.inverseTransform((new Point(magCenterX, magCenterY)),(Point)mgp);
	    //double x = (mgp.getX()*magX)-mgp.getX();
	    //double y = (mgp.getY()*magY)-mgp.getY();
	    //scale(-x,-y, magX, magY);
	}catch (Exception e) {System.out.println(e); }
    }
    
    public void scale(double magOffsetX, double magOffsetY, double magX, double magY){
	
	af_.translate(magOffsetX,magOffsetY);
	af_.scale(magX, magY);
    }
    
    /** Calculates the new image co-ordinates after translation has occurred */
    protected void calculate_rect()
    {
	double trans_x = af_.getTranslateX();
	double trans_y = af_.getTranslateY();

	xl_ = Math.round((float)trans_x);
	xr_ = Math.round((float)(trans_x + image_x_dim_)) -1;
	yt_ = Math.round((float)trans_y);
	yb_ = Math.round((float)(trans_y + image_y_dim_)) -1; 
    }

    /** Determines whether a given co-ordinate is inside this image
     *
     *  @param x The x co-ordinate
     *  @param y The y co-ordinate */
    public boolean inside(int x, int y)
    {
	return ((x>=xl_) && (x<=xr_)) && ((y>=yt_) && (y<=yb_));
    }

    /** Gets the width of the translated image */
    public double getX() {
	return af_.getTranslateX();
    }

    /** Gets the height of the translated image */
    public double getY() {
	return af_.getTranslateY();
    }
    
  
    /** Fades and colours the image on a pixel-by-pixel basis <br>
     *  First it grabs the pixels of the entire image and stores them in a 2D array.
     *  Then a bound is calculated to indicate the point from which edge
     *  fading should occur. The position of the bound will change in proportion
     *  to the age of the image so that older images have a larger faded edge.
     *  The area within the bounds, that forms the center of the image, is processed
     *  first and faded by a standard alpha value. Then each edge of the image
     *  is processed separately, creating a gradient fade from the true edge to the
     *  position of the bound. <br>
     *  The faded pixel array is then turned into a new image a returned.
     *
     *  @param img the image that requires processing
     *  @param x the x co-ordinate of the image
     *  @param y the y co-ordinate of the image
     *  @param w the width of the image
     *  @param h the height of the image
     *  @param p the position of the image in the applet (indicates age)
     *  @param fresh whether the image is being processed for the first time */
    public int[]  handlepixels(int p) {
       
	int h = image_y_dim_;
	int w = image_x_dim_;       
	
	// declare an array to hold the pixels
	int[] pixels = new int[w * h];
	// get the pixels of the image into the pixels array
	PixelGrabber pg = new PixelGrabber(image_, 0, 0, w, h, pixels, 0, w);
	
	try {
	    pg.grabPixels();
	} catch (InterruptedException e) {
	    System.err.println("interrupted waiting for pixels!");
	}
	
	    
	// check for any failures
	if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
	    System.err.println("image fetch aborted or errored");
		
	}
	
   
	// calculates the bound from which fading should begin
	double bound = p * 0.01;
	if (w > h)
	    bound *= w;
	else
	    bound *= h;

	int upperboundheight = h - (int) bound;
	int upperboundwidth = w - (int) bound;
	int lowerbound = (int) bound;

      	// loop through every pixel in the picture and handle it
	for (int j = lowerbound; j < upperboundheight; j++) {
	    for (int i = lowerbound; i < upperboundwidth; i++) {
		// width and height: x+i y+j
		pixels[j * w + i] = handlesinglepixel(pixels[j * w + i], p, fresh, false, 255);
	    }
	}

	int fade = 0;
	int fader = 0;
	int corealpha = (pixels[(h/2) * w + (w/2)] >> 24) & 0xff;
	
	// top edge
	for (int n = lowerbound; n >= 0; n--) {
	    fader = corealpha - fade;
	    if (fader < 0)
		fader = 0;
	    
	    for (int m = 0; m < w; m++){
		int index = n* w + m;
		if (index <pixels.length){
		    pixels[index] = handlesinglepixel(pixels[index], p, fresh, true, fader);
		    
		}
	    }   
	    
	    fade+= FADEVALUE;
	}

	// bottom edge
	fade = 0;
	for (int n = upperboundheight; n < h; n++) {
	    fader = corealpha - fade;
	    if (fader < 0)
		fader = 0;
	    for (int m = 0; m < w; m++){
		int index = n * w + m;
		if (index <pixels.length){
		    pixels[index] = handlesinglepixel(pixels[index], p, fresh, true, fader);
		    
		}
	    }
	    
	    fade += FADEVALUE;
	}

	// left edge
	fade = 0;
	for (int n = lowerbound; n >= 0; n--) {
	    fader = corealpha - fade;
	    if (fader < 0)
		fader = 0;
	    for (int m = 0; m < h; m++) {
		if ( m < lowerbound && n > m);
		else if ( m > upperboundheight && n > (h - m));
		else {
		    int index = m * w + n;
                    if (index <pixels.length){
			pixels[index] = handlesinglepixel(pixels[index], p, fresh, true, fader);
	
		    }
		}
                   
	    }
	    fade += FADEVALUE;    
	}
	
	// right edge
	fade = 0;
	for (int n = upperboundwidth; n < w; n++) {
	    fader = corealpha - fade;
	    if (fader < 0)
		fader = 0;
	    for (int m = 0; m < h; m++) {
		if ( m < lowerbound && (w - n) > m);
		else if ( m > upperboundheight && (w - n) > (h - m));
		else {
		    int index = m * w + n;
                    if (index <pixels.length){
			pixels[index] = handlesinglepixel(pixels[index], p, fresh, true, fader);
	
		    }
		}
		    
	    }
	    
	    fade += FADEVALUE;
	}  
	
       		// set the pixels of the whole picture to the pixels array
       	pg.setPixels(0, 0, w, h, pg.getColorModel(), pixels, 0, w);

	image_ =  app_.createImage(new MemoryImageSource(w, h, pixels, 0, w));

	return pixels;
    }

    
    /** Adjusts the colour and alpha value of an individual pixel <br>
     *  If the image is being drawn for the first time, the RGB values are
     *  extracted. If the pixel is close to white (RGB > 250) then an offwhite
     *  colour is applied to this pixel.
     *  This is done because when several white images appear in the collage
     *  simultaneously it is difficult to distinguish their edges. <br>
     *  This function also fades the alpha value of the pixel as the image ages.
     *  The alpha value is more heavily decremented as pixels get closer to the
     *  edge of the image
     *
     *  @param pixel the pixel to manipulate
     *  @param p the position of the image in collage (representative of age)
     *  @param fresh indicates whether or not the image is being drawn for the first time
     *  @param edge indicates whether or not this pixel is near the edge of the image
     *  @param fade the amount by which to fade this pixel
     *  @return the adjusted pixel as an int */
    public int handlesinglepixel(int pixel, int p, boolean fresh, boolean edge, int fade) {
       	int newpixel = 0;

        //changes the colour of the picture, only when first drawn
	//and only if the pixel is close to white
	if (fresh) {
	    
	    int red   = (pixel >> 16) & 0xff;
	    int green = (pixel >>  8) & 0xff;
	    int blue  = (pixel      ) & 0xff;
	    
	    if (red >= 250 && green >= 250 && blue >= 250) {
		
		int c = colorMask[((int) (p%NO_OF_COLORS))];

		red = (c >> 16) & 0xff;
		green = (c >> 8) & 0xff;
		blue  = (c) & 0xff;
	    }

	    newpixel |= (red << 16) & 0x00ff0000;
	    newpixel |= (green << 8) & 0x0000ff00;
	    newpixel |= blue & 0x000000ff;
	}

	else {
	    newpixel |= pixel & 0x00ffffff;
	}

	int alpha = (pixel >> 24) & 0xff;

	if (edge) {
	    // fade the edges more...
	    alpha = fade;
	}
	else if (alpha > 10 && !fresh) {
	    alpha -= 10;
	}
	
	newpixel |= (alpha << 24) & 0xff000000;
	
	return (newpixel);
    }


  

    /** Resets the alpha channel of an image so that it appears solid
     *
     *  @param img the image to restore
     *  @param x the x co-ordinate of the image
     *  @param y the y co-ordinate of the image
     *  @param w the width of the image
     *  @param h the height of the image */
    public void restoreAlpha() {
       	int h = image_y_dim_;
	int w = image_x_dim_;       

	// declare an array to hold the pixels
	int[] pixels = new int[w * h];
	// get the pixels of the image into the pixels array
	PixelGrabber pg = new PixelGrabber(image_, 0, 0, w, h, pixels, 0, w);
	try {
	    pg.grabPixels();
	    
		
	} catch (InterruptedException e) {
	    System.err.println("interrupted waiting for pixels!");
	}
	
	
	// check for any failures
	if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
	    System.err.println("image fetch aborted or errored");
	}
	
	
	    
	// loop through every pixel in the picture and handle it
	for (int j = 0; j < h; j++) {
	    for (int i = 0; i < w; i++) {
		pixels[j * w + i] |= (255 << 24) & 0xff000000;
	    }
	}   
 
	// set the pixels of the whole picture to the pixels array
	pg.setPixels(0, 0, w, h, pg.getColorModel(), pixels, 0, w);

	image_ =  app_.createImage(new MemoryImageSource(w, h, pixels, 0, w));       
	
    }
    
    /** Checks whether an image has faded to the point where it must be removed
     *  from the collage.
     *
     *  @param img the image to restore
     *  @param x the x co-ordinate of the image
     *  @param y the y co-ordinate of the image
     *  @param w the width of the image
     *  @param h the height of the image */
    public boolean checkFaded (int[] pixels) {
	int h = image_y_dim_;
	int w = image_x_dim_;

	// get the alpha value of the middle pixel of the image
	int corealpha = (pixels[(h/2) * w + (w/2)] >> 24) & 0xff;
	
	if (corealpha < 50)
	    return true;
	
	return false;
	
    }

    public boolean isValid(){
	image_x_dim_ = image_.getWidth(app_);
	image_y_dim_ = image_.getHeight(app_);

	return  (image_x_dim_) >0 && ( image_x_dim_ >0);
	
    }

    
    public void process(){

	// images x and y dimensions
	image_x_dim_ = image_.getWidth(app_);
	image_y_dim_ = image_.getHeight(app_);
	 if (( image_x_dim_ >0) && ( image_x_dim_ >0))
	     {		
		 // places and sizes the image
		 MyAffineTransform af = new MyAffineTransform(image_x_dim_,image_y_dim_);
		 
		 // sets location & size of collage image
		 setAffineTransform(af, isJava2_);
		 fresh = false;
		 if (isJava2_ ) {      	 
		     restoreAlpha(); 
		 }
		 	
	     }
  
	
    }

}	

