/// LSU EE 4702-1 (Fall 2022), GPU Programming
//

/// Purpose
//
//   Demonstrate buffers.

#if 0

/// Using a VBufferV

 /// Purpose
 //
 // A VBufferV<TYPE> object holds one copy of TYPE. TYPE can be a
 // simple type, like a float, or an aggregate type, like a structure.
 // It must be POD (plain old data), meaning that it can't have member
 // functions and it shouldn't have pointers either.
 //
 // Typically a VBufferV is bound to a pipeline as a uniform, but
 // it could also be bound as a storage buffer or used for other
 // purposes.

 /// - Instantiate Buffer
 //
VBufferV<Uni_Light_Simple> uni_light_simple;
 //
 // This object contains a host copy Uni_Light_Simple and manages a
 // GPU copy.

 // Vulkan buffer handle type:  vk::buffer, VkBuffer

 /// - Initialize Buffer.
 //
uni_light_simple.init( vh.qs, vk::BufferUsageFlagBits::eUniformBuffer );
 //
 // In this case, the usage is set to a uniform buffer (which provides
 // constants for a rendering pipeline. Some possible common values:
 //
 //  eUniformBuffer: Provides uniforms (constants) to a rendering pipeline.
 //  eStorageBuffer: Provides arrays to a rendering pipeline.
 //  eVertexBuffer:  Provides ordinary inputs to a rendering pipeline.
 //  eIndexBuffer:   Provides index inputs to a rendering pipeline.
 //  eRayTracingNV:  Provides misc data to a ray tracing "pipeline".
 //
 // The following usages are for buffers holding image and other data:
 //  eTransferSrc, eTransferDst, eUniformTexelBuffer, eStorageTexelBuffer,

 /// - Write the Host Copy of Uni_Light_Simple
 //
uni_light_simple->color = color_white * opt_light_intensity;
uni_light_simple->position = transform.eye_from_global * light_location;
 //
 // Note: uni_light_simple is not really a pointer, it just works like
 // that syntactically.
 //
 // This can be done any number of times.


 /// - Move Data to GPU
 //
uni_light_simple.to_dev();
 //
 // Note: Data will only be moved if the data has been changed since
 // the last call to to_dev(). Changes are seen when the "->" operator
 // is used.
 //
 // This can be done any number of times.


 /// - Bind to a Pipeline
 //
pipe_plain.ds_uniform_use( "BIND_LIGHT_SIMPLE", uni_light_simple );
 //
 // This is done when the pipeline is created or when the buffer handle
 // changes. This is complicated and may change.
 //
 // The shader code used by pipe_plain must use BIND_LIGHT_SIMPLE as
 // the location index. For example,
 //
layout ( binding = BIND_LIGHT_SIMPLE ) uniform Uni_Light { vec4 p, c; };

 /// - Destroy
 //
uni_light_simple.destroy();
 //
 // This should be done near the end of execution, or when the
 // buffer will no longer be used. 




/// Using a VBufferVV

 /// Purpose
 //
 // A VBufferVV<TYPE> object holds a vector (like a dynamically
 // resized array) of TYPE objects. TYPE can be a simple type (such as
 // a float) or a structure. Data is inserted and can be accessed on
 // the CPU. It is typically bound to a pipeline as a storage buffer,
 // but could be bound as a uniform.

 /// - Instantiate Buffer
 //
VBufferVV<pMatrix> sto_matrices;
 //
 // This object contains a C++ vector of mat4 types, and will manage
 // Vulkan buffers on the GPU to hold the data.

 /// - Initialize Buffer.
 //
