////////////////////////////////////////////////
////////////////////////////////////////////////
////////////////   FUNCTIONS   /////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////

#if !defined(MR4TH_ASCII_FUNCTIONS_DEFINED)
#define MR4TH_ASCII_FUNCTIONS_DEFINED 1

////////////////////////////////
// Function: Histogram

MR4TH_SYMBOL HIST_Data
hist_data(F32 *vals, U64 stride, U64 count){
  HIST_Data data = {vals, stride, count};
  return(data);
}

MR4TH_SYMBOL_STATIC HIST_Stats
hist_stats(HIST_Data *data){
  U8 *v = (U8*)data->vals;
  
  F32 val_sum = 0.f;
  F32 val_min; set_inf_F32(val_min);
  F32 val_max; set_neg_inf_F32(val_max);
  for (U32 j = 0; j < data->count; j += 1){
    F32 s = *(F32*)(v + j*data->stride);
    val_sum += s;
    val_min = Min(val_min, s);
    val_max = Max(val_max, s);
  }
  F32 val_mean = val_sum/(F32)data->count;
  
  F32 val_sqr_dev_sum = 0.f;
  for (U32 j = 0; j < data->count; j += 1){
    F32 x = (*(F32*)(v + j*data->stride) - val_mean);
    val_sqr_dev_sum += x*x;
  }
  F32 val_sd = 0.f;
  if (data->count > 1){
    F32 val_variance = val_sqr_dev_sum/(data->count - 1.f);
    val_sd = sqrt_F32(val_variance);
  }
  
  HIST_Stats stats = {0};
  stats.mean = val_mean;
  stats.sd   = val_sd;
  stats.min  = val_min;
  stats.max  = val_max;
  
  return(stats);
}

MR4TH_SYMBOL void
hist_stats_str8(Arena *arena, String8List *out, HIST_Stats *stats){
  str8_list_pushf(arena, out,
                  "mean: %f\n"
                  "sd:   %f\n"
                  "min:  %f\n"
                  "max:  %f\n",
                  stats->mean, stats->sd, stats->min, stats->max);
}

MR4TH_SYMBOL void
hist_stats_fprint(FILE *out, HIST_Stats *stats){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String8List string = {0};
  hist_stats_str8(scratch.arena, &string, stats);
  m4_fprint_str8list(out, &string);
  arena_release_scratch(&scratch);
}

