/// LSU EE 4702-1 (Fall 2022), GPU Programming
//
//
 /// Homework 1 -- SOLUTION
 //
 //  Assignment: https://www.ece.lsu.edu/koppel/gpup/2022/hw01.pdf
 //
 //  This code is based on cpu-only/demo-01-frame-buffer.cc

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

void
line_draw
( pFrame_Buffer& fb, int x0, int y0, int x1, int y1, uint32_t co)
{
  /// Problem 2
  //
  //  Write the frame buffer so that it shows a line from (x0,y0) to
  //  (x1,y1) of color co.
  //
  //  [✔] There should not be gaps between the pixels.
  //  [✔] Try to avoid writing the same pixel multiple times.

  /// SOLUTION -- Problem 2

  // Insure that x0 < x1.
  //
  if ( x0 > x1 ) { swap(x0,x1); swap(y0,y1); }
  //
  // Note that if x is swapped then y must be swapped to. We need
  // x0 < x1, but we don't care whether y0 < y1.

  int delta_x = x1 - x0;
  int delta_y = y1 - y0;

  float dydx = delta_y / float(max(1,delta_x));

  int width = fb.width_get();
  int inc_y = dydx < 0 ? -1 : 1;

  for ( float x = x0, y = y0;  x<=x1;  x++ )
    {
      float ynext = y + dydx;
      for ( int yi=y; ; yi += inc_y )
        {
          fb[ yi * width + x ] = co;
          if ( yi == int(ynext) ) break;
        }
      y = ynext;
    }
}


void
line_draw_b
( pFrame_Buffer& fb, int x0, int y0, int x1, int y1, uint32_t co)
{
  /// SOLUTION -- Problem 2, Alernative Solution: Use Bresenham's Algorithm
  //
  // The advantage of Bresenham's algorithm is that it draws a line
  // using only integer arithmetic.

  // Width of window.
  const int width = fb.width_get();

  const int dx = abs(x1-x0), dy = abs(y1-y0);
  const bool steep = dy > dx;
  const int inc_x = x1 >= x0 ? 1     : -1;
  const int inc_y = y1 >= y0 ? width : -width;
  int da = dx,       db = dy;
  int a = x0,        b = y0*width;
  int inc_a = inc_x, inc_b = inc_y;
  if ( steep ) { swap(da,db); swap(a,b); swap(inc_a,inc_b); }

  for ( int i = 0,  D = 2*db - da;  i <= da;  i++ )
    {
      fb[ a + b ] = co;
      if ( D > 0 ) { b += inc_b;  D += 2 * ( db - da); } else { D += 2 * db; }
      a += inc_a;
    }
}


