/// LSU EE 4702-1 (Fall 2022), GPU Programming
//
 /// Homework 2 --- SOLUTION
//
//   Modify this file and no others.
//   Assignment: https://www.ece.lsu.edu/koppel/gpup/2022/hw02.pdf
//   Solution: https://www.ece.lsu.edu/koppel/gpup/2022/hw02_sol.pdf
//

#include <gp/coord.h>
#include <gp/colors.h>
#include "frame_buffer.h"

struct Scene {
  vector<pCoor> coors_os;
  vector<uint32_t> colors;
};


void
prep_scene(Scene& s, const HW02_Info& hw02_info)
{
  ///
  /// Prepare Triangles -- Put Problem 1 and 2 Solutions Below
  ///

  // List of coordinates in object space and list of colors.
  //
  vector<pCoor>& coors_os = s.coors_os;
  vector<uint32_t>& colors = s.colors;

  // A starter selection of colors. For some, a lifetime collection.
  //
  uint32_t color_white = 0xffffff;
  uint32_t color_red [[maybe_unused]] = 0xff0000;
  uint32_t color_blue [[maybe_unused]] = 0xff;
  uint32_t color_green [[maybe_unused]] = 0xff00;
  //
  // For more colors see ../../../../include/colors.h
  // Or visit https://www.w3schools.com/colors/colors_picker.asp

  /// Insert First Triangle
  //
  // Insert coordinates of a triangle into coordinate list,
  // and insert its color into a color list.
  //
  coors_os << pCoor( 0, 0, 0 ) << pCoor( 9, 6, -9 ) << pCoor( 0, 7, -5 );
  colors << color_white;

  /// Insert Second Triangle
  //
  // Add coordinates and color of another triangle
  //
  coors_os << pCoor(7,4,-2) << pCoor(-3,5,-9) << pCoor(9,2,-2);
  colors << color_blue;

  /// Insert Two More Triangles
  //
  // Add a square consisting of a red and green triangle.
  //
  colors << color_lsu_spirit_gold << color_lsu_spirit_purple;
  coors_os << pCoor(-7,0,-2) << pCoor(-7,2,-2) << pCoor(-5,0,-2);
  coors_os << pCoor(-7,2,-2) << pCoor(-5,2,-2) << pCoor(-5,0,-2);

  /// Problem 1
  //
  //  Draw the v-like figure.

  /// SOLUTION -- Problem 1
  //
  pCoor pc2(-3.8,2,-2), pc3(-2.75,0,-2);
  pCoor pc4 = pc3 + 0.2 * pVect(pc3,pc2);
  coors_os << pCoor(-4,2,-2) << pc2               << pc3;
  coors_os << pCoor(-4,2,-2) << pCoor(-3.25,0,-2) << pc3;
  coors_os << pc4            << pCoor(-2,2,-2)    << pc3;
  colors << color_green << color_lsu_spirit_purple << color_lsu_spirit_gold;


  /// Problem 2
  //
  //  Draw the paddle wheel.

  // Use, but do not modify the variables below.
  const pCoor p1 [[maybe_unused]] = hw02_info.p1;
  const pCoor p2 [[maybe_unused]] = hw02_info.p2;
  const pCoor p3 [[maybe_unused]] = hw02_info.p3;
  const pCoor p4 [[maybe_unused]] = hw02_info.p4;
  const int n_pieces [[maybe_unused]] = hw02_info.n_pieces;
  // Use, but do not modify the variables above.


  /// SOLUTION -- Problem 2

  pVect vx(p1,p2), vz(p1,p4);
  pNorm ax(vx), ay( cross(vz,ax) );
  pVect vy = ay * ax.magnitude;
  float r2or1 = pNorm(p1,p3).magnitude / ax.magnitude;
  float delta_theta = 2 * M_PI / n_pieces;

  for ( int i=0; i<n_pieces; i++ )
    {
      float theta = i * delta_theta;
      pVect v = vx * cosf(theta) + vy * sinf(theta);
      pVect v2 = v * r2or1;
      coors_os <<  p1 + v   <<  p1 + v2  <<  p4 + v;
      coors_os <<  p1 + v2  <<  p4 + v2  <<  p4 + v;
      colors << color_lemon_chiffon << color_lemon_chiffon;
    }
}

