package javax.microedition.lcdui;

import lejos.nxt.LCD;


/**
 * Preliminary Graphics class for LCD Screen
 * @author Brian Bagnall
 *
 */
public class Graphics {
	/** drawArc and fillArc accuracy parameter */
	private static final int ARC_ACC = 5;

	/* Public color definitions */
	public static final int BLACK = 1;
	public static final int WHITE = 0;

	/* Public line stroke definitions */
	public static final int SOLID 	= 0;
	public static final int DOTTED 	= 2;

	private int rgbColor = BLACK;
	private int strokeStyle = SOLID;
	
	private Font font = new Font();

	public Graphics() {}
	
	public int getWidth() {
		return LCD.SCREEN_WIDTH;
	}
	
	public int getHeight() {
		return LCD.SCREEN_HEIGHT;
	}
	
	public int getCenteredX(String str) {
		return (LCD.DISPLAY_CHAR_WIDTH - str.length())*LCD.CELL_WIDTH / 2;
	}
	
	public Font getFont()
	{
		return font;
	}
	
	public void setColor(int rgb)
	{
		rgbColor = rgb;
	}
	
	public int getColor()
	{
		return rgbColor;
	}
	
	/**
	* Using rgbColor as argument even though global, because when this
	* setPixel() method is used later it will need color argument
	*/
	public void setPixel(int rgbColor, int x, int y) {
		LCD.setPixel(rgbColor, x, y);
	}

	public void drawLine(int x0, int y0, int x1, int y1) {
		drawLine(x0, y0, x1, y1, strokeStyle);
	}
	
	private void drawLine(int x0, int y0, int x1, int y1, int style) {
		// Uses Bresenham's line algorithm
		int dy = y1 - y0;
		int dx = x1 - x0;
		int stepx, stepy;
		boolean skip = false;

		if (dy < 0) { dy = -dy;  stepy = -1; } else { stepy = 1; }
		if (dx < 0) { dx = -dx;  stepx = -1; } else { stepx = 1; }
		if (style == SOLID)
		{
			// Special case horizontal and vertical lines
			if (dy == 0 || dx == 0)
			{
				LCD.bitBlt((stepx == 1 ? x0 : x1), (stepy == 1 ? y0 : y1),
						    (dx == 0 ? 1 : dx+1), (dy == 0 ? 1 : dy+1), (rgbColor == BLACK ? LCD.ROP_SET : LCD.ROP_CLEAR));
				return;
			}
		}
		dy <<= 1; // dy is now 2*dy
		dx <<= 1; // dx is now 2*dx

		setPixel(rgbColor,x0, y0);
		if (dx > dy) {
			int fraction = dy - (dx >> 1);  // same as 2*dy - dx
			while (x0 != x1) {
				if (fraction >= 0) {
					y0 += stepy;
					fraction -= dx; // same as fraction -= 2*dx
				}
				x0 += stepx;
				fraction += dy; // same as fraction -= 2*dy
				if ((style == SOLID) || !skip)
					setPixel(rgbColor, x0, y0);
				skip = !skip;
			}
    	} else {
			int fraction = dx - (dy >> 1);
			while (y0 != y1) {
				if (fraction >= 0) {
					x0 += stepx;
					fraction -= dy;
				}
				y0 += stepy;
				fraction += dx;
				if ((style == SOLID) || !skip)
					setPixel(rgbColor, x0, y0);
				skip = !skip;
			}
		}
	}

