/*
 * Renderer 3. 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 third rendering pipeline. It adds a fifth
   stage, {@link Clip}, between the {@link Projection} and
   {@link Viewport} stages.
   <ol>
      <li>The {@link Model2Camera} transformation 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 clipping stage clips off the parts of a
   {@link renderer.scene.primitives.Primitive} that extend
   out of the {@link Camera}'s view rectangle (within the
   {@link Camera}'s image-plane).
<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.

   /**
      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-camera coordinate transformation.
      scene1 = new Scene(scene.name, scene.camera);
      logMessage("=== 1. Begin model-to-camera 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-camera transform of: " + position.getModel().name);

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

               final Model tempModel = Model2Camera.model2camera(position);

               logVertexList("1. Camera", 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-camera transformation of Scene.");


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

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

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

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

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


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

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

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

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

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


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

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

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

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

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


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

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

         logMessage("======= 5. End Model: " + position.getModel().name);
         logMessage("===== 5. End position: " + position.name);
      }
      logMessage("=== 5. 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();
   }
}
