import java.awt.event.*;
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.net.*;

/** BrowserDisplay - Indeholder metoder som gør det muligt at styre
  *	displayet
  * @author Søren S Munk (u001614)
  */
public class BrowserDisplay extends JComponent {
  
    /* FØLGENDE VARIABLE ÆNDRES IKKE UNDER GENTEGNING AF ET DOKUMENT
     * dim        angiver bredde og højde på dette display
     * g          er den pen som der til enhver tid tegnes med
     * model      er den Browsermodel der indeholder information
     *            om det nuværende dokument og links 
     * currentScroll angiver hvor langt (i multipla af displayets højde)
     *            der er scrollet frem i det nuværende dokument
     */
    private Dimension dim;
    private Graphics g;
    private BrowserModel model;
    private int currentScroll;
	//Forud instillet margen
	private final int MARGEN = 15;

    /* DISSE VARIABLE OPDATERES UNDER GENTEGNING AF ET DOKUMENT
     * linkMode    angiver om der lige nu udskrives et hyperlink på lærredet
     * linkUrl     er i så fald den tilhørende url
     * lineHeight  højden af den nuværende skrifttype
     * leftMargin  x-koordinat for linjestart
     * rightMargin x-koordinat for linjeslut
     * x           x-coordinat for (venstre kant af) "cursor"-position 
     * y           y-koordinat for (bundkant af) "cursor"-position
     * imageHeight angiver højden af det hidtil højeste billede på linjen
     * fontStack   tidligere anvendte skrifttyper som skal bruges igen senere
     */
    private boolean linkMode;
    private URL linkUrl;
    private int lineHeight;
    private int leftMargin;
    private int rightMargin;
    private int x,y;
    private int imageHeight;
    private Stack fontStack;

    /** BrowserDisplay konstruktion
     *  @param width,height - dimensionerne på displayet
     *  @param m - den model, der har information om dokumenter
	 */
    BrowserDisplay(int width, int height, BrowserModel m) {
		this.dim = new Dimension(width,height);
		this.setBackground(Color.white);
		model = m;
    }

    /** scrollUp() bevirker at displayet scroller en displayside
     * tilbage
	 */
    public void scrollUp() {
		if (currentScroll > 0) { currentScroll -= dim.height; }
    }

    /** scrollDown() bevirker at displayet scroller en displayside
     * frem
	 */
    public void scrollDown() {
		if ( overFlow() ) currentScroll += dim.height;
    }

    /** scrollTop() bevirker at displayet scroller til tops i
     * dokumentet
	 */
    public void scrollTop() { currentScroll = 0; }
  
    /** getPreferredSize returnerer dimensioner på displayet
	 *	@return - diminsionen på displayet
	 */
    public Dimension getPreferredSize() { return dim; }

    /** paint (gen)tegner Displayet ved først at normalstille værdierne
     *  i alle de relevante variable og tilsidst bede det nuværende
     *  dokument kalde tegnemetoderne
	 */
    public void paint(Graphics g) { 
	this.g = g;
	this.g.clipRect(0,0,dim.width,dim.height);
	this.g.translate(0,-currentScroll); // sikrer at det rigige udsnit
	                                    // af lærredet bliver vist
	this.linkMode = false;
	this.fontStack = new Stack();
	this.g.setFont(new Font("TimesRoman",Font.PLAIN,12));
	this.lineHeight = g.getFontMetrics().getHeight();
	this.imageHeight = 0;
	this.leftMargin = MARGEN;
	this.rightMargin = dim.width-MARGEN;
	this.x = leftMargin;
	this.y = lineHeight;
	model.clearLinks();
	model.generateDocument(this);
    }

	/** overFlow checker om y positionen overskrider side højden
	 *	ved henholdsvis indsætelse af billede eller tekst
	 *	@return - sandhedsværdi om y overskrider højden
	 */
    private boolean overFlow() {
	return ( y > currentScroll + dim.height ||
		 y - lineHeight + imageHeight > currentScroll + dim.height);
    }

	/** updateLineHeight opdaterer liniehøjden udfra den aktuelle 
	 * 	font */
    private void updateLineHeight() {
	lineHeight = g.getFontMetrics().getHeight();
    }
    
    /** space udskriver et mellemrum */
    public void space() {
		if ( x != leftMargin ) { word(" ");	}
    }
    
    /** word udskriver et ord
     *  @param s - en tegnfølge som skal udskrives
     *            (følgen brydes ikke) */
    public void word(String s) {
		int w,h;
		w = g.getFontMetrics().stringWidth(s);
		h = lineHeight;

		if ( w + x > rightMargin) { newLine(); }
		if ( linkMode )
		   { model.addLink( x,
				    y-h-currentScroll,
				    x+w,
				    y-currentScroll,
				    linkUrl );
		   }
		g.drawString(s, x , y);
		x += w; 
    }
    
