/// LSU EE 4702-1 (Fall 2026), GPU Programming
//
//
 /// Homework 1 -- SOLUTION
 //
 //  Search for "SOLUTION".
 //
 //  Assignment: https://www.ece.lsu.edu/gpup/2026/hw01.pdf
 //

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

// Definitions for iVect, iCoor
#include "hw01-misc.h"

struct HW01_Data {
  HW01_Data():inited(false){};
  bool inited;
  bool tryout0, tryout1;
  int zoom;
  double t_frame_last, t_sim;
  bool paused;

};

// Global objects like this are bad style.
HW01_Data hw01_data;


void
line_draw
( pFrame_Buffer& fb, iCoor p0, iCoor p1, uint32_t co)
{
  int x0 = p0.x, y0 = p0.y;
  int x1 = p1.x, y1 = p1.y;

  const iVect delta = p1 - p0;
  const int width = fb.width_get();

  if ( abs( delta.x ) < abs( delta.y ) )
    {
      // Iterate over y
      if ( y0 > y1 ) { swap(x0,x1); swap(y0,y1); }

      float dxdy = float(delta.x) / delta.y;
      float x = x0;

      for ( int yi = y0;  yi < y1;  yi++ )
        {
          fb[ yi * width + x ] = co;
          x += dxdy;
        }
    }
  else
    {
      // Iterate over x
      if ( x0 > x1 ) { swap(x0,x1); swap(y0,y1); }

      float dydx = float(delta.y) / delta.x;
      float y = y0;

      for ( int xi = x0;  xi < x1;  xi++ )
        {
          fb[ int(y) * width + xi ] = co;
          y += dydx;
        }
    }
}

template< typename F > void
func_plot
( pFrame_Buffer& fb,
  iCoor plot_lower_left, int plot_width, int plot_height,
  float func_x0, float func_x1, F func )
{
  const int win_width [[maybe_unused]] = fb.width_get();
  const int win_height [[maybe_unused]] = fb.height_get();

  assert( plot_width > 0 && plot_height > 0 );

  //                           RRGGBB
  const uint32_t color_red [[maybe_unused]] = 0xff0000;
  const uint32_t color_deep_sky_blue(0x00bfff);

  const uint32_t color_plot [[maybe_unused]] = 0xffff00; // Gold.
  const uint32_t color_box = color_deep_sky_blue;
  const uint32_t color_background [[maybe_unused]] = 0x333333;

  iCoor p0 = plot_lower_left; // Just an abbreviation.
  iVect vx( plot_width, 0 ), vy( 0, plot_height );
  iCoor p1 = p0 + vx + vy;

  line_draw( fb, p0, p0 + vy, color_box );
  line_draw( fb, p0, p0 + vx, color_box );
  line_draw( fb, p0 + vy, p1, color_box );
  line_draw( fb, p0 + vx, p1, color_box );

  float func_y0 [[maybe_unused]] = func(func_x0);
  float func_y1 [[maybe_unused]] = func(func_x1);

  /// Homework 1: Put solutions to all problems in this routine.

  /// SOLUTION -- Part (a)
  //
  //  Write every pixel in plot area with color_background.
  //
  for ( int i=p0.x+1; i<p1.x; i++ )
    for ( int j=p0.y+1; j<p1.y; j++ )
      fb[ i + j * win_width ] = color_background;


  /// SOLUTION -- Part (b)
  //
  //  Plot the function.
  //
  // Compute a delta_x (or dx/di). Used to find the change in x for
  // a change of 1 in y.
  //
  float dfx = ( func_x1 - func_x0 ) / plot_width;
  //
  // Find the minimum and maximum value of y ( func(x) ) over the plot range.
  //
  float min_y = func(func_x0);
  float max_y = min_y;
  for ( int i=0; i<plot_width; i++ )
    {
      float x = func_x0 + dfx * i;
      float y = func(x);
      if ( y < min_y ) min_y = y;
      if ( y > max_y ) max_y = y;
    }
  //
  // Compute the range of y (func(x)) values plus a 5% margin at the
  // top and bottom ..
  //
  float delta_y_tight = max_y - min_y;
  float margin = delta_y_tight * 0.05;
  float delta_y = delta_y_tight + 2 * margin;
  //
  // .. and use these to adjust the maximum and minimum y ..
  //
  max_y += margin;
  min_y -= margin;
  //
  // .. and finally compute a scale factor for transforming y into
  // a pixel vertical coordinate.
  //
  float scale = plot_height / delta_y;
  //
  // Plot the function.
  //
  for ( int i=0; i<plot_width; i++ )
    {
      // Coordinates to plot in the function's space.
      //
      float x = func_x0 + dfx * i;
      //
      // Function value at this i value.
      //
      float y = func(x);
      //
      // Function value at next i value.
      //
      float y_next = func(x+dfx);

      // Compute coordinates to pixel space.
      //
      int ix = p0.x + i;
      int iy0 = p0.y + ( y - min_y ) * scale;
      int iy1 = p0.y + ( y_next - min_y ) * scale;
      //
      int iylo = min(iy0,iy1);
      int iyhi = max(iy0,iy1);
      //
      // Draw a line from low to high y value.
      //
      for ( int iy = iylo; iy <= iyhi; iy++ )
        fb[ ix + iy * win_width ] = color_plot;
      //
      // Note: Though this results in a continuous plot, it might be
      // misleading because the minimum and maximum values of func(x)
      // for x in [x,x+dfx) might be lower or higher than iylo and
      // iyhi.
    }
}



