/*
 * 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).

   @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-camera coordinate transformation.
               final Model model1 = Model2Camera.model2camera(position);

               logVertexList("1. Camera     ", model1);

               // 2. Apply the Camera's projection transformation.
               final Model model2 = Projection.project(model1, scene.camera);

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

               // 3. Clip primitives to the camera's view rectangle.
               final Model model3 = Clip.clip(model2);

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

               // 4. Apply the image-plane to pixel-plane transformation.
               final Model model4 = Viewport.imagePlane2pixelPlane(model3, vp);

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

               // 5. Rasterize every visible primitive into pixels.
               Rasterize.rasterize(model4, 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();
   }
}
