package cz.cuni.jagrlib.testing;

import java.util.*;
import java.util.Arrays;
import java.lang.reflect.Array;

import cz.cuni.jagrlib.*;
import cz.cuni.jagrlib.iface.*;
import cz.cuni.jagrlib.reg.*;

/**
 * Antialiased line template.
 * @version 0.24 $Rev: 89 $ $Date: 2005-10-25 16:29:44 +0200 (ut, 25 X 2005) $ $Author: pepca $
 * @since   0.24
 * @see     <a href="../../../../../cz/cuni/jagrlib/testing/LineAntialias.java">LineAntialias.java</a>
 */
public class LineAntialiasAndrejKrutak extends Piece implements LineRenderAnti
{
  //==========================================================================
  //  Data:

  /**
   * Line-join state.
   * @see    Render#LINE_JOIN
   */
  protected int join = LINE_JOIN_OVERLAP;

  /**
   * Line width in pixels.
   * @see    Render#LINE_WIDTH
   */
  protected double width = 1.0;

  //==========================================================================
  //  Interface Property:

  /**
   * Sets the given property.
   * @param  key Key string.
   * @param  value The new value.
   */
  public void set ( String key, Object value )
  {
    if ( key == null || value == null ) return;
      // LINE_JOIN:
    if ( key.compareTo(LINE_JOIN) == 0 )
    {
      if ( value instanceof Integer )
        join = ((Integer)value).intValue();
      return;
    }
      // LINE_WIDTH:
    if ( key.compareTo(LINE_WIDTH) == 0 )
    {
      if ( value instanceof Double )
        width = ((Double)value).doubleValue();
      return;
    }
  }

  /**
   * Gets the given property.
   * @param  key Key string.
   * @return The actual value or <code>null</code>.
   */
  public Object get ( String key )
  {
    if ( key == null ) return null;
    if ( key.compareTo(LINE_JOIN) == 0 )
      return new Integer(join);
    if ( key.compareTo(LINE_WIDTH) == 0 )
      return new Double(width);
    return null;
  }

  //==========================================================================
  //  Interface LineRenderAnti:

  /**
   * Sets default line width in pixels.
   * @param  wid New line width in pixels.
   * @return The old line width in pixels.
   */
  public double setWidth ( double wid )
  {
    double old = width;
    width = wid;
    return old;
  }

  /**
   * Draws a line from [x1,y1] to [x2,y2].
   * @param  x1 X coordinate of the starting pixel.
   * @param  y1 Y coordinate of the starting pixel.
   * @param  x2 X coordinate of the line end.
   * @param  y2 Y coordinate of the line end.
   * @param  wid Line width in pixels.
   */
  public void drawLine ( int x1, int y1, int x2, int y2, double wid )
  {
    drawLine((double)x1,(double)y1,(double)x2,(double)y2,wid);
  }

	class point {
		public double x, y;
		public point(double x_, double y_) {x=x_; y=y_;}
		public point add(vector v) {return new point(x+v.x, y+v.y);}
		public point sub(vector v) {return new point(x-v.x, y-v.y);}
		public vector sub(point v) {return new vector(x-v.x, y-v.y);}
		
		public String toString() {return "["+x+", "+y+"]";}
	};
	class vector {
		public double x, y;
		public vector(double x_, double y_) {x=x_; y=y_;}
		public vector(double x1, double y1, double x2, double y2) {x=x2-x1; y=y2-y1;}
		public vector(point p1, point p2) {x=p1.x-p2.x; y=p2.y-p1.y;}
		public vector getNormal() {return new vector(-y, x);}
		public void normalize(double w) {
			double s=Math.sqrt(x*x+y*y); x=x/s; y=y/s;
			if (y!=0) {
				double nx, ny; ny=Math.sqrt((w*w)/((x*x)/(y*y)+1)); nx=ny*x/y; x=nx; y=ny;
			} else {x=x*w;} //we only've got vector.x
		}
		
		public String toString() {return "["+x+", "+y+"]";}
	};
  
	/**
	 * Returns surface of 3xrect+1xtriangle plane...
	 * 
	 * +---.
	 * |   |\     y2
	 * |   | \
	 * ----+--|
	 * |   |  |   y1
	 * +---|--+
	 *   x1 x2
	 */
	 double get3Re1TriS(double x1, double x2, double y1, double y2) {
		//System.out.println(x1+" "+x2+" "+y1+" "+y2);
		return x1*y2+x1*y1+x2*y1+(x2*y2)/2;
	 }
	 
	 /**
	  * Returns x point of crossing
	  * 
	  |\
	  |-+   ----- y
	  | |\
	  +-|--
	  y1  y2
	  |-w--|
	  
	  */
	 double getCrossing(double h, double w, double y) {
		//double xx=(c1y-c2y-y) / ((c1y-c2y)/(c2x-c1x)) <= crossing point
		return (h-y) / (h/w);
	 }
	 