void kbd_input_handle(pFrame_Buffer& fb, HW01_Data& hw01_data);


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

  // Act on keys that have been pressed, perhaps by adjusting variables.
  kbd_input_handle(fb,hw01_data);

  /// Coordinates Used for Placement of Other Objects
  //
  const int sq_slen = min(win_width,win_height) * .45;
  const int sq_x0 = sq_slen * 0.1,   sq_y1 = win_height - 75;
  const int sq_x1 = sq_x0 + sq_slen, sq_y0 = sq_y1 - sq_slen;
  //
  // One vertex of the square is at (sq_x0,sq_y0) and the opposite
  // vertex is at (sq_x1,sq_y1).

  //                                            RRGGBB
  const uint32_t color_red [[maybe_unused]] = 0xff0000;
  const uint32_t color_blue [[maybe_unused]] =    0xff;
  const uint32_t color_green [[maybe_unused]] = 0xff00;

  /// Demo Code: Draw Sine Waves  -- No need to modify this.
  //
  int num_waves = 4;
  float sine_ht = sq_slen/float(num_waves);
  for ( float
          yb = sq_y1 - sine_ht/2,  freq = 4 * M_PI / ( win_width - 40 - sq_x1 );
        yb > sq_y0;
        yb -= sine_ht,  freq *= 2 )
    for ( int x = sq_x1 + 20;  x < win_width - 20;  x++ )
      {
        int y = yb + sin( x * freq ) * sine_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_pts = 12 * 4;
  float c_r1 = sq_slen/6;  // Inner radius
  float c_r2 = sq_slen/2;  // Outer radius.
  pCoor c_ctr( c_r2 + 20, c_r2 + 20 ); // Center of "circle"
  float delta_theta = 2 * numbers::pi / n_pts;
  for ( int i=0; i<n_pts; i++ )
    {
      pVect v( cosf(i*delta_theta), sinf(i*delta_theta), 0 );
      pCoor p1 = c_ctr + c_r1 * v;
      pCoor p2 = c_ctr + c_r2 * v;
      line_draw(fb, p1, p2, 0xffffff );
    }

  fb.fb_stats_off(); // Turn off "redundancy" statistics.


  /// Draw misc waves.

  const bool simple_demo = false;
  if ( simple_demo )
    {
      iCoor plot_ll( 20, 20 );
      float p_wd = 400, p_ht = 200;
      float fx0 = -2 * numbers::pi, fx1 = numbers::pi * 7;
      func_plot( fb, plot_ll, p_wd, p_ht,  fx0, fx1, sinf );
    }

  float alphap = 0.5, alphan = 1;
  // https://arxiv.org/abs/2411.13010
  auto xIELU = [&](float x)
  { return x > 0 ? alphap * x * x + 0.5 * x
      : alphan * ( exp(x) - 1 ) - alphan * x + 0.5*x; };
  float s0 = 0.7;

  iCoor p00(c_ctr.x - sq_slen*s0*0.5, c_ctr.y-sq_slen*s0*0.5);
  func_plot( fb, p00, sq_slen*s0, sq_slen*s0, -3, 3, xIELU);

  float f_carrier = 17;
  float f_modmod = 1;
  float t_sim = hw01_data.t_sim;
  float f_mod = 2 + sinf(t_sim*f_modmod);

  auto fm = [&](float t) { return sinf( 4*sinf(t*f_mod) + t*f_carrier ); };
  auto am = [&](float t) { return (1.2 + sinf(t*f_mod) ) * sinf(t*f_carrier); };

  float plot_width = sq_slen * 0.7;
  int plot_gap = sq_slen / 8;
  int plot_stride = plot_width + plot_gap;
  int plot1_ht = sq_slen * .4;
  int plot1_y1 = sq_y1 - ( sq_slen - plot1_ht ) / 2;
  int plot1_y0 = plot1_y1 - plot1_ht;
  float fx0 = hw01_data.t_sim * 0.1 - 10;
  float fxwid = numbers::pi;
  int plot2_ht = sq_slen * 0.75;
  int plot2_y0 = c_ctr.y - plot2_ht / 2;

  for ( int x0 = sq_x1 + plot_gap;  x0 + plot_stride < win_width;
        x0 += plot_stride )
    {
      iCoor p10(x0,plot1_y0);
      iCoor p20(x0,plot2_y0);
      func_plot( fb, p10, plot_width, plot1_ht, fx0, fx0+fxwid, fm );
      func_plot( fb, p20, plot_width, plot2_ht, fx0, fx0+fxwid, am );
      fxwid *= 2;
    }
  //
  // See 2024 Homework 1 for a description of iCoor.

  fb.fb_stats_off(); // Turn off "redundancy" statistics.

  const int zoom = hw01_data.zoom;

  /// 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 );

  // Coordinates of mouse pointer and circle center.
  //
  const int mouse_x = fb.mouse_x;
  const int mouse_y = fb.mouse_y;

  // 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;
      }

  /// Box Around Mouse Pointer and Zoom Area
  //
  //  Use our line drawing routines to draw the box.
  const iCoor mouse_p(fb.mouse_x,fb.mouse_y);
  const int src_wid = src_hwid * 2;
  iVect src_dx(src_wid,0), src_dy(0,src_wid);
  iCoor src_ll = mouse_p - iVect(src_hwid,src_hwid);  // Lower Left
  iCoor src_ur = mouse_p + iVect(src_hwid,src_hwid);  // Upper Right

  uint32_t color_src_box = 0x8888ff; // Light Blue
  uint32_t color_dst_box = 0x8888ff; // Light Blue

  line_draw( fb, src_ll, src_ll + src_dx, color_src_box );
  line_draw( fb, src_ll, src_ll + src_dy, color_src_box );
  line_draw( fb, src_ur, src_ur - src_dx, color_src_box );
  line_draw( fb, src_ur, src_ur - src_dy, color_src_box );

  iCoor dst_ll( sq_x0, sq_y0 ), dst_ur( sq_x1, sq_y1 );
  iVect dst_dx( sq_slen, 0 ), dst_dy( 0, sq_slen );

  line_draw( fb, dst_ll, dst_ll + dst_dx, color_dst_box );
  line_draw( fb, dst_ll, dst_ll + dst_dy, color_dst_box );
  line_draw( fb, dst_ur, dst_ur - dst_dx, color_dst_box );
  line_draw( fb, dst_ur, dst_ur - dst_dy, color_dst_box );
}

