// Wendy Chien
// Recitation F11
// Project 4

import java.awt.*;
import java.lang.*;
import java.util.*;

public class Triangle implements Drawable {
  static Vertex3D vlist[]; 
  private Vertex3D v[];
  public int vi[];
  private boolean breakright;  // if breakright is 1, then the break vertex is to the right 
  private ColorFinder cf;
  private Surface surf;
  protected int r[], g[], b[];
  boolean culled;
  Point3D normal;

  public Triangle(int vtx0, int vtx1, int vtx2) {
    vi = new int[3];
    vi[0] = vtx0;
    vi[1] = vtx1;
    vi[2] = vtx2;

    v = new Vertex3D[3];
    r = new int[3];
    g = new int[3];
    b = new int[3];

  }

  public Triangle(Vertex3D vtx0, Vertex3D vtx1, Vertex3D vtx2, int r[], int g[], int b[]) {

    v = new Vertex3D[3];

    // Sort the vertices (y values)  
    // v[0] contains the vertex with the smallest y value
    // v[1] contains the vertex with the middle y value
    // v[2] contains the vertex with the largest y value
    if (vtx0.y <= vtx1.y) {
      v[0] = vtx0;
      v[1] = vtx1;
    }
    else {
      v[0] = vtx1;
      v[1] = vtx0;
    }
    if (v[0].y <= vtx2.y) {
      if (v[1].y <= vtx2.y) {
	v[2] = vtx2;
      }
      else {
	v[2] = v[1];
	v[1] = vtx2;
      }
    }
    else {
      v[2] = v[1];
      v[1] = v[0];
      v[0] = vtx2;
    }
    
    // Find the position of the breakpoint
    float slope = (float) (v[0].y - v[2].y) / (float) (v[0].x - v[2].x);
    float xOnLine = (1/slope)*(v[1].y-v[2].y) + v[2].x;
    if (xOnLine < v[1].x) 
      breakright = true;
    else
      breakright = false;

    // Creates the ColorFinder
    cf = new ColorFinder(v,r,g,b);
  }
  
  public void setSurface (Surface s) {
    surf = s;
  }

  public void colorPixel(Raster r, int x, int y) {
    int c;

    c = cf.getColor(x,y);
    r.setPixel(c,x,y);

  }

  public void initNormal() {
    // needs for vlist to have been given a list
    
    normal = findNormal(vlist[vi[0]],vlist[vi[1]],vlist[vi[2]]);
  }
  
  public void setNormal(Point3D n) {
    normal = n;
  }

  public Point3D findNormal(Point3D vtx0, Point3D vtx1, Point3D vtx2) {
    Point3D vect1 = new Point3D();
    Point3D vect2 = new Point3D();
    Point3D n;
    Point3D b[];

    b = new Point3D[3];
   
    // points are listed in a counter-clockwise order
    b[0] = vtx0;
    b[1] = vtx1;
    b[2] = vtx2;

    // From the three points, create two vectors
    vect1.x = b[1].x - b[0].x;
    vect1.y = b[1].y - b[0].y;
    vect1.z = b[1].z - b[0].z;

    vect2.x = b[2].x - b[0].x;
    vect2.y = b[2].y - b[0].y;
    vect2.z = b[2].z - b[0].z;

    n = vect1.cross(vect2);
    
    return n;
  }

  public Point3D findNormal(Vertex3D vtx0, Vertex3D vtx1, Vertex3D vtx2) {
    Point3D vect1 = new Point3D();
    Point3D vect2 = new Point3D();
    Point3D n;
    Vertex3D b[];

    b = new Vertex3D[3];
   
    // points are listed in a counter-clockwise order
    b[0] = vtx0;
    b[1] = vtx1;
    b[2] = vtx2;
    
    // From the three points, create two vectors
    vect1.x = b[1].x - b[0].x;
    vect1.y = b[1].y - b[0].y;
    vect1.z = b[1].z - b[0].z;


    vect2.x = b[2].x - b[0].x;
    vect2.y = b[2].y - b[0].y;
    vect2.z = b[2].z - b[0].z;

    n = vect1.cross(vect2);
    
    return n;
  }

  public boolean BackFace(Point3D eye, Point3D normal) {
    Point3D toTri;
    float d;

    toTri = new Point3D();
    toTri.x = vlist[vi[0]].x - eye.x;
    toTri.y = vlist[vi[0]].y - eye.y;
    toTri.z = vlist[vi[0]].z - eye.z;

    d = toTri.dot(normal);
    if (d > 0)
      return true;
    else
      return false;
  }

  public boolean isVisible() {
    return (!culled);
  }

