#include <stdlib.h>

// ---
// Header includes
#include "base/base_inc.h"
#include "os/os_inc.h"


// CUDA headers
#include "kernels.h"

// ---
// .C includes
#include "base/base_inc.c"
#include "os/os_inc.c"
#include "os/os_entry_point.c"


#define grid_file_path_bin "D:\\dev\\eigsol_gpu\\out\\grid.bin"
#define grid_file_path "D:\\dev\\eigsol_gpu\\out\\grid.dat"
#define knotpoints_file_path "D:\\dev\\eigsol_gpu\\out\\knotpoints.dat"

global U32 enable_logging = 1;

// Complex number with double precision
typedef struct Z64 Z64;
struct Z64
{
  F64 re;
  F64 im;
};

typedef struct Grid Grid;
struct Grid 
{
  F64 start;
  F64 end;
  U32 num_steps;
  F64 *points;
};


typedef struct BSplineCtx BSplineCtx;
struct BSplineCtx 
{
  Arena* arena;
  U32 order;
  F64 *knotpoints;
  U32 num_knotpoints;
  U32 num_bsplines;
  U32 num_phys_points;
};



//~ Globals
global BSplineCtx g_bspline_ctx = {0};

//~ Functions

function inline void
logger_debug(String8 msg)
{
  OutputDebugString(msg.str);
}

#define LOG(msg) if(enable_logging) { logger_debug(msg) };


function void
write_array_binary_F64(String8 path_to_file, F64 *values, U32 array_size) 
{
  OS_Handle file_handle = OS_file_open(OS_AccessFlag_Write | OS_AccessFlag_CreateNew, 
      path_to_file);
  { 
    ArenaTemp scratch = scratch_get(0, 0);
    String8List list = {0};
    String8 temp = {0};
    temp.str = (U8*)values;
    temp.size = sizeof(F64)*array_size;
    str8_list_push(scratch.arena, &list, temp);
    OS_file_write(scratch.arena, file_handle, 0, list, 0);
    
    String8List log_list = {0};
    str8_list_push(scratch.arena, &log_list, str8_lit("Wrote binary array data to"));
    str8_list_push(scratch.arena, &log_list, path_to_file);
    StringJoin join = {0};
    join.sep = str8_lit(" ");
    join.post = str8_lit("\n");
    String8 log_msg = str8_list_join(scratch.arena, log_list, &join);
    OutputDebugString(log_msg.str);
  }
  OS_file_close(file_handle);
}

function void
write_array_F64(String8 path_to_file, F64 *values, U32 array_size, char* fmt) 
{
  OS_Handle file_handle = OS_file_open(OS_AccessFlag_Write | OS_AccessFlag_CreateNew, 
      path_to_file);
  {
    ArenaTemp scratch = scratch_get(0, 0);
    String8List list = {0};
    for(U32 i = 0; i < array_size; i++) 
    {
      str8_list_pushf(scratch.arena, &list, fmt, values[i]);
    }
    OS_file_write(scratch.arena, file_handle, 0, list, 0);
    
    String8List log_list = {0};
    str8_list_push(scratch.arena, &log_list, str8_lit("Wrote array to"));
    str8_list_push(scratch.arena, &log_list, path_to_file);
    StringJoin join = {0};
    join.sep = str8_lit(" ");
    join.post = str8_lit("\n");
    String8 log_msg = str8_list_join(scratch.arena, log_list, &join);
    OutputDebugString(log_msg.str);
  }
  OS_file_close(file_handle);
  
}

function F64 bspline_recursion(F64 x, U32 k, U32 i)
{
  F64 *t = g_bspline_ctx.knotpoints;

  if(k == 1)
  {
    if(i < g_bspline_ctx.num_bsplines && x >= t[i] && x < t[i+1])
    {
      return 1.0;
    } else {
      return 0.0;
    }
  } 
  else 
  {
    F64 term1 = (x - t[i])/(t[i+k-1]-t[i])*bspline_recursion(x, k-1, i);
    F64 term2 = (t[i+k]-x)/(t[i+k]-t[i+1])*bspline_recursion(x, k-1, i+1);

    return term1 + term2;
  }


}


function F64 get_bspline_F64(F64 x_coord, U32 index)
{
  U32 k = g_bspline_ctx.order;
  F64 out = bspline_recursion(x_coord, k, index);
  return out;
}


  function
void EntryPoint() 
{
  OS_InitReceipt os_receipt = OS_init();
  OS_InitGfxReceipt os_gfx_receipt = OS_gfx_init(os_receipt);

  Arena *arena = m_make_arena();

  Grid grid = {0};
  grid.start = 0.0;
  grid.end = 10.0;
  grid.num_steps = 1000;

  grid.points = PushArrayZero(arena, F64, grid.num_steps);
  F64 step_size = (grid.end-grid.start)/(F64)grid.num_steps;
  grid.points[0] = grid.start;
  grid.points[grid.num_steps-1] = grid.end;
  for(U32 i = 1; i < grid.num_steps-1; i++)
  {
    grid.points[i] = grid.points[i-1] + step_size;
  }

  write_array_binary_F64(str8_lit(grid_file_path_bin), grid.points, grid.num_steps);
  write_array_F64(str8_lit(grid_file_path), grid.points, grid.num_steps, "%13.6e\n");

  // Create knotpoint sequence.
  U32 k = 4;
  U32 N = 35;
  g_bspline_ctx.order = k; 
  g_bspline_ctx.num_knotpoints = N; 
  g_bspline_ctx.num_bsplines = N-k;
  g_bspline_ctx.num_phys_points = N-(2*k)+2; // Remove k points at each end, and then add back the first and last points.
  g_bspline_ctx.arena = arena;
  g_bspline_ctx.knotpoints = (F64*)PushArrayZero(arena, F64, g_bspline_ctx.num_knotpoints);
  // Set up physical points;
  F64 delta = (grid.end-grid.start)/(g_bspline_ctx.num_phys_points-1);
  // Set ghost points including first physical
  U32 phys_point_index = g_bspline_ctx.num_phys_points + k-1;
  for(U32 i = 0; i < k; i++) 
  {
    g_bspline_ctx.knotpoints[i] = grid.start;
  }
  for(U32 i = k; i < phys_point_index; i++) 
  {
     g_bspline_ctx.knotpoints[i] = g_bspline_ctx.knotpoints[i-1] + delta;
  }
  // Set the last points
  F64 last_physical = grid.end;
  for(U32 i = phys_point_index; i < g_bspline_ctx.num_knotpoints; i++)
  {
     g_bspline_ctx.knotpoints[i] = last_physical;
  }

  write_array_F64(str8_lit(knotpoints_file_path), g_bspline_ctx.knotpoints, g_bspline_ctx.num_knotpoints, "%13.6e\n");


  String8 Bspline3_fn = str8_lit("D:\\dev\\eigsol_gpu\\out\\Bspline3.dat");
  F64 *Bspline3 = (F64 *)PushArrayZero(arena, F64, grid.num_steps);
  for(U32 i = 0; i < grid.num_steps; i++)
  {
    Bspline3[i] = get_bspline_F64(grid.points[i], 3);
  }

  write_array_F64(Bspline3_fn, Bspline3, grid.num_steps, "%13.6e\n");









}