    /** rule laver en skillelinje på tværs af lærredet */
    public void rule() { 
		newLine();
		g.drawLine( leftMargin, y-(lineHeight/2), rightMargin, y-(lineHeight/2) );
		paragraph();
    }

    /** blackDot laver en markering af et listepunkt (sort cirkel e.l.)
	 *	på lærredet */
    public void blackDot() {
		int r = (lineHeight / 2);  //radius bliver 50% af LH.
		g.drawOval(leftMargin, y - (lineHeight / 2)-(r/2), r, r);
		g.fillOval(leftMargin, y - (lineHeight / 2)-(r/2), r, r);
		x += r;
		space();
    }

    /** whiteDot laver en markering af et listepunkt (cirkelring e.l.)
	 *	på lærredet */
    public void whiteDot() {
		int r = (lineHeight / 2);  //radius bliver 50% af LH.
		g.drawOval(leftMargin, y - (lineHeight / 2)-(r/2), r, r);
		x += r;
		space();
    }

    /** newLine laver et linjeskifte på lærredet, under den
	 *	forudsætning at der ikke er indsat et billede i linien
	 */
    public void newLine() {
		x  = leftMargin;
		if ( imageHeight != 0 ) { y += imageHeight; }
		else { y += lineHeight; }
		imageHeight = 0;
    }

    /** paragraph laver et afsnitsskifte (tom linje) på lærredet */
    public void paragraph() {
		if ( x > leftMargin ) { newLine(); }
		newLine();
    }
    
    /** image henter et billede og indsætter det på lærredet
     * @param url - en url for det billede som skal indsættes
     *              (kan kun håndtere gif-billeder) */
    public void image(URL url) {
	int w,h;
	Image i;
	try {
	    i = getToolkit().getImage(url);
	    MediaTracker m = new MediaTracker(this);
	    m.addImage(i,0);
	    m.waitForAll(); 
	    w = i.getWidth(this);
	    h = i.getHeight(this);

	    if ( x + w > rightMargin ) newLine();
 	    if ( linkMode )
	       { model.addLink( x,
				y - currentScroll - lineHeight,
				x + w,
				y + h - currentScroll - lineHeight,
				linkUrl );
	       }
	    g.drawImage(i, x, y - lineHeight+2, this);
	    if (h > imageHeight) 
		imageHeight = h;
	    x += w;
      	} catch (Exception e)  { word("**missing image**"); }
    }
    
    /** normalMode markerer at en evt. hyperlink er slut */
    public void normalMode() { 
		g.setColor(Color.black);
      	linkMode = false; 
	}
    
    /** anchorMode markerer start af et hyperlink
     *  @param url - url på den hyperlink som starter her
     */
    public void anchorMode(URL url) {
	if (model.seenURL(url)) g.setColor(Color.red);
	else { g.setColor(Color.blue); }
	linkMode = true;
	linkUrl = url; 
    }
 
    /** incmargin rykker venstre margin en tand ind */
    public void incMargin() {
	leftMargin += g.getFontMetrics().stringWidth("   ");
    }
    
    /** decmargin rykker venstre margin en tand ud */
    public void decMargin() {
		if ( leftMargin - g.getFontMetrics().stringWidth("   ") <= MARGEN )
		    { leftMargin = MARGEN; }
		else
		    { leftMargin -= g.getFontMetrics().stringWidth("   "); }
    }

    /** popFont skifter til tidligere anvendt skrifttype ifølge stakken */
    public void popFont() {
		g.setFont( (Font) fontStack.pop() );
		updateLineHeight();
    }

    /** pushHeaderFont vælger en stor skrift */
    public void pushHeaderFont() {
		fontStack.push(g.getFont());
		g.setFont(new Font(g.getFont().getName(),
							g.getFont().getStyle(),20));
		updateLineHeight();
    }

    /** pushBoldFont vælger en fed skrift */
    public void pushBoldFont() {
		fontStack.push( g.getFont() );
		int newStyle;
		if ( g.getFont().isItalic() )
			 { newStyle = Font.BOLD | Font.ITALIC; }
		else { newStyle = Font.BOLD; }
		
		g.setFont( new Font(g.getFont().getName(),
					newStyle, g.getFont().getSize() ) );
		updateLineHeight();
    }

    /** pushItalicFont vælger en kursiv skrift */
    public void pushItalicFont() {
		fontStack.push( g.getFont() );
		int newStyle;
		if ( g.getFont().isBold() )
			 { newStyle = Font.BOLD | Font.ITALIC; } 
		else { newStyle = Font.ITALIC; }
		
		g.setFont( new Font(g.getFont().getName(),
					newStyle, g.getFont().getSize() ) );
		updateLineHeight();
    }
}