sto_matrices.init( vh.qs, vk::BufferUsageFlagBits::eStorageBuffer );
 //
 // In this case, the usage is set to a uniform buffer (which provides
 // constants for a rendering pipeline. Some possible common values:
 //
 //  eStorageBuffer: Provides arrays to a rendering pipeline.
 //  eVertexBuffer:  Provides ordinary inputs to a rendering pipeline.
 //  eIndexBuffer:   Provides index inputs to a rendering pipeline.
 //  eRayTracingNV:  Provides misc data to a ray tracing "pipeline".
 //
 //  The following are possible, but not as useful:
 //
 //  eUniformBuffer: Provides uniforms (constants) to a rendering pipeline.

 /// - Read, write, use as std::vector.

 sto_matrices.clear(); // After this executes, contains zero elements.
 sto_matrices << first_matrix;  // Now has one.
 sto_matrices << second_matrix; // Now has two.
 printf("First %f, of %zu\n", sto_matrices[0].rc(0,1), sto_matrices.size());
 printf("Second %f\n", sto_matrices[1]);
 sto_matrices.reserve(100);
 printf("Hmm %f, of %zu\n", sto_matrices[2], sto_matrices.size());

 sto_matrices.reserve(1000);
 sto_matrices.resize(100);
 sto_matrices.push_back( i_dont_like_the_chevron );
 sto_matrices.append( another_vector_of_matrices );
 pMatrix need_to_check = sto_matrices[2];
 for ( mat4& m: sto_matrices ) { prod = prod * m; }
 //
 // Note: some std::vector member functions work on VBufferVV.


 /// - Move Data to GPU AND Create Vulkan Buffer (if necessary)
 //
sto_matrices.to_dev();
 //
 // Note: Data will only be moved if the data has been changed since
 // the last call to to_dev().
 //
 // This can be done any number of times.


 /// - Bind to a Pipeline
 //
pipe_plain.ds_storage_follow( "BIND_MATRIXES", sto_matrices );
 //
 // Note that the name does not need to match the object name.
 // The suffix _follow means that the pipeline will watch for
 // changes in so_matrices.
 //
 // The shader code used by pipe_plain must use BIND_LIGHT_SIMPLE as
 // the location index. For example,
 //
layout ( binding = BIND_MATRIXES ) buffer Sto_Array { mat4 ms[]; };

 /// - Destroy
 //
sto_matrices.destroy();
 //
 // This should be done near the end of execution, or when the
 // buffer will no longer be used. 




#endif


// Include files provided for this course.
//
#define MAIN_INCLUDE
#include <vhelper.h>

#include <gp/coord.h>
#include <gp/pstring.h>
#include <gp/misc.h>
#include <gp/colors.h>

#include <vutil-texture.h>
#include <vutil-pipeline.h>
#include "shapes.h"

struct Uni_Light_Simple {
  vec4 position;
  vec4 color;
};

class World {
public:
  World(pVulkan_Helper &vh)
    :vh(vh),ff_state(vh.qs),frame_timer(vh.frame_timer),shapes(ff_state),
     transform(vh.qs){}
  void setup_and_run();
  void render(vk::CommandBuffer& cb);
  void keyboard_handle();

  // Class providing utilities, such as showing text.
  //
  pVulkan_Helper& vh;
  VFixed_Function_State_Manager ff_state;

  // Class for showing frame timing.
  //
  pFrame_Timer& frame_timer;

  Shapes shapes;

  // Class for easy keyboard control of variables.
  //
  pVariable_Control variable_control;

  pCoor light_location;
  float opt_light_intensity;
  enum { MI_Eye, MI_Light, MI_Ball, MI_Ball_V, MI_COUNT } opt_move_item;

  pCoor eye_location;
  pVect eye_direction;

  VTransform transform;
  VBufferV<Uni_Light_Simple> uni_light_simple;
  VPipeline pipe_plain;
  VVertex_Buffer_Set bset_plain;
};

