/*
 * 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.
<p>
   This second version of the rendering pipeline does each stage
   of the pipeline on the entire {@link Scene} data structure
   before it moves on to the next pipeline stage. Each stage of
   this pipeline produces a new {@link Scene} object that is
   the transformation of the {@link Scene} object from the
   previous stage. These intermediate {@link Scene} objects are
   made publicly available for post rendering special effects.

   @see Pipeline
*/
public final class Pipeline2
{
   // Make the three intermediate Scene objects
   // available for special processing.
   public static Scene scene1 = null; // Will hold the result of stage 1.
   public static Scene scene2 = null; // Will hold the result of stage 2.
   public static Scene scene3 = null; // Will hold the result of stage 3.
   public static Scene scene4 = null; // Will hold the result of stage 4.
   public static Scene scene5 = null; // Will hold the result of stage 5.
   public static Scene scene6 = null; // Will hold the result of stage 6.

   /**
      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 (Pipeline 2): " + scene.name);

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

      // 1. Apply each Position's model-to-view coordinate transformation.
      scene1 = new Scene(scene.name, scene.camera);
      logMessage("=== 1. Begin model-to-view transformation of Scene.");
      for (final Position position : scene.positionList)
      {
         PipelineLogger.debugPosition = position.debug;

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

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

            if ( position.getModel().visible )
            {
               logMessage("======= 1. Model-to-view transform of: " + position.getModel().name);

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

               final Model tempModel = Model2View.model2view(position);

               logVertexList("1. View", tempModel);

               scene1.addPosition( new Position(tempModel, position.name) );

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

            logMessage("===== 1. End position: " + position.name);
         }
         else
         {
            logMessage("===== 1. Hidden position: " + position.name);
         }
      }
      logMessage("=== 1. End model-to-view transformation of Scene.");


      // 2. Apply the Camera's normalizing view-to-camera coordinate transformation.
      scene2 = new Scene(scene.name, scene.camera);
      logMessage(""); // blank line
      logMessage("=== 2. Begin view-to-camera transformation of Scene.");
      for (final Position position : scene1.positionList)
      {
         logMessage("===== 2. View-to-camera transform of position: " + position.name);
         logMessage("======= 2. View-to-camera transform of model: " + position.getModel().name);

         final Model tempModel = View2Camera.view2camera(position.getModel(),
                                                         scene.camera);

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

         scene2.addPosition( new Position(tempModel, position.name) );

         logMessage("======= 2. End Model: " + position.getModel().name);
         logMessage("===== 2. End position: " + position.name);
      }
      logMessage("=== 2. End view-to-camera transformation of Scene.");


      // 3. Near Clip.
      scene3 = new Scene(scene.name, scene.camera);
      logMessage(""); // blank line
      logMessage("=== 3. Begin near clipping of Primitives.");
      for (final Position position : scene2.positionList)
      {
         logMessage("===== 3. Near Clip position: " + position.name);
         logMessage("======= 3. Near Clip model: " + position.getModel().name);

         final Model tempModel = NearClip.clip(position.getModel(),
                                               scene.camera);

         logVertexList("3. Near Clipped", tempModel);
         logColorList("3. Near_Clipped", tempModel);
         logPrimitiveList("3. Near Clipped", tempModel);

         scene3.addPosition( new Position(tempModel, position.name) );

         logMessage("======= 3. End Model: " + position.getModel().name);
         logMessage("===== 3. End position: " + position.name);
      }
      logMessage("=== 3. End near clipping of Scene.");


      // 4. Apply the Camera's projection transformation.
      scene4 = new Scene(scene.name, scene.camera);
      logMessage(""); // blank line
      logMessage("=== 4. Begin projection transformation of Scene.");
      for (final Position position : scene3.positionList)
      {
         logMessage("===== 4. Project position: " + position.name);
         logMessage("======= 4. Project model: " + position.getModel().name);

         final Model tempModel = Projection.project(position.getModel(),
                                                    scene.camera);

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

         scene4.addPosition( new Position(tempModel, position.name) );

         logMessage("======= 4. End Model: " + position.getModel().name);
         logMessage("===== 4. End position: " + position.name);
      }
      logMessage("=== 4. End projection transformation of Scene.");


      // 5. Clip.
      scene5 = new Scene(scene.name, scene.camera);
      logMessage(""); // blank line
      logMessage("=== 5. Begin clipping of Primitives.");
      for (final Position position : scene4.positionList)
      {
         logMessage("===== 5. Clip position: " + position.name);
         logMessage("======= 5. Clip model: " + position.getModel().name);

         final Model tempModel = Clip.clip(position.getModel());

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

         scene5.addPosition( new Position(tempModel, position.name) );

         logMessage("======= 5. End Model: " + position.getModel().name);
         logMessage("===== 5. End position: " + position.name);
      }
      logMessage("=== 5. End clipping of Scene.");


      // 6. Apply the image-plane to pixel-plane transformation.
      scene6 = new Scene(scene.name, scene.camera);
      logMessage(""); // blank line
      logMessage("=== 6. Begin image-plane to pixel-plane transformation of Scene.");
      for (final Position position : scene5.positionList)
      {
         logMessage("===== 6. Transform position: " + position.name);
         logMessage("======= 6. Transform model: " + position.getModel().name);

         final Model tempModel = Viewport.imagePlane2pixelPlane(
                                                      position.getModel(), vp);

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

         scene6.addPosition( new Position(tempModel, position.name) );

         logMessage("======= 6. End Model: " + position.getModel().name);
         logMessage("===== 6. End position: " + position.name);
      }
      logMessage("=== 6. End image-plane to pixel-plane transformation of Scene.");


      // 7. Rasterize and clip every visible primitive into pixels.
      logMessage(""); // blank line
      logMessage("=== 7. Begin rasterization of Scene.");
      for (final Position position : scene6.positionList)
      {
         logMessage("===== 7. Rasterize position: " + position.name);
         logMessage("======= 7. Rasterize model: " + position.getModel().name);

         Rasterize.rasterize(position.getModel(), vp);

         logMessage("======= 7. End Model: " + position.getModel().name);
         logMessage("===== 7. End position: " + position.name);
      }
      logMessage("=== 7. End rasterization of Scene.");

      logMessage("== End Rendering of Scene (Pipeline 2): " + scene.name);
   }



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