MR4TH_SYMBOL void
hist_print(STREAM *stream, HIST_Data *data, HIST_ControlParams *params){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  // Formula: bucket -> bar
  //  half_bucket = bar_count/2;
  //  bar = FLOOR( (bucket*bar_count + half_bucket)/bucket_count )
  
  
  // default precision
  F32 precision = params->precision;
  if (params->integer){
    precision = floor_F32(precision);
  }
  if (precision <= 0){
    precision = 1.f;
  }
  
  // stats
  HIST_Stats stats = hist_stats(data);
  F32 hist_min = floor_F32(stats.min);
  F32 hist_max = ceil_F32(stats.max);
  if (params->integer){
    hist_max += 1.f;
  }
  
  F32 hist_range = hist_max - hist_min;
  
  // setup buckets & bars
  U32 bucket_count = (U32)ceil_F32(hist_range/precision);
  U32 bar_count = params->width;
  bar_count = ClampTop(bar_count, bucket_count);
  
  U32 half_bucket = bar_count/2;
  
  HIST_Bar *bars = push_array(scratch.arena, HIST_Bar, bar_count + 1);
  
  // assign buckets to bars
  {
    U32 bucket_idx = 0;
    U32 bar_idx = 0;
    U32 accum = bar_count/2;
    for (;;){
      bucket_idx += 1;
      accum += bar_count;
      if (accum >= bucket_count){
        bar_idx += 1;
        bars[bar_idx].first_bucket_idx = bucket_idx;
        accum -= bucket_count;
      }
      if (bucket_idx >= bucket_count){
        break;
      }
    }
  }
  
  // fill buckets with data points
  U8 *v = (U8*)data->vals;
  for (U32 i = 0; i < data->count; i += 1){
    F32 s = *(F32*)(v + i*data->stride);
    
    U32 bucket_idx = (U32)floor_F32((s - hist_min)/precision);
    bucket_idx = ClampTop(bucket_idx, bucket_count - 1);
    
    U32 bar_idx = (bucket_idx*bar_count + half_bucket)/bucket_count;
    HIST_Bar *bar = bars + bar_idx;
    bar->data_point_count += 1;
  }
  
  // calculate bar heights
  for (U32 i = 0; i < bar_count; i += 1){
    HIST_Bar *bar = bars + i;
    U32 number_buckets_on_this_bar = (bar + 1)->first_bucket_idx - bar->first_bucket_idx;
    bar->height = ((F32)bar->data_point_count)/number_buckets_on_this_bar;
  }
  
  // calculate max height
  F32 max_height; set_neg_inf_F32(max_height);
  for (U32 i = 0; i < bar_count; i += 1){
    HIST_Bar *bar = bars + i;
    max_height = Max(max_height, bar->height);
  }
  
  // fill histogram ascii
  U32 ascii_width = params->width + 1;
  U32 grid_size = ascii_width*params->height;
  U8 *grid = stream_alloc(stream, grid_size);
  
  for (U32 x = 0; x < params->width; x += 1){
    U32 bar_idx = x;
    if (bar_idx < bar_count){
      HIST_Bar *bar = bars + bar_idx;
      F32 bar_grid_height_F32 = params->height*bar->height/max_height;
      U32 bar_grid_height = (U32)ceil_F32(bar_grid_height_F32);
      for (U32 y = 0; y < params->height; y += 1){
        U8 c = ' ';
        if (y >= params->height - bar_grid_height){
          c = '*';
        }
        grid[y*ascii_width + x] = c;
      }
    }
  }
  
  for (U32 y = 0; y < params->height; y += 1){
    grid[y*ascii_width + params->width] = '\n';
  }
  
  arena_release_scratch(&scratch);
}

MR4TH_SYMBOL void
hist_fprint(FILE *out, HIST_Data *data, HIST_ControlParams *params){
  STREAM *stream = stream_new();
  hist_print(stream, data, params);
  stream_fprint(out, stream);
  stream_release(stream);
}

////////////////////////////////
// Function: Table

MR4TH_SYMBOL_STATIC TABLE*
table_begin(Arena *arena){
  TABLE *table = push_array(arena, TABLE, 1);
  return(table);
}

MR4TH_SYMBOL void
table_default_align(TABLE *table, TABLE_CellFlags align){
  table->default_cell_flags = (table->default_cell_flags&~TABLE_CellFlag_AlignMask) | (align&TABLE_CellFlag_AlignMask);
}

MR4TH_SYMBOL_STATIC void
table_row(Arena *arena, TABLE *table){
  TABLE_Row *row = push_array(arena, TABLE_Row, 1);
  SLLQueuePush(table->first_row, table->last_row, row);
  table->row_count += 1;
}

MR4TH_SYMBOL void
table_hz_separator(Arena *arena, TABLE *table){
  TABLE_Row *row = table->last_row;
  if (row != 0){
    row->has_hz_separator = 1;
  }
}

MR4TH_SYMBOL_STATIC void
table_cell(Arena *arena, TABLE *table, String8 string){
  TABLE_Row *row = table->last_row;
  if (row != 0){
    TABLE_Cell *cell = push_array(arena, TABLE_Cell, 1);
    SLLQueuePush(row->first_cell, row->last_cell, cell);
    row->cell_count += 1;
    table->col_count = Max(table->col_count, row->cell_count);
    cell->string = str8_push_copy(arena, string);
    cell->flags = table->default_cell_flags;
  }
}