void
render_ray_trace_os
(pFrame_Buffer& demo_frame_buffer, const HW02_Info& hw02_info)
{
  Scene s;
  vector<pCoor>& coors_os = s.coors_os;
  vector<uint32_t>& colors = s.colors;
  prep_scene(s,hw02_info);

  ///
  /// Put Problem 3 Solution in this Routine, Further Below
  ///

  ///
  /// Transform Triangle Coordinates from Object Space to Eye Space
  ///

  // Specifications of User's Monitor (Or Window)
  //
  const uint win_width = demo_frame_buffer.width_get();
  const uint win_height = demo_frame_buffer.height_get();
  const float aspect = 1.0 * win_width / win_height;
  //
  // win_width and win_height are in window (pixel) space.

  // These are the size of the monitor in eye space.
  //
  const float width_m = 1.6;
  const float height_m = width_m / aspect;
  const float qn = 1; // Distance from eye to monitor along z axis.
  //
  // width_m and height_m are in eye space.

  // Prepare a transformation matrix that will transform object-space
  // coordinates to eye-space coordinates.
  //
  pMatrix_Translate center_eye(-hw02_info.eye_location);
  pMatrix_Rotation rotate_eye(hw02_info.eye_direction,pVect(0,0,-1));
  pMatrix eye_from_object = rotate_eye * center_eye;
  pMatrix object_from_eye = pMatrix_Translate(hw02_info.eye_location)
    * pMatrix_Rotation(pVect(0,0,-1),hw02_info.eye_direction);

  // Convert triangle coordinates from object space to eye space.
  //
  vector<pCoor> coors_es = eye_from_object * coors_os;
  pCoor light_location_o = object_from_eye*hw02_info.light_location;

  // Coordinates of Window Corners in Eye Space
  //
  pCoor window_ll_e( -width_m/2, -height_m/2, -qn);  // Lower-Left
  pCoor window_ul_e( -width_m/2,  height_m/2, -qn);  // Upper-Left
  pCoor window_lr_e( +width_m/2, -height_m/2, -qn);  // Lower-Right

  /// Problem 3 -- These may be helpful.
  //
  pCoor window_ll_o = object_from_eye * window_ll_e;
  pCoor window_ul_o = object_from_eye * window_ul_e;
  pCoor window_lr_o = object_from_eye * window_lr_e;

  /// Problem 3 -- Maybe add some code here.
  //
  /// SOLUTION -- A SMALL PART OF Problem 3 

  pCoor eye_location = hw02_info.eye_location; // For convenience.
  pVect window_dx_o = pVect( window_ll_o, window_lr_o ) / win_width;
  pVect window_dy_o = pVect( window_ll_o, window_ul_o ) / win_height;
  //
  // Compute object-space versions of dx and dy.


  ///
  /// Render Triangles
  ///
#pragma omp parallel for
  // Outer Loops (yw, xw): Iterate over each pixel.
  //
  for ( uint yw = 0;  yw < win_height; yw++ )
    for ( uint xw = 0;  xw < win_width; xw++ )
      {
        /// Problem 3 -- Compute px_o and ray in object space.
        //
        //  [ ] Do not use transformation matrices here.

        /// SOLUTION -- Problem 3

        // Object-Space Coordinate of Pixel.
        //
        pCoor px_o = window_ll_o + window_dx_o * xw + window_dy_o * yw;
        //
        // Note that object space coordinate is now computed using
        // object-space window positions and derivatives (dx, dy).

        // Ray From Eye to Pixel.
        //
        pVect ray( eye_location, px_o );
        //         ^^^^^^^^^^^^
        /// SOLUTION -- Problem 3
        //  Compute ray from eye_location rather than the origin.
        //  Change is underlined above.

        float tmin = 1e10;  // Distance to closest triangle found so far.
        auto ic = colors.begin();

        // Inner Loop (it): Iterate over each triangle.
        //
        for ( auto it = coors_os.begin(); it != coors_os.end(); )
          {
            // Get Triangle and its Color
            //
            pCoor o0 = *it++,  o1 = *it++,  o2 = *it++;
            uint32_t color = *ic++;

            /// Problem 3 -- Fix computation of t and s.

            // Find where ray intercepts plane defined by triangle.
            //
            pVect tn(o0,o1,o2);  // Triangle normal.

            float t = dot(  pVect( eye_location, o0 ),  tn ) / dot( ray, tn );
            //                     ^^^^^^^^^^^^
            /// SOLUTION -- Problem 3  underlined above.
            //
            //  Change vector from pVect(o0) to pVect(eye_location,o0).

            // t indicates distance from eye to intercept point in
            // units of ray lengths.

            // Skip this triangle if a closer triangle already found ..
            // ..or if it is too close to the eye.
            //
            if ( t >= tmin || t < 1 ) continue;

            /// Problem 3 -- Fix computation of s.

            // Compute the coordinate of the line/plane intercept.
            //
            /// SOLUTION -- Problem 3
            //  Change s = t * ray; to s = eye_location + t * ray.
            //  That change is underlined below.
            //
            pCoor s = eye_location + t * ray;
            //        ^^^^^^^^^^^^^^
            // Is s inside the triangle? We don't know yet.

            // Compute a barycentric coordinate of s.
            //
            pVect v01(o0,o1), v02(o0,o2), v0s(o0,s);
            pVect v01i = cross(tn,v01);  // Vector normal to edge o0,o1
            float b2 = dot(v0s,v01i) / dot( v02,v01i );
            if ( b2 < 0 || b2 > 1 ) continue;
            //
            // Note: If b2 < 0 then s is on the wrong side of edge o0,o1.
            // If b2 > 1 then s is on the right side, but too far away.

            // Compute another barycentric coordinate of s.
            //
            pVect v02i = cross(tn,v02);
            float b1 = dot( v0s, v02i) / dot(v01,v02i);
            if ( b1 < 0 || b1 + b2 > 1 ) continue;

            // At this point we know that s is in the triangle.

            // Compute lighting.
            pNorm s_to_l(s,light_location_o);
            float ill = max( 0.1f, fabs( dot(s_to_l,pNorm(tn)) ) );
            pColor ci = pColor(color) * ill * hw02_info.light_intensity;

            // Update the minimum distance and write the frame buffer.
            //
            tmin = t;
            demo_frame_buffer[ xw + yw * win_width ] = ci.int_rgb();
          }
      }
}

