package graphics.raytracer;

import java.awt.*;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;

public class DoImage {

  private Targets scene = Targets.getTargets();
  private Dimension d = null;

  private int pixels[][] = null;

  public Dimension getSize() {
    return d;
  }

  public void setSize(Dimension _d) {
    d = _d;
    pixels = new int[d.width][d.height];
  }

  public DoImage(Dimension _d) {
    d = _d;
    pixels = new int[d.width][d.height];
  }

  public void doTheWork() {

    int screenWidth = d.width;
    int screenHeight = d.height;
    int pixY = 0;
    int pixX = 0;
    double deltaY = 1.0 / screenHeight;
    double deltaX = 1.0 / screenWidth;
    double tmpy = 1.0;
    double tmpz = 0.0; // tmpz is the projection plane

    int screenStartLine = 0;
    int screenEndLine = 2 * screenHeight;
    for (pixY = screenStartLine; pixY < screenEndLine; pixY++) {
      double tmpx = -1.0;
      tmpy -= deltaY;
      pixX = renderALine(screenWidth, deltaX, tmpx, tmpy, tmpz, pixY);
    }
  }

  /**
   * set the band for the image to render
   */
  public void setBand(Dimension _band) {
    band = _band;
  }

  private Dimension band = new Dimension(0, 200);

  private int renderALine(int w,
                          double xstep,
                          double tmpx,
                          double tmpy,
                          double tmpz,
                          int pixY) {
    int pixX;
    int xMin = 0;
    int xMax = 2 * w;
    //xMax = w;
    for (pixX = xMin; pixX < xMax; pixX++) {
      tmpx += xstep;
      int realX = pixX / 2;
      int realY = pixY / 2;
      if (realX < band.width) continue;
      if (realX > band.height) continue;
      int c = computePixel(tmpx, tmpy, tmpz);


      addPixel(c, realX, w, realY);
      // visual feedback on progress
      // this is really slow! Using g.drawLine to draw a dot?
      //g.setColor(new Color(c, c, c));
      //g.drawLine(realX, realY, realX, realY);
    }
    return pixX;
  }

  private int computePixel(double tmpx, double tmpy, double tmpz) {
    Vec projP = new Vec(tmpx, tmpy, tmpz);
    int c = paintPix(projP);
    return c;
  }

  private void addPixel(int c, int realX, int w, int realY) {
    getPixels()[realX][realY] = (255 << 24) | (c << 16) | (c << 8) | (c << 0);
  }

  public static Image int2Image(int i[][]) {
    Toolkit tk = Toolkit.getDefaultToolkit();
    int width = i.length;
    int height = i[0].length;
    int pels[] = new int[width * height];
    for (int x = 0; x < width; x++)
      for (int y = 0; y < height; y++)
        pels[x + y * width] = i[x][y];
    return tk.createImage(
        new MemoryImageSource(width, height,
                              ColorModel.getRGBdefault(),
                              pels, 0, width));
  }

  public Image int2SubImage(int i[][]) {
    int startx = band.width;
    int endx = band.height;
    int xWidth = endx - startx;
    int sx = 0;
    int subImage[][] = new int[xWidth][d.height];
    for (int x = 0; x < subImage.length; x++)
      for (int y = 0; y < subImage[0].length; y++)
        subImage[x][y] = i[x + band.width][y];

    return int2Image(subImage);
  }

  public int[][] int2SubInt(int i[][]) {
    int startx = band.width;
    int endx = band.height;
    int xWidth = endx - startx;
    int sx = 0;
    int subImage[][] = new int[xWidth][d.height];
    for (int x = 0; x < subImage.length; x++)
      for (int y = 0; y < subImage[0].length; y++)
        subImage[x][y] = i[x + band.width][y];

    return subImage;
  }

  public int[][] getSubPixels() {
    doTheWork();
    return int2SubInt(pixels);
  }

  public Image getImage() {
    doTheWork();
    return //int2Image(pixels);
        int2SubImage(pixels);
  }

  public Image getSubBand() {
    doTheWork();
    Toolkit tk = Toolkit.getDefaultToolkit();
    int xSubImageWidth = band.height - band.width;
    int ySubImageHeight = d.height;
    int subImage[] = new int[xSubImageWidth * ySubImageHeight];
    int i = 0;
    for (int y = 0; y < d.height; y++)
      for (int x = band.height; x < band.width; x++)
          //subImage[i++] = pixels[x + d.width * y];
        System.out.println("dimension=" + band);

    return tk.createImage(
        new MemoryImageSource(xSubImageWidth, xSubImageWidth,
                              ColorModel.getRGBdefault(),
                              subImage, band.height, band.width));
    // 0 is offset...in pixels.
    // band.width, band,height, xmin, xmax.
  }

  private int paintPix(Vec projP) {
    double t[] = new double[1];
    int color = 0;
    Vec R1 = new Vec(projP);
    R1.sub(scene.VRP);
    R1.normalize();

    t[0] = 0.0;

    int obj = intersectObjects(scene.VRP, R1, t, 0, false);

    /* if it does then find out how to paint the pixel */
    if (t[0] > 0.0) {
      color = ((Target) scene.getElementAt(obj)).shade(obj,
                                                       R1, t);
    }

    color += 16;  // ambient light

    if (color > 255)
      color = 255;

    return color;
  }

  private int intersectObjects(Vec R0, Vec R1, double result[], int object,
                               boolean shadowCheck) {
    double minDist = 0.0, dist;
    int hit = -1;

    for (int i = 0; i < scene.getSize(); i++) {
      if ((shadowCheck == true) && (object == i))
        continue;
      dist = ((Target) scene.getElementAt(i)).intersectTest(R0,
                                                            R1, i);

      if (dist == 0.0)
        continue;

      /* save the first t */
      if ((minDist == 0.0) && (dist > 0.0)) {
        minDist = dist;
        hit = i;
      } else if ((dist > 0.0) && (dist < minDist)) {
        minDist = dist;
        hit = i;
      }
    }
    result[0] = minDist;
    return hit;
  }

  public int[][] getPixels() {
    return pixels;
  }
}