// Copyright 1996, Marimba Inc. All Rights Reserved.


// @(#)Pattern.java, 1.22, 09/25/96





package marimba.gui;





import java.awt.*;


import java.net.*;


import java.util.*;





import java.awt.image.ImageObserver;





import marimba.util.ThreadUtil;





/**


 * Efficiently render a pattern over an area.


 *


 * @author	Arthur van Hoff


 * @version 	1.22, 09/25/96


 */


public class Pattern implements ImageObserver, Runnable {


    static Hashtable patHash = new Hashtable();


    static final int PATWIDTH = 300;


    static final int PATHEIGHT = 200;





    // This is a hack. We need a frame to create an


    // offscreen image. Unfortunately creating a frame


    // causes the focus to be lost on the PC, that is why


    // we keep the old one around in a static variable.


    static final Frame frm = new Frame();


    static {


	frm.addNotify();


    }


    


    Color col;


    URL url;


    Image patImg;


    int patWidth;


    int patHeight;


    boolean initialized;


    boolean done;


    Vector observers;





    /**


     * Create a new pattern.


     */


    Pattern(Color col, URL url) {


	this.col = col;


	this.url = url;


    }





    /**


     * Initialize this pattern.


     */


    void init() {


	initialized = true;


	Image img = ImageCache.get(url);


	if (Toolkit.getDefaultToolkit().prepareImage(img, -1, -1, this)) {


	    initPattern(img);


	}


    }





    /**


      * Initialize the pattern image from the original. If the original


      * pattern is small it is replicated a number of times to fill the


      * patImage.


      */


    synchronized void initPattern(Image img) {


	int w = img.getWidth(this);


	int h = img.getHeight(this);


	if ((w < 0) || (h < 0)) {


	    return;


	}


	


	if (patImg == null) {


	    patWidth = ((PATWIDTH + w-1) / w) * w;


	    patHeight = ((PATHEIGHT + h-1) / h) * h;





	    patImg = frm.createImage(patWidth, patHeight);





	    Graphics g = patImg.getGraphics();


	    try {


		g.setColor(col);


		g.fillRect(0, 0, patWidth, patHeight);


	    } finally {


		g.dispose();


	    }


	}


	


	Graphics g = patImg.getGraphics();


	try {


	    // draw the initial image


	    done = g.drawImage(img, 0, 0, this);





	    // replicate horizontally


	    for (int x = w ; x < patWidth ; x += x) {


		g.copyArea(0, 0, x, h, x, 0);


	    }





	    // replicate vertically


	    for (int y = h ; y < patHeight ; y += y) {


		g.copyArea(0, 0, patWidth, y, 0, y);


	    }


	} finally {


	    g.dispose();


	}





	if (done) {


	    // Notify anyone who is waiting for this background...


	    notifyAll();





	    if (observers != null) {


		ThreadUtil.forkSystem("Pattern Notification", this);


	    }


	}


    }





    /**


     * Notify observers.


     */


    public void run() {


	for (int i = 0, n = observers.size() ; i < n ; i++) {


	    ImageObserver observer = (ImageObserver)observers.elementAt(i);


	    observer.imageUpdate(patImg, ALLBITS, 0, 0, patWidth, patHeight);


	}


	observers = null;


    }





    /**


     * More of the original image has arrived. Repaint the pattern.


     */


    public synchronized boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) {


	if ((flags & ALLBITS) != 0) {


	    initPattern(img);


	    return false;


	}


	if ((flags & ERROR) != 0) {


	    done = true;


	    notifyAll();


	    return false;


	}


	return true;


    }





    /**


     * Fill a rectangle with the replicated pattern. 


     */


    public void fillRect(Graphics g, int x, int y, int w, int h, ImageObserver observer) {


	fillRect(g, x, y, w, h, x, y, w, h, observer);


    }





    /**


     * Fill part of a rectangle with the replicated pattern. The area of the


     * rectangle is defined by x,y,w,h, the part that needs filling is defined


     * by cx,cy,cw,ch.


     */


    public synchronized void fillRect(Graphics g, int x, int y, int w, int h,


				      int cx, int cy, int cw, int ch, ImageObserver observer) {


	if (cx < x) {


	    cw -= x - cx;


	    cx = x;


	}


	if (cx + cw > x + w) {


	    cw = (x + w) - cx;


	}


	if (cw <= 0) {


	    return;


	}





	if (cy < y) {


	    ch -= y - cy;


	    cy = y;


	}


	if (cy + ch > y + h) {


	    ch = (y + h) - cy;


	}


	if (ch < 0) {


	    return;


	}





	if (!initialized) {


	    init();


	}





	if (patImg == null) {


	    g.setColor(col);


	    g.fillRect(cx, cy, cw, ch);


	} else {


	    Graphics gc = g.create(cx, cy, cw, ch);


	    try {


		x += ((cx - x) / patWidth) * patWidth;


		y += ((cy - y) / patHeight) * patHeight;





		for (int py = y - cy ; py < ch ; py += patHeight) {


		    for (int px = x - cx ; px < cw ; px += patWidth) {


			gc.drawImage(patImg, px, py, null);


		    }


		}


	    } finally {


		gc.dispose();


	    }


	}





	if ((observer != null) && !done) {


	    if (observers == null) {


		observers = new Vector();


		observers.addElement(observer);


	    } else if (observers.indexOf(observer) < 0) {


		observers.addElement(observer);


	    }


	}


    }





    /**


     * Wait for this background to arrive.


     */


    public synchronized void waitFor() {


	if (!initialized) {


	    init();


	}


	try {


	    while (!done) {


		wait();


	    }


	} catch (InterruptedException e) {


	    return;


	}


    }





    /**


     * Hashcode


     */


    public int hashCode() {


	return col.hashCode() ^ url.hashCode();


    }





    /**


     * Equality.


     */


    public boolean equals(Object obj) {


	if (obj instanceof Pattern) {


	    Pattern p = (Pattern)obj;


	    return col.equals(p.col) && url.equals(p.url);


	}


	return false;


    }





    /**


     * Debugging.


     */


    public String toString() {


	return getClass().getName() + "[url=" + url.toExternalForm() + "," + patWidth +"x" + patHeight + (done ? ",done" : "") + "]";


    }





    /**


     * Get a pattern from the cache.


     */


    public synchronized static Pattern getPattern(Color col, URL url) {


	Pattern p = new Pattern(col, url);


	Pattern pat = (Pattern)patHash.get(p);


	if (pat != null) {


	    return pat;


	}


	patHash.put(p, p);


	return p;


    }





    /**


     * Check if a URL is the prefix of another URL.


     */


    static boolean prefixURL(URL prefix, URL child) {


	return prefix.getProtocol().equals(child.getProtocol()) &&


	    prefix.getHost().equals(child.getHost()) &&


	    (prefix.getPort() == child.getPort()) &&


	    child.getFile().startsWith(prefix.getFile());


    }





    /**


     * Flush images from the cache. Only images that are


     * relative to the given URL are flushed.


     */


    public synchronized static void flush(URL url) {


	Vector v = null;


	for (Enumeration e = patHash.keys() ; e.hasMoreElements() ;) {


	    Pattern p = (Pattern)e.nextElement();


	    if (prefixURL(url, p.url)) {


		if (v == null) {


		    v = new Vector();


		}


		//System.out.println("FLUSHING: " + p.url.toExternalForm());


		v.addElement(p);


	    }


	}


	if (v != null) {


	    for (Enumeration e = v.elements() ; e.hasMoreElements() ;) {


		patHash.remove(e.nextElement());


	    }


	}


    }


}


