/*
 * Renderer 5. The MIT License.
 * Copyright (c) 2022 rlkraft@pnw.edu
 * See LICENSE for details.
*/

package renderer.pipeline;

import renderer.scene.*;
import renderer.scene.util.CheckModels;
import renderer.framebuffer.*;
import static renderer.pipeline.PipelineLogger.*;

import java.awt.Color;

/**
   This renderer takes as its input a {@link Scene} data structure
   and a {@link FrameBuffer.Viewport} within a {@link FrameBuffer}
   data structure. This renderer mutates the {@link FrameBuffer.Viewport}
   so that it is filled in with the rendered image of the geometric
   scene represented by the {@link Scene} object.
<p>
   This implements our fifth rendering pipeline. In the previous
   renderer the first pipeline stage was Model2Camera. This renderer
   splits that stage into two stages, {@link Model2View} and
   {@link View2Camera}.
   <ol>
      <li>The {@link Model2View} transformation stage.
      <li>The {@link View2Camera} transformation stage.
      <li>The {@link NearClip} stage.
      <li>The {@link Projection} transformation stage.
      <li>The {@link Clip} stage.
      <li>The {@link Viewport} transformation stage.
      <li>The {@link Rasterize} stage.
   </ol>
   The {@link View2Camera} stage acts as a view volume normalization stage
   that converts the {@link Camera}'s configurable view volume into the
   normalized (standard) view volume used by the clipping stages. Coordinates
   relative to the {@link Camera}'s arbitrary view volume are called "view
   coordinates" and coordinates relative to the normalized view volume are
   called "camera coordinates". The {@link View2Camera} stage stage converts
   vertex coordinates from view coordinates to camera coordinates. The
   {@link View2Camera} stage allows the camera's view rectangle
   (in view coordinates) to take on any aspect ratio.

   @see Pipeline2
*/
public final class Pipeline
{
   /**
      Mutate the {@link FrameBuffer}'s default {@link FrameBuffer.Viewport}
      so that it holds the rendered image of the {@link Scene} object.

      @param scene  {@link Scene} object to render
      @param fb     {@link FrameBuffer} to hold rendered image of the {@link Scene}
   */
   public static void render(final Scene scene, final FrameBuffer fb)
   {
      render(scene, fb.vp); // Render into the default viewport.
   }


   /**
      Mutate the given {@link FrameBuffer.Viewport} so that it
      holds the rendered image of the {@link Scene} object.

      @param scene  {@link Scene} object to render
      @param vp     {@link FrameBuffer.Viewport} to hold rendered image of the {@link Scene}
   */
   public static void render(final Scene scene, final FrameBuffer.Viewport vp)
   {
      PipelineLogger.debugScene = scene.debug;

      // Check if the models in this scene have any obvious problems.
      final boolean ok = CheckModels.check(scene);
      if (! ok)
      {
         System.out.println(
            "**** WARNING: Even though there are problems with this Scene, the");
         System.out.println(
            "**** WARNING: renderer will try to continue with rendering it.");
         System.out.flush();
      }

      logMessage("\n== Begin Rendering of Scene: " + scene.name);

      logMessage("-- Current Camera:\n" + scene.camera);

      // For every Position in the Scene, render the Position's Model.
      for (final Position position : scene.positionList)
      {
         PipelineLogger.debugPosition = position.debug;

         if ( position.visible )
         {
            logMessage("==== Render position: " + position.name);

            logMessage("------ Translation vector = " + position.getTranslation());

            if ( position.getModel().visible )
            {
               logMessage("====== Render model: " + position.getModel().name);

               logVertexList("0. Model        ", position.getModel());

               // 1. Apply the Position's model-to-view coordinate transformation.
               final Model model1 = Model2View.model2view(position);

               logVertexList("1. View         ", model1);

               // 2. Apply the Camera's normalizing view-to-camera coordinate transformation.
               final Model model2 = View2Camera.view2camera(model1, scene.camera);

               logVertexList("2. Camera       ", model2);
               logColorList("2. Camera       ", model2);
               logPrimitiveList("2. Camera       ", model2);

               // 3. Clip primitives to the camera's near plane.
               final Model model3 = NearClip.clip(model2, scene.camera);

               logVertexList("3. Near_Clipped", model3);
               logColorList("3. Near_Clipped", model3);
               logPrimitiveList("3. Near_Clipped", model3);

               // 4. Apply the Camera's projection transformation.
               final Model model4 = Projection.project(model3, scene.camera);

               logVertexList("4. Projected   ", model4);
               logColorList("4. Projected   ", model4);
               logPrimitiveList("4. Projected   ", model4);

               // 5. Clip primitives to the camera's view rectangle.
               final Model model5 = Clip.clip(model4);

               logVertexList("5. Clipped     ", model5);
               logColorList("5. Clipped     ", model5);
               logPrimitiveList("5. Clipped     ", model5);

               // 6. Apply the image-plane to pixel-plane transformation.
               final Model model6 = Viewport.imagePlane2pixelPlane(model5, vp);

               logVertexList("6. Pixel-plane", model6);
               logPrimitiveList("6. Pixel-plane", model6);
               logColorList("6. Pixel-plane", model6);

               // 7. Rasterize every visible primitive into pixels.
               Rasterize.rasterize(model6, vp);

               logMessage("====== End model: " + position.getModel().name);
            }
            else
            {
               logMessage("====== Hidden model: " + position.getModel().name);
            }

            logMessage("==== End position: " + position.name);
            logMessage(""); // blank line
         }
         else
         {
            logMessage("==== Hidden position: " + position.name);
            logMessage(""); // blank line
         }
      }
      logMessage("== End Rendering of Scene: " + scene.name);
   }



   // Private default constructor to enforce noninstantiable class.
   // See Item 4 in "Effective Java", 3rd Ed, Joshua Bloch.
   private Pipeline() {
      throw new AssertionError();
   }
}