MR4TH_SYMBOL_STATIC void
table_cellf(Arena *arena, TABLE *table, char *fmt, ...){
  ArenaTemp scratch = arena_get_scratch(&arena, 1);
  va_list args;
  va_start(args, fmt);
  String8 string = str8_pushfv(scratch.arena, fmt, args);
  va_end(args);
  table_cell(arena, table, string);
  arena_release_scratch(&scratch);
}

MR4TH_SYMBOL void
table_cell_align(TABLE *table, TABLE_CellFlags align){
  TABLE_Row *row = table->last_row;
  if (row != 0){
    TABLE_Cell *cell = row->last_cell;
    if (cell != 0){
      cell->flags = (cell->flags&~TABLE_CellFlag_AlignMask) | (align&TABLE_CellFlag_AlignMask);
    }
  }
}

MR4TH_SYMBOL_STATIC void
table_print(STREAM *stream, TABLE *table, TABLE_Style *style){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  // calculate width of each column & count row separators
  U64 *col_widths = push_array(scratch.arena, U64, table->col_count);
  U64 row_separator_count = 0;
  for (TABLE_Row *row = table->first_row;
       row != 0;
       row = row->next){
    U64 idx = 0;
    for (TABLE_Cell *cell = row->first_cell;
         cell != 0;
         cell = cell->next, idx += 1){
      col_widths[idx] = Max(col_widths[idx], cell->string.size);
    }
    if (row->next != 0 && row->has_hz_separator){
      row_separator_count += 1;
    }
  }
  
  // calculate table sizes
  U64 col_widths_total = 0;
  for (U64 i = 0; i < table->col_count; i += 1){
    col_widths_total += col_widths[i];
  }
  
  U64 col_separator_count = 0;
  if (table->col_count > 0){
    col_separator_count = table->col_count - 1;
  }
  
  U64 width_total = (col_widths_total
                     + col_separator_count*style->width[1]
                     + style->width[0] + style->width[2]);
  
  U64 row_count = table->row_count;
  U64 height_total = (row_count
                      + row_separator_count*style->height[1]
                      + style->height[0] + style->height[2]);
  
  // fill table ascii
  U64 ascii_width = width_total + 1;
  U64 grid_size = ascii_width*height_total;
  U8 *grid = stream_alloc(stream, grid_size);
  {
    U32 y = 0;
    
    // top border
    for (U32 i = 0; i < style->height[0]; i += 1, y += 1){
      U8 *grid_line = grid + y*ascii_width;
      U32 x = 0;
      
      // left
      {
        MemoryCopy(grid_line + x, style->knots[0][0] + i*style->width[0], style->width[0]);
        x += style->width[0];
      }
      
      // middle
      U8 filler = style->hz_sep[0][i];
      for (U64 col_idx = 0; col_idx < table->col_count; col_idx += 1){
        U64 fill_to_x = x + col_widths[col_idx];
        for (; x < fill_to_x; x += 1){
          grid_line[x] = filler;
        }
        if (col_idx + 1 < table->col_count){
          MemoryCopy(grid_line + x, style->knots[1][0] + i*style->width[1], style->width[1]);
          x += style->width[1];
        }
      }
      
      // right
      {
        MemoryCopy(grid_line + x, style->knots[2][0] + i*style->width[2], style->width[2]);
        x += style->width[2];
      }
      
      Assert(x == width_total);
      grid_line[x] = '\n';
    }
    
    // table data
    for (TABLE_Row *row = table->first_row;
         row != 0;
         row = row->next){
      
      // table row //
      {
        U8 *grid_line = grid + y*ascii_width;
        U32 x = 0;
        
        // left
        {
          MemoryCopy(grid_line + x, style->vr_sep[0], style->width[0]);
          x += style->width[0];
        }
        
        // middle
        TABLE_Cell *cell = row->first_cell;
        for (U64 col_idx = 0; col_idx < table->col_count; col_idx += 1){
          U64 x0 = x;
          U64 xopl = x + col_widths[col_idx];
          
          U64 xtext = x;
          if (cell != 0){
            switch (cell->flags&TABLE_CellFlag_AlignMask){
              default: case TABLE_CellFlag_LeftAlign:
              {}break;
              
              case TABLE_CellFlag_RightAlign:
              {
                xtext = xopl - cell->string.size;
              }break;
              
              case TABLE_CellFlag_CenterAlign:
              {
                xtext = (x0 + xopl - cell->string.size)/2;
              }break;
            }
            
            for (; x < xtext; x += 1){
              grid_line[x] = ' ';
            }
            MemoryCopy(grid_line + x, cell->string.str, cell->string.size);
            x += cell->string.size;
            cell = cell->next;
          }
          
          for (; x < xopl; x += 1){
            grid_line[x] = ' ';
          }
          
          if (col_idx + 1 < table->col_count){
            MemoryCopy(grid_line + x, style->vr_sep[1], style->width[1]);
            x += style->width[1];
          }
        }
        
        // right
        {
          MemoryCopy(grid_line + x, style->vr_sep[2], style->width[2]);
          x += style->width[2];
        }
        
        Assert(x == width_total);
        grid_line[width_total] = '\n';
      }
      y += 1;
      
      // hz separator //
      if (row->next != 0 && row->has_hz_separator){
        for (U32 i = 0; i < style->height[1]; i += 1, y += 1){
          U8 *grid_line = grid + y*ascii_width;
          U32 x = 0;
          
          // left
          {
            MemoryCopy(grid_line + x, style->knots[0][1] + i*style->width[0], style->width[0]);
            x += style->width[0];
          }
          
          // middle
          U8 filler = style->hz_sep[1][i];
          for (U64 col_idx = 0; col_idx < table->col_count; col_idx += 1){
            U64 fill_to_x = x + col_widths[col_idx];
            for (; x < fill_to_x; x += 1){
              grid_line[x] = filler;
            }
            if (col_idx + 1 < table->col_count){
              MemoryCopy(grid_line + x, style->knots[1][1] + i*style->width[1], style->width[1]);
              x += style->width[1];
            }
          }
          
          // right
          {
            MemoryCopy(grid_line + x, style->knots[2][1] + i*style->width[2], style->width[2]);
            x += style->width[2];
          }
          
          Assert(x == width_total);
          grid_line[x] = '\n';
        }
      }
    }
    
    // bottom border
    for (U32 i = 0; i < style->height[2]; i += 1, y += 1){
      U8 *grid_line = grid + y*ascii_width;
      U32 x = 0;
      
      // left
      {
        MemoryCopy(grid_line + x, style->knots[0][2] + i*style->width[0], style->width[0]);
        x += style->width[0];
      }
      
      // middle
      U8 filler = style->hz_sep[2][i];
      for (U64 col_idx = 0; col_idx < table->col_count; col_idx += 1){
        U64 fill_to_x = x + col_widths[col_idx];
        for (; x < fill_to_x; x += 1){
          grid_line[x] = filler;
        }
        if (col_idx + 1 < table->col_count){
          MemoryCopy(grid_line + x, style->knots[1][2] + i*style->width[1], style->width[1]);
          x += style->width[1];
        }
      }
      
      // right
      {
        MemoryCopy(grid_line + x, style->knots[2][2] + i*style->width[2], style->width[2]);
        x += style->width[2];
      }
      
      Assert(x == width_total);
      grid_line[x] = '\n';
    }
  }
  
  arena_release_scratch(&scratch);
}