	 double makeInt(double x) {
		return (int)x;
	 }
	 void putPoint(double[][] screen, int x, int y, int pos, point corner, double v) {
		switch (pos) {
		case 11:
			screen[y][x]+=v;
			break;
		case 12:
			screen[(int)corner.y-y][x]+=v;
			break;
		case 21:
			screen[y][(int)corner.x-x]+=v;
			break;
		case 22:
			screen[(int)corner.y-y][(int)corner.x-x]+=v;
			break;
		}
	 }
	/**
	 * Draw a right triangle to specified alpha mask
	 * @param screen output alphamask
	 * @param p1 corner of the right angle
	 * @param p2 'y-different corner'
	 * @param p2 'x-different corner'
	 */
	void drawRightTriangle(double[][] screen, point p1, point p2, point p3)
	{
		int sy=Array.getLength(screen);
		int sx=Array.getLength(screen[0]);
		int pos=0;
		point corner0=new point(0, 0), corner1=new point(0, 0), corner2=new point(0, 0);
		double h=0, w=0;
		int x, y;
		
		//System.out.println("drt: "+p1+" "+p2+" "+p3);
		if (p1.x<p3.x && p1.y<p2.y) { // |/ 
			pos=11;
			corner0.x=p1.x;
			corner0.y=p1.y;
			h=p2.y-p1.y;
			w=p3.x-p1.x;
			//System.out.println("left top"+corner0.x+":"+corner0.y);
		} else if (p1.x<p3.x && p1.y>p2.y) { // |\ 
			pos=12;
			corner0.x=p1.x;
			corner0.y=makeInt(p1.y)+1-p1.y;
			h=p1.y-p2.y;
			w=p3.x-p1.x;
			//System.out.println("left bottom"+corner0.x+":"+corner0.y);
		} else if (p1.x>p3.x && p1.y<p2.y) { // \| 
			pos=21;
			corner0.x=makeInt(p1.x)+1-p1.x;
			corner0.y=p1.y;
			h=p2.y-p1.y;
			w=p1.x-p3.x;
			//System.out.println("right top"+corner0.x+":"+corner0.y);
		} else if (p1.x>p3.x && p1.y>p2.y) { // /| 
			pos=22;
			corner0.x=makeInt(p1.x)+1-p1.x;
			corner0.y=makeInt(p1.y)+1-p1.y;
			h=p1.y-p2.y;
			w=p1.x-p3.x;
			//System.out.println("right bottom"+corner0.x+":"+corner0.y);
		} else System.out.println("wtf?!");

		//System.out.println("pos: "+pos+"=> "+w+"x"+h);
		//and now... booo - let's draw :-)
		corner1.x=corner0.x;
		corner1.y=h*(1-(corner1.x-corner0.x)/w);
		while (corner1.x<(corner0.x+w)) {
			boolean cont;
			double cw;
			//find the 2nd corner
			corner2.x=Math.min((double)((int)corner1.x+1), corner0.x+w);
			corner2.y=h*(1-(corner2.x-corner0.x)/w);
			cw=corner2.x-corner1.x;
			x=(int)corner1.x;
			y=0;
			//now draw the column...
			
			//System.out.println("X: "+corner1.x+"-"+corner2.x);
			cont=false;
			if ((int)corner2.y>0) { //is there a bottom line?
				putPoint(screen, x, 0, pos, p1, get3Re1TriS(cw, 0, 1-corner0.y, 0));
				y=1;
				cont=true;
				//System.out.println("AAA");
			} else {
				if (corner1.y<1) {
					//putPoint(screen, x, 0, pos, p1, get3Re1TriS(0, cw, corner1.y-corner2.y, corner2.y-corner0.y));
					putPoint(screen, x, 0, pos, p1, get3Re1TriS(0, cw, corner2.y-makeInt(corner2.y), corner2.y-corner1.y));
					//System.out.println("BBB");
				} else {
					double xx=getCrossing(corner1.y-corner2.y, cw, 1-corner0.y);
					
					putPoint(screen, x, 0, pos, p1, get3Re1TriS(xx, cw-xx, 1-corner0.y, 0));
					y=1;
					cont=true;
					//System.out.println("CCC");
				}
			}
			if ((int)corner2.y>1) { //fill a few boxes...
				for (y=1; y<(int)corner2.y; y++)
					putPoint(screen, x, y, pos, p1, get3Re1TriS(cw, 0, 1, 0));
				cont=true;
				//System.out.println("DDD"+y);
			}
			if (cont==true) { //whe have more pixels to draw, y contains the first position
				//System.out.println("EEE"+y+"/"+((int)(corner1.y)+1));
				double x1, x2, y1, y2;
				while (y<(int)(corner1.y)+1) {
					//System.out.println(y+" & " + (int)(corner1.y+1) + " + " + corner2.y);
					if (corner2.y>y) {
						if (corner1.y>y+1) {
							double xx=getCrossing(corner1.y-corner2.y, cw, makeInt(corner2.y)+1-corner2.y);
							//System.out.println("a "+x+":"+y+"=> "+xx+", "+cw);
							putPoint(screen, x, y, pos, p1, get3Re1TriS(xx, cw-xx, corner2.y-makeInt(corner2.y), makeInt(corner2.y)+1-corner2.y));
						} else {
							//System.out.println("b "+x+":"+y+"=> "+cw);
							putPoint(screen, x, y, pos, p1, get3Re1TriS(0, cw, corner2.y-makeInt(corner2.y), corner1.y-corner2.y));
						}
					} else {
						if (corner1.y>y+1) {
							double xxx=getCrossing(corner1.y-corner2.y, cw, y-corner2.y);
							double xx=getCrossing(corner1.y-corner2.y, cw, y+1-corner2.y);
							//System.out.println("c "+x+":"+y+"=> "+xx+", "+get3Re1TriS(xx, xxx-xx, 0, 1));
							putPoint(screen, x, y, pos, p1, get3Re1TriS(xx, xxx-xx, 0, 1));
						} else {
							double xx=getCrossing(corner1.y-corner2.y, cw, y-corner2.y);
							//System.out.println("d "+x+":"+y+"=> "+xx+", "+cw);
							putPoint(screen, x, y, pos, p1, get3Re1TriS(0, xx, 0, corner1.y-makeInt(corner1.y)));
						}
					}
					y++;
				}
			}
			corner1.x=corner2.x;
			corner1.y=corner2.y;
		};
	}
	