void
kbd_input_handle(pFrame_Buffer& fb, HW01_Data& hw01_data)
{
  if ( !hw01_data.inited )
    {
      hw01_data.inited = true;
      hw01_data.tryout0 = false;
      hw01_data.tryout1 = false;
      hw01_data.zoom = 4;
      hw01_data.t_frame_last = time_wall_fp();
      hw01_data.t_sim = 0;
      hw01_data.paused = false;
    }

  switch ( fb.keyboard_key ) {
#if 0
  case FB_KEY_RIGHT: case FB_KEY_LEFT:
    {
      const int inc = fb.keyboard_key == FB_KEY_LEFT ? -kbd_inc : kbd_inc;
      break;
    }
  case FB_KEY_UP: case FB_KEY_DOWN:
    {
      const int inc = fb.keyboard_key == FB_KEY_DOWN ? -kbd_inc : kbd_inc;
      break;
    }
#endif

    case 'p': case 'P': hw01_data.paused = !hw01_data.paused; break;

  case 'y':
    hw01_data.tryout0 = !hw01_data.tryout0; break;
  case 'Y':
    hw01_data.tryout1 = !hw01_data.tryout1; break;

  case 'u': case 'U': if ( hw01_data.zoom > 1 ) hw01_data.zoom--; break;
  case 'z': case 'Z': hw01_data.zoom++; break;

  default:
    break;
  };

  const double t_now = time_wall_fp();
  if ( ! hw01_data.paused )
    hw01_data.t_sim += t_now - hw01_data.t_frame_last;

  hw01_data.t_frame_last = t_now;


  fb.fbprintf("Tryout 0,1:  %s,%s ('y','Y')  %s ('p')  "
              "Zoom: %d ('u','z') \n",
              hw01_data.tryout0 ? "TRUE " : "FALSE",
              hw01_data.tryout1 ? "TRUE " : "FALSE",
              hw01_data.paused ? "PAUSED " : "RUNNING",
              hw01_data.zoom);
}

int
main(int argc, char **argv)
{
  pFrame_Buffer demo_frame_buffer(argc,argv);

  demo_frame_buffer.show
    ( [&](pFrame_Buffer& fb){ render_hw01(fb,hw01_data); } );
  return 0;
}