void
World::setup_and_run()
{
  // Setup Vulkan context, etc.
  vh.init();
  vh.display_cb_set([&](){});
  vh.cbs_cmd_record.push_back( [&](vk::CommandBuffer& cb){ render(cb); });


  eye_location = pCoor(1,0.5,3);
  eye_location = pCoor(1,.5,10.2);
  eye_direction = pVect(0,0,-1);

  opt_light_intensity = 4.3;
  light_location = pCoor(6.2,0.0,3.7);
  variable_control.insert(opt_light_intensity,"Light Intensity");

  opt_move_item = MI_Eye;

  uni_light_simple.init(vh.qs,vk::BufferUsageFlagBits::eUniformBuffer);

  // Start the graphics. The function below does not return until the
  // user exits by closing the window.
  //
  vh.message_loop_spin();
  //
  // At this point the user exited and so it's time to
  // clean up.

  uni_light_simple.destroy();
  pipe_plain.destroy();
  bset_plain.destroy();
  shapes.destroy();
  transform.destroy();

  vh.finish();
}

void
World::render(vk::CommandBuffer& cb)
{
  // This routine called whenever window needs to be updated.

  // Get any waiting keyboard commands.
  //
  keyboard_handle();

  /// Frame Buffer Informational Messages
  //
  //  Print messages using utility functions provided for this course.
  //
  vh.fbprintf("%s\n",frame_timer.frame_rate_text_get());

  vh.fbprintf
    ("Eye location: [%5.1f, %5.1f, %5.1f]  "
     "Eye direction: [%+.2f, %+.2f, %+.2f]\n",
     eye_location.x, eye_location.y, eye_location.z,
     eye_direction.x, eye_direction.y, eye_direction.z);

  vh.fbprintf
    ("Light location: [%5.1f, %5.1f, %5.1f]\n",
     light_location.x, light_location.y, light_location.z);

  pVariable_Control_Elt* const cvar = variable_control.current;
  vh.fbprintf("VAR %s = %.5f  (TAB or '`' to change, +/- to adjust)\n",
                      cvar->name,cvar->get_val());


  // -------------------------------------------------------------------------
  ///
  /// Specification of Transformation Matrices
  ///

  transform.eye_from_global_set
    ( pMatrix_Rotation(eye_direction,pVect(0,0,-1))
      * pMatrix_Translate(-eye_location) );

  /// Setup Projection Transformation:  Eye Space -> Clip Space
  //
  const int win_width = vh.s_extent.width;
  const int win_height = vh.s_extent.height;
  const float aspect = float(win_width) / win_height;
  const float n_dist = 0.01;
  const float xr = .8 * n_dist;

  // Frustum: left, right, bottom, top, near, far
  transform.clip_from_eye_set
    ( pMatrix_Frustum( -xr, xr,                // left, right
                       -xr/aspect, xr/aspect,  // bottom, top
                       n_dist, 5000            // near, far 
                       ) );

  /// Lighting
  //
  uni_light_simple->color = color_white * opt_light_intensity;
  uni_light_simple->position = transform.eye_from_global * light_location;
  uni_light_simple.to_dev();

  //
  // -------------------------------------------------------------------------


  ///
  /// Paint Single Triangle.
  ///

  if ( !pipe_plain )
    pipe_plain
      .init( vh.qs )
      .ds_follow( transform )
      .ds_uniform_use( "BIND_LIGHT_SIMPLE", uni_light_simple )
      .shader_inputs_info_set<pCoor,pNorm,pColor>()
      .shader_code_set
      ("demo-03-shdr-code.cc", "vs_main();", nullptr, "fs_main();")
      .topology_set( vk::PrimitiveTopology::eTriangleList )
      .create();

  // Reset buffers in this buffer set.
  //
  bset_plain.reset(pipe_plain);

  // Object-space coordinates of a triangle.
  //
  pCoor p0 = { 0, 0,  0 };
  pCoor p1 = { 9, 6, -9 };
  pCoor p2 = { 0, 5, -5 };

  // Find triangle normal using cross product function (in coord.h).
  //
  pNorm tri_norm = cross( p0, p1, p2 );
  pColor color_tri( .2, .900, .1 ); // Red, Green, Blue; Values in [0,1]

  // Insert vertex coordinates into buffer set (bset_plain).
  //
  bset_plain << p0 << p1 << p2;

  // Insert vertex colors and normals.
  //
  bset_plain << color_tri << color_red << color_blue;
  bset_plain << tri_norm << tri_norm << tri_norm;
  //
  // All three vertices here have the same normal.

  // Add a square consisting of a red and green triangle.
  //
  bset_plain << pCoor(-2,0,-2) << pCoor(-4,2,-2) << pCoor(-4,0,-2);
  bset_plain << color_red << color_red << color_red;
  bset_plain << pCoor(-2,0,-2) << pCoor(-2,2,-2) << pCoor(-4,2,-2);
  bset_plain << color_green << color_green << color_green;
  pNorm snorm = cross( pCoor(-4,0,-2), pCoor(-4,2,-2), pCoor(-2,0,-2) );
  bset_plain << snorm << snorm << snorm << snorm << snorm << snorm;

  bset_plain.to_dev();
  pipe_plain.record_draw(cb, bset_plain);

  // Render Marker for Light Source
  //
  shapes.record_tetrahedron(cb,transform,light_location,0.2);

}



