package graphics.tracer;

class Sphere implements Target {
  private Vec3f center;
  private float radius;
  private float radiusSq;
  private static Scene scene;

  public static Sphere makeSphere(
      double x, double y, double z, double r) {
    return
        new Sphere(
            (float) x,
            (float) y,
            (float) z,
            (float) r);
  }

  public Sphere(float x, float y, float z, float r) {
    center = new Vec3f(x, y, z);
    radius = r;
    radiusSq = r * r;
  }

  public Vec3f getCenter() {
    return center;
  }

  public float getRadius() {
    return radius;
  }


  public static final float sqrt(float f) {
    return (float) Math.sqrt(f);
  }

  public static final float sqrt(double f) {
    return (float) Math.sqrt(f);
  }

  public Sphere(Vec3f v, float r) {
    center = new Vec3f(v);
    radius = r;
    radiusSq = r * r;
  }


  public void initScene(Scene s) {
    scene = s;
  }

  public boolean initScene() {
    return ((scene == null) ? false : true);
  }

  public float intersectTest(Vec3f R0, Vec3f R1, int object) {

    float t,          /* where the ray intersects */
        loc,        /* square distance from center of sphere to projP */
        tca,        /* how far is the 'closest approach' from VRP */
        thc;        /* length sqare of the half chord */
    Vec3f vecoc;      /* vector to the center of the sphere from VRP */
    boolean inside = false;

    /* use the closest approach algorithm */
    vecoc = new Vec3f(center);
    vecoc.sub(R0);
    loc = vecoc.dotProduct(vecoc);

    if (loc <= radiusSq)
      inside = true;

    tca = vecoc.dotProduct(R1);   /* find the closest approach */

    if ((inside != true) && (tca <= 0.0))
      return (0);  /* object is behind the VRP */

    /* compute the half chord square from the ray intersection to the
       intersection normal. */
    thc = (float) ((tca * tca) + radiusSq - loc);
    if (thc < 0)
      return (0);   /* ray misses the sphere */

    /* find the ray intersection */
    if (inside == true)
      t = tca + sqrt(thc);
    else
      t = tca - sqrt(thc);

    return (t);
  }

  public int shade(int object, Vec3f R1, float t[]) {

    Vec3f intersection, normal, lightSource;

    float tShadow[] = new float[1];
    tShadow[0] = 0;

    /* calculate the intersection POINT on the object */
    intersection = new Vec3f(R1);
    intersection.mult(t[0]);
    intersection.add(scene.getEye());

    /* find the normal vector from sphere's center to the intersection */
    normal = new Vec3f(intersection);
    normal.sub(center);
    normal.normalize();

    /* locate the light source from intersection */
    lightSource = new Vec3f(scene.getLight());
    lightSource.sub(intersection);
    lightSource.normalize();

    /* check if the light can be "seen" by the intersection point */
    intersectObjects(
        intersection, lightSource, tShadow, object, true);

    float intensity = lightSource.dotProduct(normal);
    if (intensity < 0)
      intensity = 0;

    if (tShadow[0] > 0) /* something is in the way */
      intensity = SceneConsts.Gs * intensity;
    // pixel gets ambient light only
    // pixel gets all kinds of light
    else {
      intensity = intensity * SceneConsts.Ip * SceneConsts.GKd;
    }

    intensity = intensity + SceneConsts.GIa * SceneConsts.GKa;
    if (intensity > 1)
      intensity = 1;

    /* find the corresponding color in the color lookup table */
    intensity = intensity * 255;

    return ((int) intensity);
  }

  int intersectObjects(
      Vec3f R0, Vec3f R1,
      float result[], int object,
      boolean shadowCheck) {
    float minDist = 0;
    float dist;
    int hit = -1;

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

      if (dist == 0) continue;

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

}