void
render_ray_trace
(pFrame_Buffer& demo_frame_buffer, const HW02_Info& hw02_info)
{
  Scene s;
  vector<pCoor>& coors_os = s.coors_os;
  vector<uint32_t>& colors = s.colors;
  prep_scene(s,hw02_info);

  /// Do not modify this routine. Modify render_trace_os for Problem 3.

  ///
  /// Transform Triangle Coordinates from Object Space to Eye Space
  ///

  // Specifications of User's Monitor (Or Window)
  //
  const uint win_width = demo_frame_buffer.width_get();
  const uint win_height = demo_frame_buffer.height_get();
  const float aspect = 1.0 * win_width / win_height;
  //
  // win_width and win_height are in window (pixel) space.

  // These are the size of the monitor in eye space.
  //
  const float width_m = 1.6;
  const float height_m = width_m / aspect;
  const float qn = 1; // Distance from eye to monitor along z axis.
  //
  // width_m and height_m are in eye space.

  // Prepare a transformation matrix that will transform object-space
  // coordinates to eye-space coordinates.
  //
  pMatrix_Translate center_eye(-hw02_info.eye_location);
  pMatrix_Rotation rotate_eye(hw02_info.eye_direction,pVect(0,0,-1));
  pMatrix eye_from_object = rotate_eye * center_eye;

  // Convert triangle coordinates from object space to eye space.
  //
  vector<pCoor> coors_es = eye_from_object * coors_os;

  // Coordinates of Corners of User Monitor Window in Eye Space
  //
  pCoor window_ll_e( -width_m/2, -height_m/2, -qn);  // Lower-Left
  pCoor window_ul_e( -width_m/2,  height_m/2, -qn);  // Upper-Left
  pCoor window_lr_e( +width_m/2, -height_m/2, -qn);  // Lower-Right

  pVect window_dx_e = pVect( window_ll_e, window_lr_e ) / win_width;
  pVect window_dy_e = pVect( window_ll_e, window_ul_e ) / win_height;


  ///
  /// Render Triangles
  ///

#pragma omp parallel for
  // Outer Loops (yw, xw): Iterate over each pixel.
  //
  for ( uint yw = 0;  yw < win_height; yw++ )
    for ( uint xw = 0;  xw < win_width; xw++ )
      {
        /// Do not modify this routine! Modify render_trace_os for Problem 3.

        // Eye-Space Coordinate of Pixel.
        //
        pCoor px_e = window_ll_e + window_dx_e * xw + window_dy_e * yw;

        // Ray From Eye to Pixel.
        //
        pVect ray( pCoor(0,0,0), px_e );

        float tmin = 1e10;  // Distance to closest triangle found so far.
        auto ic = colors.begin();

        /// Do not modify this routine! Modify render_trace_os for Problem 3.

        // Inner Loop (it): Iterate over each triangle.
        //
        for ( auto it = coors_es.begin(); it != coors_es.end(); )
          {
            // Get Triangle and its Color
            //
            pCoor e0 = *it++,  e1 = *it++,  e2 = *it++;
            uint32_t color = *ic++;

            // Find where ray intercepts plane defined by triangle.
            //
            pVect tn(e0,e1,e2);  // Triangle normal.
            float t = dot( pVect(pCoor(0,0,0),e0), tn ) / dot( ray, tn );
            //
            // t indicates distance from eye to intercept point in
            // units of ray lengths.

            /// Do not modify this routine! Modify render_trace_os for Problem 3.

            // Skip this triangle if a closer triangle already found ..
            // ..or if it is too close to the eye.
            //
            if ( t >= tmin || t < 1 ) continue;

            // Compute the coordinate of the line/plane intercept.
            //
            pCoor s = t * ray;
            //
            // Is s inside the triangle? We don't know yet.

            // Compute a barycentric coordinate of s.
            //
            pVect v01(e0,e1), v02(e0,e2), v0s(e0,s);
            pVect v01i = cross(tn,v01);  // Vector normal to edge e0,e1
            float b2 = dot(v0s,v01i) / dot( v02,v01i );
            if ( b2 < 0 || b2 > 1 ) continue;
            //
            // Note: If b2 < 0 then s is on the wrong side of edge e0,e1.
            // If b2 > 1 then s is on the right side, but too far away.

            // Compute another barycentric coordinate of s.
            //
            pVect v02i = cross(tn,v02);
            float b1 = dot( v0s, v02i) / dot(v01,v02i);
            if ( b1 < 0 || b1 + b2 > 1 ) continue;

            // At this point we know that s is in the triangle.

            // Compute lighting.
            pNorm s_to_l( s, hw02_info.light_location );
            float ill = max( 0.1f, fabs( dot(s_to_l,pNorm(tn)) ) );
            pColor ci = pColor(color) * ill * hw02_info.light_intensity;

            // Update the minimum distance and write the frame buffer.
            //
            tmin = t;
            demo_frame_buffer[ xw + yw * win_width ] = ci.int_rgb();
          }
      }
}


int
main(int argc, char **argv)
{
  pFrame_Buffer demo_frame_buffer(argc,argv);
  Render render( demo_frame_buffer );
  RENDER_INSERT(render, render_ray_trace);
  RENDER_INSERT(render, render_ray_trace_os);
  render.run();
  return 0;
}