void
World::keyboard_handle()
{
  const int key = vh.keyboard_key_get();
  if ( !key ) return;
  pVect adjustment(0,0,0);
  pVect user_rot_axis(0,0,0);
  const bool kb_mod_s = vh.keyboard_shift;
  const bool kb_mod_c = vh.keyboard_control;
  const float move_amt = kb_mod_s ? 2.0 : kb_mod_c ? 0.08 : 0.4;

  switch ( key ) {
  case FB_KEY_LEFT: adjustment.x = -move_amt; break;
  case FB_KEY_RIGHT: adjustment.x = move_amt; break;
  case FB_KEY_PAGE_UP: adjustment.y = move_amt; break;
  case FB_KEY_PAGE_DOWN: adjustment.y = -move_amt; break;
  case FB_KEY_DOWN: adjustment.z = move_amt; break;
  case FB_KEY_UP: adjustment.z = -move_amt; break;
  case FB_KEY_DELETE: user_rot_axis.y = 1; break;
  case FB_KEY_INSERT: user_rot_axis.y =  -1; break;
  case FB_KEY_HOME: user_rot_axis.x = 1; break;
  case FB_KEY_END: user_rot_axis.x = -1; break;
  case 'b': case 'B': opt_move_item = MI_Ball; break;
  case 'e': case 'E': opt_move_item = MI_Eye; break;
  case 'l': case 'L': opt_move_item = MI_Light; break;
  case FB_KEY_TAB:
    if ( !kb_mod_s ) { variable_control.switch_var_right(); break; }
  case 96: variable_control.switch_var_left(); break;
  case '-':case '_': variable_control.adjust_lower(); break;
  case '+':case '=': variable_control.adjust_higher(); break;
  default: printf("Unknown key, %d\n",key); break;
  }

  // Update eye_direction based on keyboard command.
  //
  if ( user_rot_axis.x || user_rot_axis.y )
    {
      pMatrix_Rotation rotall(eye_direction,pVect(0,0,-1));
      user_rot_axis *= invert(rotall);
      eye_direction *= pMatrix_Rotation(user_rot_axis, M_PI * 0.03);
    }

  // Update eye_location based on keyboard command.
  //
  if ( adjustment.x || adjustment.y || adjustment.z )
    {
      const double angle =
        fabs(eye_direction.y) > 0.99
        ? 0 : atan2(eye_direction.x,-eye_direction.z);
      pMatrix_Rotation rotall(pVect(0,1,0),-angle);
      adjustment *= rotall;

      switch ( opt_move_item ){
      case MI_Light: light_location += adjustment; break;
      case MI_Eye: eye_location += adjustment; break;
      //  case MI_Ball: object_location += adjustment; break;
      default: break;
      }
    }
}


int
main(int argv, char **argc)
{
  pVulkan_Helper pvulkan_helper(argv,argc);
  World world(pvulkan_helper);

  world.setup_and_run();

  return 0;
}