	public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
		drawArc(x, y, width, height, startAngle, arcAngle, strokeStyle, false);
	}
	
	public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
		// drawArc is for now only SOLID 
		drawArc(x, y, width, height, startAngle, arcAngle, SOLID, true);
	}

	private void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle, 
			int style, boolean fill) {
		// Scale up width and height to create more accurate ellipse form
		int xscale = (width < height) ? ARC_ACC : ((ARC_ACC * width + (width >> 1)) / height);
		int yscale = (width < height) ? ((ARC_ACC * height + (height >> 1)) / width) : ARC_ACC;
		
		// Calculate x, y center and radius from upper left corner
		int x0 = x + (width >> 1);
		int y0 = y + (height >> 1);
		int radius = (width < height) ? (width >> 1) : (height >> 1);
		
		// Check and set start and end angle
		int endAngle = startAngle + arcAngle;
		while (endAngle < 0) endAngle = endAngle + 360;
		while (endAngle > 360) endAngle = endAngle - 360;
		if(arcAngle < 0) { // Switches start and end
			int temp = startAngle;
			startAngle = endAngle;
			endAngle = (temp > 0) ? temp : 360;
		}
		
		// Initialize scaled up Bresenham's circle algorithm
		int f = (1 - ARC_ACC * radius);
		int ddF_x = 0;
		int ddF_y = -2 * ARC_ACC * radius;
		int xc = 0;
		int yc = ARC_ACC * radius;
		int dotskip = 0;
		while (xc < yc) {
			if (f >= 0) { 
				yc--;
				ddF_y += 2;
				f += ddF_y;
			}
		    
			xc++;
		    ddF_x += 2;
		    f += ddF_x + 1;
		    
		    // Skip points for dotted version
		    dotskip = (dotskip + 1) % (2 * ARC_ACC);
		    if ((style == DOTTED) && !fill && (dotskip < ((2 * ARC_ACC) - 1))) continue;

		    // Scale down again
		    int xxp = (xc * xscale + (xscale >> 1)) / (ARC_ACC * ARC_ACC);
		    int xyp = (xc * yscale + (yscale >> 1)) / (ARC_ACC * ARC_ACC);
		    int yyp = (yc * yscale + (yscale >> 1)) / (ARC_ACC * ARC_ACC);
		    int yxp = (yc * xscale + (xscale >> 1)) / (ARC_ACC * ARC_ACC);
		    
		    // Calculate angle for partly circles / ellipses
		    // NOTE: Below, (float) should not be needed. Not sure why Math.round() only accepts float.
		    int tp = (int) Math.round((float) Math.toDegrees(Math.atan2(yc, xc)));
		    if (fill) {
		    	/* TODO: Optimize more by drawing horizontal lines */
		    	if (((90 - tp) >= startAngle) && ((90 - tp) <= endAngle))
		    		drawLine(x0, y0, x0 + yxp, y0 - xyp, style); // 0   - 45 degrees
		    	if ((tp >= startAngle) && (tp <= endAngle))
		    		drawLine(x0, y0, x0 + xxp, y0 - yyp, style); // 45  - 90 degrees
		    	if (((180 - tp) >= startAngle) && ((180 - tp) <= endAngle))
		    		drawLine(x0, y0, x0 - xxp, y0 - yyp, style); // 90  - 135 degrees
		    	if (((180 - (90 - tp)) >= startAngle) && ((180 - (90 - tp)) <= endAngle))
		    		drawLine(x0, y0, x0 - yxp, y0 - xyp, style); // 135 - 180 degrees
		    	if (((270 - tp) >= startAngle) && ((270 - tp) <= endAngle))
		    		drawLine(x0, y0, x0 - yxp, y0 + xyp, style); // 180 - 225 degrees
		    	if (((270 - (90 - tp)) >= startAngle) && ((270 - (90 - tp)) <= endAngle))
		    		drawLine(x0, y0, x0 - xxp, y0 + yyp, style); // 225 - 270 degrees
		    	if (((360 - tp) >= startAngle) && ((360 - tp) <= endAngle))
		    		drawLine(x0, y0, x0 + xxp, y0 + yyp, style); // 270 - 315 degrees
		    	if (((360 - (90 - tp)) >= startAngle) && ((360 - (90 - tp)) <= endAngle))
		    		drawLine(x0, y0, x0 + yxp, y0 + xyp, style); // 315 - 360 degrees
		    } else {
		    	if (((90 - tp) >= startAngle) && ((90 - tp) <= endAngle))
		    		setPixel(rgbColor, x0 + yxp, y0 - xyp); // 0   - 45 degrees
		    	if ((tp >= startAngle) && (tp <= endAngle))
		    		setPixel(rgbColor, x0 + xxp, y0 - yyp); // 45  - 90 degrees
		    	if (((180 - tp) >= startAngle) && ((180 - tp) <= endAngle))
		    		setPixel(rgbColor, x0 - xxp, y0 - yyp); // 90  - 135 degrees
		    	if (((180 - (90 - tp)) >= startAngle) && ((180 - (90 - tp)) <= endAngle))
		    		setPixel(rgbColor, x0 - yxp, y0 - xyp); // 135 - 180 degrees
		    	if (((270 - tp) >= startAngle) && ((270 - tp) <= endAngle))
		    		setPixel(rgbColor, x0 - yxp, y0 + xyp); // 180 - 225 degrees
		    	if (((270 - (90 - tp)) >= startAngle) && ((270 - (90 - tp)) <= endAngle))
		    		setPixel(rgbColor, x0 - xxp, y0 + yyp); // 225 - 270 degrees
		    	if (((360 - tp) >= startAngle) && ((360 - tp) <= endAngle))
		    		setPixel(rgbColor, x0 + xxp, y0 + yyp); // 270 - 315 degrees
		    	if (((360 - (90 - tp)) >= startAngle) && ((360 - (90 - tp)) <= endAngle))
		    		setPixel(rgbColor, x0 + yxp, y0 + xyp); // 315 - 360 degrees
		    }
		}
	}

	public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {

		int xc = x + (width/2);
		int yc = y + (height/2);
		int a = arcWidth/2;
		int b = arcHeight/2;

		int translateX = (width/2) - (arcWidth/2);
		int translateY = (height/2) - (arcHeight/2);

		// Draw 4 sides:
		int xDiff = arcWidth/2;
		int yDiff = arcHeight/2;
		drawLine(x, y+yDiff, x, height-yDiff);
		drawLine(width, y+yDiff, width, height-yDiff);
		drawLine(x+xDiff, y, width-xDiff, y);
		drawLine(x+xDiff, height, width-xDiff, height);


		/* e(x,y) = b^2*x^2 + a^2*y^2 - a^2*b^2 */
		int xxx = 0, yyy = b;
		int a2 = a*a, b2 = b*b;
		int crit1 = -(a2/4 + a%2 + b2);
		int crit2 = -(b2/4 + b%2 + a2);
		int crit3 = -(b2/4 + b%2);
		int t = -a2*yyy; /* e(xxx+1/2,y-1/2) - (a^2+b^2)/4 */
		int dxt = 2*b2*xxx, dyt = -2*a2*yyy;
		int d2xt = 2*b2, d2yt = 2*a2;

		while (yyy>=0 && xxx<=a) {
			setPixel(BLACK, xc+xxx + translateX, yc+yyy + translateY); // Q4
			if (xxx!=0 || yyy!=0)
				setPixel(BLACK, xc-xxx - translateX, yc-yyy - translateY); // Q2
			if (xxx!=0 && yyy!=0) {
				setPixel(BLACK, xc+xxx + translateX, yc-yyy - translateY); // Q1
				setPixel(BLACK, xc-xxx - translateX, yc+yyy + translateY); // Q3
			}
			if (t + b2*xxx <= crit1 ||   /* e(xxx+1,y-1/2) <= 0 */
			    t + a2*yyy <= crit3)      /* e(xxx+1/2,y) <= 0 */
				{xxx++; dxt += d2xt; t += dxt;} // incx()
			 else if (t - a2*yyy > crit2) /* e(xxx+1/2,y-1) > 0 */
				{yyy--; dyt += d2yt; t += dyt;}
			else {
				{xxx++; dxt += d2xt; t += dxt;} // incx()
				{yyy--; dyt += d2yt; t += dyt;}
			}
		}
	}

	
	public void drawRect(int x, int y, int width, int height) {
		if ((width < 0) || (height < 0))
			return;

		if (height == 0 || width == 0) {
	    		drawLine(x, y, x + width, y + height);
		} else {
	    	drawLine(x, y, x + width - 1, y);
			drawLine(x + width, y, x + width, y + height - 1);
			drawLine(x + width, y + height, x + 1, y + height);
			drawLine(x, y + height, x, y + 1);
		}
    }
	 
	public void fillRect(int x, int y, int width, int height) {
		if ((width < 0) || (height < 0))
			return;
		LCD.bitBlt(x, y, width, height, (rgbColor == BLACK ? LCD.ROP_SET : LCD.ROP_CLEAR));
 	}

	public void drawString(String str, int x, int y, int rop) {
		LCD.drawString(str, x, y, rop);
	}
	
	public void drawString(String str, int x, int y) {
		drawString(str, x, y, LCD.ROP_COPY);
	}
	
	public void drawString(String str, int x, int y, boolean invert) {
		LCD.drawString(str, x, y, invert);
	}
	
	public void drawChar(char c, int x, int y, boolean invert) {
		LCD.drawChar(c, x, y, invert);
	}
	
	public void drawImage(Image img, int x, int y, boolean invert) {
		if (img == null) {
			return;
		}
		LCD.bitBlt(img.getData(), img.getWidth(), img.getHeight(), 0, 0, x, y, img.getWidth(), img.getHeight(), (invert ? LCD.ROP_COPYINVERTED : LCD.ROP_COPY));
	}

	public void drawImage(Image img, int sx, int sy, int x, int y, int w, int h, int rop) {
		if (img == null)
			LCD.bitBlt(x, y, w, h, rop);
		else
			LCD.bitBlt(img.getData(), img.getWidth(), img.getHeight(), sx, sy, x, y, w, h, rop);
	}
	
	public int getStrokeStyle() {
		return strokeStyle;
	}

	public void setStrokeStyle(int style) {
		if (style != SOLID && style != DOTTED) {
			throw new IllegalArgumentException();
		}
		strokeStyle = style;
	}

	// Temp for testing purposes until Canvas made.
	public void refresh() {
		LCD.setDisplay();
		LCD.refresh();
	}
	
	// Temp method for testing. Clears out graphics buffer
	// and refreshes screen.
	public void clear() {
		LCD.clearDisplay();
	}
	
	public void autoRefresh(boolean on)
	{
		LCD.setAutoRefresh((on ? 1 : 0));
	}
}