MR4TH_SYMBOL void
table_fprint(FILE *out, TABLE *table, TABLE_Style *style){
  STREAM *stream = stream_new();
  table_print(stream, table, style);
  stream_fprint(out, stream);
  stream_release(stream);
}

MR4TH_SYMBOL U64
table_get_width(TABLE *table, TABLE_Style *style){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  // calculate width of each column & count row separators
  U64 *col_widths = push_array(scratch.arena, U64, table->col_count);
  U64 row_separator_count = 0;
  for (TABLE_Row *row = table->first_row;
       row != 0;
       row = row->next){
    U64 idx = 0;
    for (TABLE_Cell *cell = row->first_cell;
         cell != 0;
         cell = cell->next, idx += 1){
      col_widths[idx] = Max(col_widths[idx], cell->string.size);
    }
    if (row->next != 0 && row->has_hz_separator){
      row_separator_count += 1;
    }
  }
  
  // calculate table sizes
  U64 col_widths_total = 0;
  for (U64 i = 0; i < table->col_count; i += 1){
    col_widths_total += col_widths[i];
  }
  
  U64 col_separator_count = 0;
  if (table->col_count > 0){
    col_separator_count = table->col_count - 1;
  }
  
  U64 width_total = (col_widths_total +
                     col_separator_count*style->width[1] +
                     style->width[0] + style->width[2]);
  
  arena_release_scratch(&scratch);
  return(width_total);
}