	/**
	 * remove border of the frame
	 */
	void removeBorder(double[][] screen, double rl, double rt, double rr, double rb)
	{
		int sy=Array.getLength(screen);
		int sx=Array.getLength(screen[0]);
		int x, y;
		for (x=0; x<sx; x++) {
			screen[0][x]+=rt;
			screen[sy-1][x]+=rb;
		}
		for (y=0; y<sy; y++) {
			screen[y][0]+=rl;
			screen[y][sx-1]+=rr;
		}
		//undo duplicates in corners
		screen[0][0]-=rl*rt;
		screen[sy-1][0]-=rl*rb;
		screen[0][sx-1]-=rr*rt;
		screen[sy-1][sx-1]-=rr*rb;
	}
  /**
   * Draws a line from [x1,y1] to [x2,y2].
   * @param  x1 X coordinate of the starting pixel.
   * @param  y1 Y coordinate of the starting pixel.
   * @param  x2 X coordinate of the line end.
   * @param  y2 Y coordinate of the line end.
   * @param  wid Line width in pixels.
   */
  public void drawLine ( double x1, double y1, double x2, double y2, double w )
  {
	//x1=10; x2=10; y1=10; y2=500; w=1;
	AlphaMask out = (AlphaMask)getInterface(PL_OUTPUT,IFACE+"AlphaMask");
	int sw, sh, ofx, ofy;
	double[][] screen;
	
	//repair metrics
	x1+=0.5;
	y1+=0.5;
	x2+=0.5;
	y2+=0.5;
	w/=2;
	if (x1>x2) { //draw in direction left -> right
		double dt=x1; x1=x2; x2=dt;
		dt=y1; y1=y2; y2=dt;
	}
	vector offset;
	point c1, c2, c3, c4;
	
	vector v=new vector(x1, y1, x2, y2);
	vector vn=v.getNormal();
	vn.normalize(w);
	point p1=new point(x1, y1);
	point p2=new point(x2, y2);
	
	if (y1<y2) {
		//System.out.println("Y1<Y2");
		c1=p1.sub(vn);
		c2=p1.add(vn);
		c3=p2.sub(vn);
		c4=p2.add(vn);
	} else {
		//System.out.println("Y1>=Y2");
		c2=p1.sub(vn);
		c4=p1.add(vn);
		c1=p2.sub(vn);
		c3=p2.add(vn);
	}

	offset=new vector((int)c2.x, (int)c1.y); //left-top corner pixel
	c1=c1.sub(offset);
	c2=c2.sub(offset);
	c3=c3.sub(offset);
	c4=c4.sub(offset);
	
	ofx=(int)offset.x;
	ofy=(int)offset.y;
	sw=(int)c3.x+1;
	sh=(int)c4.y+1;
	screen=new double[sh][sw];
	for (int i=0; i<sh; i++) for (int j=0; j<sw; j++) screen[i][j]=0;
	//System.out.println(p1 + ", " + p2 + ": " + v + ", " + vn);
	//System.out.println(c1 + ", " + c2 + ", " + c3 + ", " + c4);
	if ((x1!=x2) && (y1!=y2)) {
		drawRightTriangle(screen, new point(c2.x, c1.y), c2, c1); //left-top
		drawRightTriangle(screen, new point(c2.x, c4.y), c2, c4); //left-bottom
		drawRightTriangle(screen, new point(c3.x, c1.y), c3, c1); //right-top
		drawRightTriangle(screen, new point(c3.x, c4.y), c3, c4); //right-bottom
	}
	//System.out.println(sw+"x"+sh+": "+(c2.x-makeInt(c2.x))+" "+(c1.y-makeInt(c1.y))+" "+(1-(c3.x-makeInt(c3.x)))+" "+(1-(c4.y-makeInt(c4.y))));
	removeBorder(screen, c2.x-makeInt(c2.x), c1.y-makeInt(c1.y), 1-(c3.x-makeInt(c3.x)), 1-(c4.y-makeInt(c4.y)));

	for (int i=0; i<sh; i++) for (int j=0; j<sw; j++) out.putPixel(ofx+j, ofy+i, 1-screen[i][j]);
	//for (int i=0; i<sh; i++) {out.putPixel(ofx-1, ofy+i, 1); out.putPixel(ofx+sw, ofy+i, 1);}
	//for (int i=0; i<sw; i++) {out.putPixel(ofx+i, ofy-1, 1); out.putPixel(ofx+i, ofy+sh, 1);}
  }