void
render_hw01(pFrame_Buffer& fb)
{
  // Window Size, in Pixels
  //
  const int win_width = fb.width_get();
  const int win_height = fb.height_get();

  //                           RRGGBB
  const uint32_t color_red = 0xff0000;

  /// Compute square coordinates based on window size.
  //
  const int sq_slen = min(win_width,win_height) * .45;
  const int sq_x0 = sq_slen * 0.1,   sq_y0 = win_height - 150;
  const int sq_x1 = sq_x0 + sq_slen, sq_y1 = sq_y0 - sq_slen;
  //
  // One vertex of the square is at (sq_x0,sq_y0) and the opposite
  // vertex is at (sq_x1,sq_y1).


  /// Problem 1
  //
  // Modify the code below so that the square properly uses the
  // coordinates in variables sq_x0, etc.
  //

  // Note: This is the original code.
  if ( false )
  for ( int x=100; x<500; x++ )
    {
      fb[ 100 * win_width + x ] = color_red;
      fb[ 500 * win_width + x ] = 0xffff;
      fb[ x * win_width + 100 ] = 0xff00ff;
      fb[ x * win_width + 500 ] = 0xff00;
    }

  /// SOLUTION -- Problem 1
  //
  // Draw the horizontal lines.
  //
  for ( int x=sq_x0; x<=sq_x1; x++ )
    {
      fb[ sq_y1 * win_width + x ] = color_red;
      fb[ sq_y0 * win_width + x ] = 0xffff00;
    }
  //
  // Draw the vertical lines.
  //
  for ( int y=sq_y0; y>=sq_y1; y-- )
    {
      fb[ y * win_width + sq_x0 ] = 0xff00ff;
      fb[ y * win_width + sq_x1 ] = 0xff00;
    }

  /// Demo Code: Draw Sine Waves  -- No need to modify this.
  //
  int num_waves = 4;
  float plot_ht = sq_slen/float(num_waves);
  for ( float yb = sq_y0 - plot_ht/2,
          freq = 4 * M_PI / float( win_width - 40 - sq_x1 );
        yb > sq_y1; yb -= plot_ht, freq *= 2 )
    for ( int x = sq_x1 + 20;  x < win_width - 20;  x++ )
      {
        int y = yb + sin( x * freq ) * plot_ht * .45;
        fb[ y * win_width + x ] = 0xff00;
      }


  /// Demo Code: Draw Lines Radiating From a Point -- No need to modify.
  //
  //  This code calls the line_draw routine.
  //
  int n_lines = 24;
  float c_r1 = sq_slen/6;  // Inner radius
  float c_r2 = sq_slen/2;  // Outer radius.
  pCoor c_ctr( win_width - c_r2 - 20, c_r2 + 20 ); // Center of lines.
  float delta_theta = 2 * M_PI / n_lines;
  for ( int i=0; i<n_lines; i++ )
    {
      float theta = i * delta_theta;
      pVect dir( cosf(theta), sinf(theta), 0 );
      pCoor p1 = c_ctr + c_r1 * dir;  // Point on inner radius.
      pCoor p2 = c_ctr + c_r2 * dir;  // Point on outer radius.
      line_draw(fb, p1.x, p1.y, p2.x, p2.y, 0xffffff);
    }


  /// Problem 3
  //
  //  Copy the pixels around the mouse pointer into the square,
  //  (sq_x0+1,sq_y0-1) to (sq_x1-1,sq_y1+1), so that the square shows
  //  a zoomed (magnified) version of the area around the mouse
  //  pointer. (Do not overwrite the lines drawn above.) Use variable
  //  zoom as the factor, so that each pixel under the mouse pointer
  //  is copied to 4*4=16 pixels in the square.
  //
  fb.fb_stats_off(); // Turn off "redundancy" statistics.

  // Coordinates of mouse pointer.
  //
  const int mouse_x = fb.mouse_x;
  const int mouse_y = fb.mouse_y;
  const int zoom = 4;

  /// SOLUTION -- Problem 3, Copy/Zoom Frame Buffer
  ///

  // Compute coordinates of the center of the big box.
  //
  const int x_mid = ( sq_x0 + sq_x1 ) / 2;
  const int y_mid = ( sq_y0 + sq_y1 ) / 2;

  // Compute 1/2 the length of the mouse box.
  //
  const int src_hwid = sq_slen / ( zoom * 2 );

  // Iterate using relative coordinates.
  //
  // Note: x=0,y=0  is at the center of the big box and mouse box.
  //
  for ( int y = -src_hwid;  y < src_hwid;  y++ )
    for ( int x = -src_hwid;  x < src_hwid;  x++ )
      {
        // Read the pixel from the mouse area.
        //
        uint32_t pixel = fb[ mouse_x + x + ( mouse_y + y ) * win_width ];

        // Compute the index of the lower-left part of the big box that
        // the pixel above will be written to.
        //
        int fb_idx = x_mid + x * zoom + ( y_mid + y * zoom ) * win_width;

        // Write the pixel zoom * zoom times.
        //
        for ( int yy = 0;  yy < zoom;  yy++ )
          for ( int xx = 0;  xx < zoom;  xx++ )
            fb[ fb_idx + xx + yy * win_width ] = pixel;
      }

  /// Problem 3 -- Put code to draw the blue square here (below).

  /// SOLUTION -- Problem 3, Blue Box Around Mouse Pointer
  //
  //  Use our line drawing routines to draw the box.

  line_draw( fb,
             mouse_x - src_hwid,  mouse_y + src_hwid,
             mouse_x + src_hwid,  mouse_y + src_hwid,  0xff );
  line_draw( fb,
             mouse_x - src_hwid,  mouse_y - src_hwid,
             mouse_x + src_hwid,  mouse_y - src_hwid,  0xff );
  line_draw( fb,
             mouse_x - src_hwid,  mouse_y + src_hwid,
             mouse_x - src_hwid,  mouse_y - src_hwid,  0xff );
  line_draw( fb,
             mouse_x + src_hwid,  mouse_y + src_hwid,
             mouse_x + src_hwid,  mouse_y - src_hwid,  0xff );

}


int
main(int argc, char **argv)
{
  pFrame_Buffer demo_frame_buffer(argc,argv);
  demo_frame_buffer.show(render_hw01);
  return 0;
}