#endif /* MR4TH_ASCII_FUNCTIONS_DEFINED */


////////////////////////////////////////////////
////////////////////////////////////////////////
//////////////////   TESTS   ///////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////

#if defined(MR4TH_ASCII_TESTS)

MR4TH_SYMBOL_STATIC void
ascii_test1(void){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  // stream
  STREAM *stream = stream_new();
  
  // prng
  PRNG *prng = 0;
  PRNG prng_ = {0};
  {
    PRNG_Seed seed = {0};
    os_get_entropy(&seed, sizeof(seed));
    prng_ = prng_from_seed(&seed);
    prng = &prng_;
  }
  
  // histogram
  {
    U64 count = Million(1);
    F32 *vals = push_array(scratch.arena, F32, count);
    for (U64 i = 0; i < count; i += 1){
      U32 x = 0;
      for (U32 j = 0; j < 2; j += 1){
        U32 x0 = prng_u32_bounded(prng, 50);
        x = Max(x, x0);
      }
      vals[i] = (F32)x;
    }
    
    HIST_Data data = hist_data(vals, sizeof(*vals), count);
    
    HIST_ControlParams params = {0};
    params.integer = 1;
    params.width = 30;
    params.height = 20;
    
    stream_printf(stream, "\n");
    hist_print(stream, &data, &params);
  }
  
  // table
  {
    TABLE *table = table_begin(scratch.arena);
    
    table_row(scratch.arena, table);
    table_cellf(scratch.arena, table, "NAME");
    table_cellf(scratch.arena, table, "TRIAL 1");
    table_cellf(scratch.arena, table, "TRIAL 2");
    table_cellf(scratch.arena, table, "TRIAL 3");
    table_cellf(scratch.arena, table, "RANK");
    table_hz_separator(scratch.arena, table);
    
    {    
      table_row(scratch.arena, table);
      table_cellf(scratch.arena, table, "Alice");
      table_cellf(scratch.arena, table, "33.3333333333");
      table_cellf(scratch.arena, table, "17.909090909");
      table_cellf(scratch.arena, table, "5.61");
      table_cellf(scratch.arena, table, "1");
    }
    
    {    
      table_row(scratch.arena, table);
      table_cellf(scratch.arena, table, "Bob");
      table_cellf(scratch.arena, table, "27.555555555");
      table_cellf(scratch.arena, table, "21");
      table_cellf(scratch.arena, table, "5.332");
      table_cellf(scratch.arena, table, "2");
    }
    
    {
      table_row(scratch.arena, table);
      table_cellf(scratch.arena, table, "Eve");
      table_cellf(scratch.arena, table, "21.0990099");
      table_cellf(scratch.arena, table, "18.001");
      table_cellf(scratch.arena, table, "2");
      table_cellf(scratch.arena, table, "3");
    }
    
    stream_printf(stream, "\n");
    table_print(stream, table, &table_wire_style);
  }
  
  stream_printf(stream, "\n");
  
  stream_fprint(stdout, stream);
}

#endif /* MR4TH_ASCII_TESTS */