  public void Illuminate(Light lightlist[], int lights, Point3D eye, boolean cull) {
    int i,j;
    Point3D norm, n;
    float intensityR, intensityG, intensityB;
    Point3D l;
    Vertex3D vtemp[];

    culled = false;
    if (cull) {
      if (BackFace(eye,normal)) { 
	culled = true;
      }
    }
    if (!culled) {
      intensityR = 0;
      intensityG = 0;
      intensityB = 0;
      for (i=0;i<lights;i++) {
	// ambient
	if (lightlist[i].lightType == Light.AMBIENT) {
	  intensityR += lightlist[i].ir*surf.ka;
	  intensityG += lightlist[i].ig*surf.ka;
	  intensityB += lightlist[i].ib*surf.ka;
	}
	else if (lightlist[i].lightType == Light.DIRECTIONAL) {
	  l = new Point3D(-1*lightlist[i].x,-1*lightlist[i].y,-1*lightlist[i].z);
	  l = l.normalize();
	  n = normal.normalize();
	  intensityR += lightlist[i].ir*surf.kd*n.dot(l);
	  intensityG += lightlist[i].ig*surf.kd*n.dot(l);
	  intensityB += lightlist[i].ib*surf.kd*n.dot(l);
	}	
      }
      for (j = 0; j < 3; j++) {
	r[j] = (int) (256*intensityR*surf.r);
	g[j] = (int) (256*intensityG*surf.g);
	b[j] = (int) (256*intensityB*surf.b);
      }
      vtemp = new Vertex3D[3];
      for (j=0;j<3;j++) {
	vtemp[j] = vlist[vi[j]];
      }
      cf = new ColorFinder(vtemp,r,g,b);
    }
  }

  public void ClipAndDraw(ZRaster raster, Matrix3D project) {
    boolean vtxin[];
    Vertex3D vtx[];
    Vertex3D saved[];
    int i,j;
    boolean last = false;
    boolean next;
    Vertex3D h;
    Triangle t[];
    
    vtxin = new boolean[3];
    vtx = new Vertex3D[3];
    saved = new Vertex3D[5];
    for (i=0;i<3;i++) {
      vtx[i] = vlist[vi[i]];
      if ((vtx[i].z <= 1) && (vtx[i].z >= -1))
	vtxin[i] = true;
      else
	vtxin[i] = false;
    }

    t = new Triangle[3];
    h = new Vertex3D();
    j = 0;
    if (vtxin[0]) 
      last = true;
    for (i=0;i<3;i++) {
      next = vtxin[(i+1)%3];
      if ((!last) && next) {
	// find the intersect point
	h.x = vtx[i].x + (vtx[i].z/(vtx[i].z-vtx[(i+1)%3].z))*(vtx[(i+1)%3].x - vtx[i].x);
	h.y = vtx[i].y + (vtx[i].z/(vtx[i].z-vtx[(i+1)%3].z))*(vtx[(i+1)%3].y - vtx[i].y);
	h.z = ((vtx[i].z > 1) ? 1 : -1);
	saved[j] = h;
	j++;
      }
      else if (last && next) {
	saved[j] = vtx[(i+1)%3];
	j++;
      }
      else if (last && (!next)) {
	// find the intersect point
	h.x = vtx[i].x + (vtx[(i+1)%3].z/(vtx[(i+1)%3].z-vtx[i].z))*(vtx[i].x - vtx[(i+1)%3].x);
	h.y = vtx[i].y + (vtx[(i+1)%3].z/(vtx[(i+1)%3].z-vtx[i].z))*(vtx[i].y - vtx[(i+1)%3].y);
	h.z = ((vtx[(i+1)%3].z > 1) ? 1 : -1);
	//h.z = ((vtx[i].z < 1) ? 1 : -1);
	saved[j] = h; 
	j++;
      }
      else if ((Math.abs(vtx[i].z-vtx[(i+1)%3].z) > 2) && (!last) && (!next)) {
	h.x = vtx[i].x + (vtx[i].z/(vtx[i].z-vtx[(i+1)%3].z))*(vtx[(i+1)%3].x - vtx[i].x);
	h.y = vtx[i].y + (vtx[i].z/(vtx[i].z-vtx[(i+1)%3].z))*(vtx[(i+1)%3].y - vtx[i].y);
	h.z = ((vtx[i].z > 1) ? 1 : -1);
	saved[j] = h;
	j++;
	h.x = vtx[i].x + (vtx[(i+1)%3].z/(vtx[(i+1)%3].z-vtx[i].z))*(vtx[i].x - vtx[(i+1)%3].x);
	h.y = vtx[i].y + (vtx[(i+1)%3].z/(vtx[(i+1)%3].z-vtx[i].z))*(vtx[i].y - vtx[(i+1)%3].y);
	h.z = ((vtx[(i+1)%3].z > 1) ? 1 : -1);
	//h.z = ((vtx[i].z < 1) ? 1 : -1);
	saved[j] = h; 
	j++;
      }
      last = next;
    }
    
    
    for (i=0;i<j;i++) {
      saved[i] = project.transform(saved[i]);
    }
    int loop;
    for (i=2;i<j;i++) {
      t[i-2] = new Triangle(saved[0],saved[i-1],saved[i],r,g,b);
      t[i-2].Draw(raster);
    }
        
  }
  