/*
class LCD extends JPanel {
	public static final int SCREEN_WIDTH = 100;
	public static final int SCREEN_HEIGHT = 64;
	public static final int SCREEN_SCALE = 4;

	public static int [] screenBuf;
	public Graphics nxjGraphics;
	
	// drawArc and fillArc parameters
	int x = 10;
	int y = 10;
	int width = 80;
	int height = 50;
	int start = 0;
	int angle = -135;
	
	public LCD() {
		setBackground(Color.WHITE);
		setPreferredSize(new Dimension(SCREEN_SCALE * SCREEN_WIDTH, 
				SCREEN_SCALE * SCREEN_HEIGHT));

		nxjGraphics = new Graphics();
		nxjGraphics.setStrokeStyle(Graphics.DOTTED);
		nxjGraphics.drawLine(0, 0, 100, 64);
		nxjGraphics.fillArc(x, y, width, height, start, angle);
		nxjGraphics.drawArc(10, 10, 20, 40, 0, 360);
//		nxjGraphics.drawRoundRect(75, 5, 20, 10, 45, 45);
		nxjGraphics.refresh();
		repaint();
	}

	public static void drawString(String str, int x, int y) {}
	public static void setDisplay(int [] buff) {
		screenBuf = buff;
	}
	public static void refresh() {}
	
    public synchronized void paint(java.awt.Graphics g) {
        int w = getSize().width;
        int h = getSize().height;

        java.awt.Graphics2D g2 = (java.awt.Graphics2D) g;
        g2.setBackground(getBackground());
        g2.clearRect(0, 0, w, h); 
        
        // Draw example image for verification
        g2.setColor(Color.RED);
        g2.fillArc(SCREEN_SCALE * x, SCREEN_SCALE * y, SCREEN_SCALE * width, 
        		SCREEN_SCALE * height, start, angle);
        g2.drawRect(300, 20, 80, 40);
        g2.drawRoundRect(300, 20, 80, 40, 45, 30);

        // Draw NXJ image
        g2.setColor(Color.BLACK);
        for (int xp = 0; xp < 100; xp++) {
        	for (int yp = 0; yp < 64; yp++) {
        		if (xp < 0 || xp >= 100 || yp < 0 || yp >= 64) continue;
        		int xChar = xp / 4;
        		int yChar = yp / 8;
        		int index = yChar * 25 + xChar;
        		int specificBit = (yp % 8) + ((xp % 4) * 8);
        		if ((screenBuf[index] & (1 << specificBit)) != 0) {
        			g2.drawRect(SCREEN_SCALE * xp, SCREEN_SCALE * yp, 
        					SCREEN_SCALE, SCREEN_SCALE);
        		}
        	}
        }        
    }
}

class TestApp extends JFrame {
	public TestApp() {
		// End application when window is closed
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });

        setTitle("NXJ Grapics test app");
		getContentPane().add(new LCD());
	    pack();
	    show();
	}

	public static void main(String[] args) {
		new TestApp();
	}
}

*/