  /**
   * Draws a line from [x1,y1] to [x2,y2].
   * @param  x1 X coordinate of the starting pixel.
   * @param  y1 Y coordinate of the starting pixel.
   * @param  x2 X coordinate of the line end.
   * @param  y2 Y coordinate of the line end.
   */
  public void drawLine ( int x1, int y1, int x2, int y2 )
  {
    drawLine(x1,y1,x2,y2,width);
  }

  /**
   * Draws a line from [x1,y1] to [x2,y2].
   * Calls <a href="#drawLine(int, int, int, int, double)">drawLine(int,int,int,int,double)</a>
   * directly after rounding the <code>double</code> arguments.
   * @param  x1 X coordinate of the starting pixel.
   * @param  y1 Y coordinate of the starting pixel.
   * @param  x2 X coordinate of the line end.
   * @param  y2 Y coordinate of the line end.
   */
  public void drawLine ( double x1, double y1, double x2, double y2 )
  {
    drawLine(x1,y1,x2,y2,width);
  }

  //==========================================================================
  //  Module registration:

  /** Object name. */
  private final static String NAME = "andree=>LineAnti";

  /** Object template identifier. */
  protected final static String TEMPLATE_NAME = "LineRenderAntiToAlphaMask";

  /** Object description. */
  private final static String DESCRIPTION = "Draws anti-aliased line segment.";
  /** Object category. */
  protected final static String CATEGORY = C_2D + "." + C_DRAW + "." + C_LINE + "." + C_INTEGER;

  /**
   * General-purpose registration routine.
   * Sets all plugs, strings, etc. to the given Template.
   */
  public static int setTemplate ( Template t, int ord )
  {
    if ( t != null && ord <= 0 )
    {
      t.setRegStrings(NAME,TEMPLATE_NAME,CATEGORY,DESCRIPTION);
        // Plugs:
      t.newInputPlug(PL_INPUT,IFACE+"LineRenderAnti");
      t.newOutputPlug(PL_OUTPUT,IFACE+"AlphaMask");
        // Property:
      t.propBegin(LINE_JOIN,TYPE_INTEGER,"Line-join style",true);
        t.propDefault(String.valueOf(LINE_JOIN_DISJOINT));
        t.propBounds(String.valueOf(LINE_JOIN_DISJOINT),String.valueOf(LINE_JOIN_OVERLAP));
        t.propManipulator(MANIPULATOR_COMBO);
        t.propEnum("Disjoint",String.valueOf(LINE_JOIN_DISJOINT),"Subsequent line segments are disjoint");
        t.propEnum("Overlap",String.valueOf(LINE_JOIN_OVERLAP),"Subsequent line segments have one common pixel");
      t.propEnd();
      t.propBegin(LINE_WIDTH,TYPE_DOUBLE,"Line width",true);
        t.propDefault("1.0");
        t.propBounds("0.01","100.0");
      t.propEnd();
    }
    return 1;
  }

  /**
   * Static registration instance for this class.
   * Automatically initialized in class-loading time.
   */
  public static final RegPiece reg;
  static
  {
    reg = new RegPiece();
    setTemplate(reg,0);
  }

}