  public void setVertexList(Vertex3D[] list) {
    vlist = list;
  }

  public void Draw(ZRaster r) {
    // Draws the triangle.
    int y, y1, y2; 
    int x;
    int x1, x2;
    float dx1, dx2;
    boolean horiz1 = false;
    boolean horiz2 = false;
    float x1OnLine, x2OnLine;
    float zvals[];
    int tempz;
    
    zvals = new float[4];
    cf.getPlaneEqn(zvals,(int) v[0].z, (int) v[1].z, (int) v[2].z);

    if ((v[0].y - v[1].y) != 0) 
      dx1 = (float) (v[0].x - v[1].x)/ (float) (v[0].y - v[1].y);
    else {
      dx1 = 0;
      horiz1 = true;
    }
    if ((v[0].y - v[2].y) != 0) 
      dx2 = (float) (v[0].x - v[2].x)/ (float) (v[0].y - v[2].y);
    else {
      dx2 = 0;
      horiz2 = true;
    }
    
    y = (int) Math.ceil(v[0].y);
    y1 = (int) Math.ceil(v[1].y);
    y2 = (int) Math.ceil(v[2].y);

    while (y < y1) {
      // Goes through the scan lines above the breakpoint

      x1OnLine = (float) ((dx1)*((float)y-v[1].y) + v[1].x);
      x2OnLine = (float) ((dx2)*((float)y-v[2].y) + v[2].x);

      x1 = (int) Math.ceil(x1OnLine);
      x2 = (int) Math.ceil(x2OnLine);
      
      // Scans the line.
      if (x1 <= x2) {
	for (x=x1;x<x2;x++) {
	  if ((x >= 0) && (x < r.width) && (y >= 0) && (y < r.height)) {
	    tempz = (int) Math.round((zvals[3] - zvals[0]*x - zvals[1]*y)/zvals[2]);
	    if (tempz < r.getZ(x,y)) {
	      colorPixel(r,x,y);
	      r.setZ(tempz,x,y);
	    }
	  }
	}
      }
      else {
	for (x=x2;x<x1;x++) {
	  if ((x >= 0) && (x < r.width) && (y >= 0) && (y < r.height)) {
	    tempz = (int) Math.round((zvals[3] - zvals[0]*x - zvals[1]*y)/zvals[2]);
	    if (tempz < r.getZ(x,y)) {
	      colorPixel(r,x,y);
	      r.setZ(tempz,x,y);
	    }
	  }
	}
      }

      y++;
      
    }

    // Scan the second part of the triangle (the part after the breakpoint)

    // Changes the slope of one of the edges (because we are past the breakpoint)
    if ((v[1].y - v[2].y) != 0) 
      dx1 = (float) (v[1].x - v[2].x)/ (float) (v[1].y - v[2].y);
    else {
      dx1 = 0;
      horiz1 = true;
    }
    
    while (y < y2) {
      // Goes through the scan lines below the breakpoint
      x1OnLine = (float) ((dx1)*((float)y-v[2].y) + v[2].x);
      x2OnLine = (float) ((dx2)*((float)y-v[2].y) + v[2].x);

      x1 = (int) Math.ceil(x1OnLine);
      x2 = (int) Math.ceil(x2OnLine);
	
      // Scans the line.
      if (x1 <= x2) {
	for (x=x1;x<x2;x++) {
	  if ((x >= 0) && (x < r.width) && (y >= 0) && (y < r.height)) {
	    tempz = (int) Math.round((zvals[3] - zvals[0]*x - zvals[1]*y)/zvals[2]);
	    if (tempz < r.getZ(x,y)) {
	      colorPixel(r,x,y);
	      r.setZ(tempz,x,y);
	    }
	  }
	}
      }
      else {
	for (x=x2;x<x1;x++) {
	  if ((x >= 0) && (x < r.width) && (y >= 0) && (y < r.height)) {
	    tempz = (int) Math.round((zvals[3] - zvals[0]*x - zvals[1]*y)/zvals[2]);
	    if (tempz < r.getZ(x,y)) {
	      colorPixel(r,x,y);
	      r.setZ(tempz,x,y);
	    }
	  }
	}
      }
      
      y++;

    }

  }

  public void printLine(String s) {
    System.out.println(s);
  }

  public void printStats(int x1, int x2, float x1OnLine, float x2OnLine) {
    
    System.out.println("Statistics");
    System.out.println("x1: "+x1);
    System.out.println("x2: "+x2);
    System.out.println("x1OnLine: "+x1OnLine);
    System.out.println("x2OnLine: "+x2OnLine);
    
  }

  public void printOut() {
        
    System.out.println("v[0]: ("+v[0].x+","+v[0].y+")");
    System.out.println("v[1]: ("+v[1].x+","+v[1].y+")");
    System.out.println("v[2]: ("+v[2].x+","+v[2].y+")");
    if (breakright) 
      System.out.println("breakright");
    else
      System.out.println("breakleft");
      
  }

}

