#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "cognition_typedefs.h"
#include "cognition_year.h"
#include "cognition_candle.h"
#include "cognition_time.h"
#include "cognition.h"
#include "cognition_db_candle_loader.h"

//#define printf(...) (void)0

typedef struct {
  union
  {
    i32 rule_buffer[5];
    struct
    {
      i32 candle_attrib_1;
      i32 candle_lookback_1;
      b32 less_than_bigger_than;
      i32 candle_attrib_2;
      i32 candle_lookback_2;
    };
  };
} rule;

rule generate_rule(size_t max_lookback)
{
  rule Result;
  i32 candle_attrib_1 = (u8)rand_f(4);
  i32 candle_lookback_1 = (i32)rand_f(max_lookback);
  b32 less_than_bigger_than = (b32)rand_f(2);
  i32 candle_attrib_2 = (u8)rand_f(4);
  i32 candle_lookback_2 = (i32)rand_f(max_lookback);

  //if we're on the same candle
  //we shouldnt compare if the low is bigger than the high
  //or conversely we shouldnt compare if the low is smaller
  i32 lookback_delta = (candle_lookback_2 - candle_lookback_1);

  if(candle_lookback_1 == candle_lookback_2)
  {
    if(candle_attrib_1==0)
    {
      candle_attrib_2  = 3;
    }
    else if(candle_attrib_1 == 3)
    {
      candle_attrib_2 = 0;
    }
    else
    {
      candle_attrib_1 = (u8)rand_f(2) ? 0 : 3;
      if(candle_attrib_1==0)
      {
        candle_attrib_2  = 3;
      }
      else if(candle_attrib_1 == 3)
      {
        candle_attrib_2 = 0;
      }
    }
  } // if lookback is the same
  else if( lookback_delta == 1 )
  {
    //if the candle_attrib is the close
    if( candle_attrib_1 == 3 )
    {
      //pick everything but the open
      candle_attrib_2 = (i8)rand_f_lower_bound(1,4);
    }
  }
  else if(lookback_delta == -1)
  {
    //if the candle_attrib is the open
    if( candle_attrib_1 == 0 )
    {
      //pick everything but the close
      candle_attrib_2 = rand_f(3);
    }
  }

  Result.candle_attrib_1 = candle_attrib_1;
  Result.candle_attrib_2 = candle_attrib_2;
  Result.candle_lookback_1 = candle_lookback_1;
  Result.candle_lookback_2 = candle_lookback_2;
  Result.less_than_bigger_than = less_than_bigger_than;
  return(Result);
}

static char attrib_lut[4] = {'O','H','L','C'};
static char less_than_bigger_than_lut[4] = {'<','>'};

void print_rule(rule rule_to_print)
{
  printf(" %c[%d] %c %c[%d] ",
      attrib_lut[rule_to_print.candle_attrib_1],
      rule_to_print.candle_lookback_1,
      less_than_bigger_than_lut[rule_to_print.less_than_bigger_than],
      attrib_lut[rule_to_print.candle_attrib_2],
      rule_to_print.candle_lookback_2
      );
}

b32 rules_are_equal(rule new_rule, rule old_rule)
{
  b32 Result_normal = 1;
  b32 Result_inverted = 1;

  b32 Result_total = 0;
  i32 inverted_buffer[5] = {0};
  inverted_buffer[0] = old_rule.candle_attrib_2;
  inverted_buffer[1] = old_rule.candle_lookback_2;
  inverted_buffer[2] = !old_rule.less_than_bigger_than;
  inverted_buffer[3] = old_rule.candle_attrib_1;
  inverted_buffer[4] = old_rule.candle_lookback_1;

  for(size_t i = 0; i < 5; ++i)
  {
    b32 check = !(new_rule.rule_buffer[i] == old_rule.rule_buffer[i]);
    if(check)
    {
      Result_normal = 0;
      break;
    }
  }

  for(size_t i = 0; i < 5; ++i)
  {
    b32 check = !(new_rule.rule_buffer[i] == inverted_buffer[i]);
    if(check)
    {
      Result_inverted = 0;
      break;
    }
  }

  if( (Result_inverted == 1) || (Result_normal == 1) )
  {
    Result_total = 1;
  }
  return(Result_total);
}

static inline
void generate_rule_row( rule *work_row, size_t number_of_rules_in_row, size_t pattern_lookback)
{
  size_t rules_pushed_in_row = 0;

  while(rules_pushed_in_row < number_of_rules_in_row)
  {
    //generate rule
    rule temp_rule = generate_rule(pattern_lookback);

    //initialize rules are equal check
    b32 rules_are_equal_check = 0;
    //for every other rule in the row check if this temp rule is equal to the anyone of the rules
    //if it is set the bool to one
    for(size_t check_i = 0; check_i < rules_pushed_in_row; check_i+=5)
    {
      rule check_rule = work_row[ check_i ];
      if( rules_are_equal(temp_rule, check_rule) )
      {
        rules_are_equal_check = 1;
      }
    }
    //if rules raent equal push rule to rule row
    //else repeat until we have a row of unique rules
    if(!rules_are_equal_check)
    {
      work_row[rules_pushed_in_row] = temp_rule;
      rules_pushed_in_row++;
    }
    else
    {
      printf("found equal rule\n");
      print_rule(temp_rule);
    }
  }
}

static inline
b32 replace_work_row_to_table_if_unique(rule *work_row, rule *rule_table, size_t rows_pushed, const size_t number_of_rules_in_row,
                                              size_t replace_this_row)
{
  b32 Result = 0;
  b32 found_duplicate_row = 0;

  for(size_t row_check_i = 0; row_check_i < rows_pushed; ++row_check_i)
  {
    rule *check_row = &rule_table[ (row_check_i)*number_of_rules_in_row ];

    size_t common_rules_found = 0;

    for(size_t rule_comp_i = 0; rule_comp_i < number_of_rules_in_row; ++rule_comp_i)
    {
      rule new_row_rule = work_row[rule_comp_i];
      for(size_t rule_comp_j = 0; rule_comp_j < number_of_rules_in_row; ++rule_comp_j)
      {
        rule check_rule = check_row[rule_comp_j];
        if( rules_are_equal(new_row_rule, check_rule) )
        {
          common_rules_found += 1;
        }
      }
    }

    if(common_rules_found == number_of_rules_in_row)
    {
      found_duplicate_row = 1;
      break;
    }
  }

  if(!found_duplicate_row)
  {
    rule *dest_row = (&rule_table[(replace_this_row) * number_of_rules_in_row]);
    for(size_t rule_copy_i = 0; rule_copy_i < number_of_rules_in_row; ++rule_copy_i)
    {
      dest_row[rule_copy_i] = work_row[rule_copy_i];
    }
    Result = 1;
  }
  return(Result);
}

static inline
void push_work_row_to_table_if_unique(rule *work_row, rule *rule_table, size_t *rows_pushed, const size_t number_of_rules_in_row)
{
  b32 found_duplicate_row = 0;
  //printf("entering function\n");

  for(size_t row_check_i = 0; row_check_i < *rows_pushed; ++row_check_i)
  {
    //printf(" row check i %zu rows pushed %zu\n", row_check_i , *rows_pushed );
    rule *check_row = &rule_table[ (row_check_i)*number_of_rules_in_row ];

    size_t common_rules_found = 0;

    for(size_t rule_comp_i = 0; rule_comp_i < number_of_rules_in_row; ++rule_comp_i)
    {
      rule new_row_rule = work_row[rule_comp_i];
      for(size_t rule_comp_j = 0; rule_comp_j < number_of_rules_in_row; ++rule_comp_j)
      {
        rule check_rule = check_row[rule_comp_j];
        if( rules_are_equal(new_row_rule, check_rule) )
        {
          common_rules_found += 1;
        }
      }
    }

    if(common_rules_found == number_of_rules_in_row)
    {
      found_duplicate_row = 1;
      printf("found duplicate rule row\n");
      break;
    }
  }

  if(found_duplicate_row == 0)
  {
    rule *dest_row = (&rule_table[ (*rows_pushed) * number_of_rules_in_row]);
    for(size_t rule_copy_i = 0; rule_copy_i < number_of_rules_in_row; ++rule_copy_i)
    {
      dest_row[rule_copy_i] = work_row[rule_copy_i];
    }
    *rows_pushed += 1;
  }
  else
  {
    printf("FOUND DUPLICATE skipping\n");
  }
}

static inline void copy_rule_row_from_to(rule *src_table, size_t source_row_idx, rule *dest_table, size_t dest_row_idx,size_t row_size)
{
  for(size_t copy_rule_i = 0; copy_rule_i < row_size; ++copy_rule_i)
  {
    rule *src = &src_table[(row_size*source_row_idx)+copy_rule_i];
    rule *dest = &dest_table[(row_size*dest_row_idx)+copy_rule_i];
    *dest = *src;
  }
}

void print_rule_table_with_objective( rule *table, size_t row_size, size_t rows_to_print, f64 *obj)
{
  size_t total_rules_to_print = row_size*rows_to_print;
  for(size_t print_rule_i = 0; print_rule_i < total_rules_to_print; ++print_rule_i)
  {
    print_rule(table[print_rule_i]);
    if(!(print_rule_i%(row_size-1)) && (print_rule_i != 0))
    {
      printf(" <----> [%f]\n", obj[print_rule_i/row_size]);
    }
  }
}

static inline void print_rule_row(rule *rule_row,  size_t row_size)
{
  for(size_t print_rule_i = 0; print_rule_i < row_size; ++print_rule_i)
  {
    print_rule( rule_row[print_rule_i]);
  }
  printf("\n");
}

static inline void print_rule_row_with_objective_function_from_table
(rule *rule_table, size_t row_idx, size_t row_size, f64 objective_function_value)
{
  printf("trying to print_parent with row_idx %zu\n",row_idx);
  for(size_t print_rule_i = 0; print_rule_i < row_size; ++print_rule_i)
  {
    print_rule(rule_table[(row_size*row_idx) + print_rule_i]);
  }
  printf(" <----> [%f]\n", objective_function_value);
}

void print_rule_table( rule *table, size_t row_size, size_t rows_to_print)
{
  size_t total_rules_to_print = row_size*rows_to_print;
  for(size_t print_rule_i = 0; print_rule_i < total_rules_to_print; ++print_rule_i)
  {
    if(!(print_rule_i%row_size) && (print_rule_i != 0))
    {
      printf("\n");
    }
    print_rule(table[print_rule_i]);
  }
  printf("\n");
}

typedef struct {
  f64 max;
  b32 initialized;
}Max;

/*
static inline void copy_from_table_into_work_row(rule *work_row, rule *src_table, size_t source_row_idx)
{
  for(size_t copy_rule_i = 0; copy_rule_i < row_size; ++copy_rule_i)
  {
    rule *src = &src_table[(row_size*source_row_idx)+copy_rule_i];
    rule *dest = &work_row[copy_rule_i];
    *dest = *src;
  }
}
*/

void for_every_candle_check_if_pattern_in_rule_table_is_present_accum_sums_for_matrin_ratio_calc( candle64_vec *candles,
                                                                                                  rule *selection_table,
                                                                                                  f64 *total_return,
                                                                                                  Max *total_return_max,
                                                                                                  f64 *ulcer_sq_sum,
                                                                                                  u64 *pattern_freq,
                                                                                                  size_t population_size,
                                                                                                  size_t pattern_lookback,
                                                                                                  size_t number_of_rules_in_row
                                                                                                  )
{
  //for every candle
  for(size_t candle_i = pattern_lookback-1; candle_i < candles->held-1; ++candle_i)
  {
    //for every generated rule
    for( size_t rule_row_i = 0; rule_row_i < population_size; ++rule_row_i)
    {
      rule *current_rule_row = &selection_table[rule_row_i*number_of_rules_in_row];

      size_t rules_met = 0;

      for(size_t rule_i = 0; rule_i < number_of_rules_in_row; ++rule_i)
      {
        rule current_rule = current_rule_row[rule_i];
        b32 condition = 0;
        if( current_rule.less_than_bigger_than == 1 )
        {
          condition =
            (candles->elements[candle_i-current_rule.candle_lookback_1].candle_attribs[current_rule.candle_attrib_1]
              >
            candles->elements[candle_i-current_rule.candle_lookback_2].candle_attribs[current_rule.candle_attrib_2]);
        }
        else if(current_rule.less_than_bigger_than == 0)
        {
          condition =
            (candles->elements[candle_i-current_rule.candle_lookback_1].candle_attribs[current_rule.candle_attrib_1]
              <
            candles->elements[candle_i-current_rule.candle_lookback_2].candle_attribs[current_rule.candle_attrib_2]);
        }
        else
        {
          printf("[GENETIC_ALGORITHM_LAYER_ERROR]: found pattern with unrecognizable comparison value \n");
          exit(21);
        }

        if(condition == 1)
        {
          rules_met+=1;
        }
      }

      //if all rules in the rule row are found accumulate
      if(rules_met == number_of_rules_in_row)
      {
        f64 current_close = candles->elements[candle_i].close;
        f64 next_close = candles->elements[candle_i+1].close;
        total_return[rule_row_i] += log(next_close/current_close);
        pattern_freq[rule_row_i] += 1;

        if( !total_return_max[rule_row_i].initialized )
        {
          total_return_max[rule_row_i].max = total_return[rule_row_i];
          total_return_max[rule_row_i].initialized = 1;
        }
        else
        {
          if(total_return[rule_row_i] > total_return_max[rule_row_i].max)
          {
            total_return_max[rule_row_i].max = total_return[rule_row_i];
          }
          else
          {
            f64 sqr_comp = ( (total_return[rule_row_i]/total_return_max[rule_row_i].max)-1.0f);
            ulcer_sq_sum[rule_row_i] += (sqr_comp*sqr_comp);
          }
        }
      }
    }
  }
}

typedef struct {
  f64 *value;
  u64 *key;
  size_t held;
  size_t capacity;
} u64f64_map;

u64f64_map give_u64_f64_map(size_t size)
{
  u64f64_map Result;
  Result.key = (u64*)malloc(size*sizeof(u64));
  Result.value = (f64*)malloc(size*sizeof(f64));
  if(Result.key == NULL)
  {
    printf("[TYPE_LAYER_ERROR]: failed to allocate memory for key buffer in map buy more ram\n");
    exit(21);
  }
  if(Result.value == NULL)
  {
    printf("[TYPE_LAYER_ERROR]: failed to allocate memory for value buffer in map buy more ram\n");
    exit(21);
  }
  Result.held = 0;
  Result.capacity = size;
  return(Result);
}

void zero_u64f64_map( u64f64_map m )
{
  for(size_t kv_i = 0; kv_i < m.capacity; ++kv_i)
  {
    m.key[kv_i] = 0;
    m.value[kv_i] = 0;
  }
}

void print_u64f64_map( u64f64_map m)
{
  for(size_t kv_i = 0; kv_i < m.held; ++kv_i)
  {
    printf("Key %ld Value %f\n", m.key[kv_i], m.value[kv_i]);
  }
}

void free_u64f64_map( u64f64_map m )
{
  free(m.key);
  free(m.value);
}

void
calculate_martin_ratio_for_every_non_zero_frequency_non_negative_return_pattern_and_push_to_map_in_decending_order_accum_fitness_sum
( size_t population_size,
  f64 *total_return,
  u64 *pattern_freq,
  f64 frequency_threshold,
  f64 *ulcer_sq_sum,
  f64 *fitness_sum,
  u64f64_map *map )
{
  for(size_t pattern_i = 0; pattern_i < population_size; ++pattern_i)
  {
    if( (total_return[pattern_i] > 0.0f) && (pattern_freq[pattern_i] >= frequency_threshold))
    {
      //calculate ulcer index
      f64 UI = sqrt(         (ulcer_sq_sum[pattern_i]) /   ((f64)pattern_freq[pattern_i]));
      //devide tutal return by ulcer index to get a patterns martin raio
      f64 martin_ratio_value = total_return[pattern_i]/UI;
      //append to fitness sum
      *fitness_sum += martin_ratio_value;


      //push to map sorting in descending order
      if(map->held == 0)
      {
        map->key[0] = pattern_i;
        map->value[0] = martin_ratio_value;
      }
      else
      {
        assert(map->held < map->capacity && "capcity error\n");
        i64 instert_here = map->held;
        map->value[instert_here] = martin_ratio_value;
        map->key[instert_here] = pattern_i;
        while( (map->value[instert_here-1] < map->value[instert_here]) && (instert_here-1 >= 0) )
        {
          //swap martin ratio
          f64 temp_value = map->value[instert_here-1];
          map->value[instert_here-1] = map->value[instert_here];
          map->value[instert_here] = temp_value;

          //swap idx in selection table ratio
          u64 temp_key = map->key[instert_here-1];
          map->key[instert_here-1] = map->key[instert_here];
          map->key[instert_here] = temp_key;
          instert_here--;
        }
      }
      map->held = map->held+1;
    }
  }
}



void find_two_non_equal_parent_rule_rows( u64 *parent_idx_in_selection_table, u64 *parent_idx_in_map,
                                          f64 fitness_sum, u64f64_map map, size_t population_size )
{
  size_t parents_created = 0;
  size_t prev_sorted_map_idx = population_size+1; //initializing at invalid value instead of 0 to allow for the best pair to be chose as a parent
  while(parents_created < 2)
  {
    //pick random number from 0 fitness_sum we call this the threshold
    f64 fitness_selection_threshold = rand_f(fitness_sum);
    f64 fitness_selection_ticker = 0.0f;
    size_t sorted_map_idx = 0;
    //decend the map until threshold is met once it is that parent is picked given they're not the first parent
    while(1) {
      fitness_selection_ticker += (map.value[sorted_map_idx]);
      assert( (map.key[sorted_map_idx] < (population_size))&& "false value");
      if(fitness_selection_ticker >= fitness_selection_threshold)
      {
        if(sorted_map_idx != prev_sorted_map_idx) {
          parent_idx_in_selection_table[parents_created] = map.key[sorted_map_idx];
          parent_idx_in_map[parents_created]= sorted_map_idx;
          printf("[GENETIC LAYER LOG]: found valid parent %zu/%zu selection_idx %zu sorted_map_idx %zu \n", parents_created, (size_t)2,
              parent_idx_in_selection_table[parents_created], sorted_map_idx);
          ++parents_created;
          prev_sorted_map_idx = sorted_map_idx;
        }
        break;
      }

      sorted_map_idx+=1;
    }
  }
}

int main(void)
{
  srand(time(0));
  u64 start = nanos();

  //LOAD IN ONE HOUR CANDLES
  candle64_vec eth_hourly_candles = load_candles_from_db("coinbase","ETH-USD",3600,2018,2023);

  // initialization parameters
  size_t number_of_rules_in_row = 3;
  size_t pattern_lookback = 5;
  size_t population_size = 200;
  f64 mutation_attrib_change_cance = 0.05f;

  //size_t rule_row_i = 0;
  // ALLOCATION
  size_t rules_to_alloc = number_of_rules_in_row*population_size;
  rule *rule_rows =       (rule *)malloc(sizeof(rule)*rules_to_alloc*2);

  rule *selection_table = &rule_rows[0];
  rule *breading_table =  &rule_rows[population_size*number_of_rules_in_row];

  rule *work_row =        (rule *)malloc(sizeof(rule)*number_of_rules_in_row);
  rule *work_row_two =    (rule *)malloc(sizeof(rule)*number_of_rules_in_row);

  u64f64_map map = give_u64_f64_map(population_size);


  f64 *total_return = malloc( sizeof(f64)* population_size);
  f64 *ulcer_sq_sum = malloc( sizeof(f64) * population_size);
  Max *total_return_max = malloc( sizeof(Max) * population_size);
  u64 *pattern_freq = malloc( sizeof(u64) * population_size);
  // ALOCATION

  zero_u64f64_map( map );

  //INITIAL_GENERATION
  size_t rows_pushed = 0;
  while(rows_pushed < population_size) {
    generate_rule_row( work_row, number_of_rules_in_row, pattern_lookback );
    push_work_row_to_table_if_unique( work_row, selection_table, &rows_pushed, number_of_rules_in_row);
  }
  //INITIAL_GENERATION


  size_t generation_count = 0;
  size_t total_generations = 3000;
  while(1)
  {
    //print_rule_table(selection_table, number_of_rules_in_row, population_size);

    // buffers for ulcer index calculation for each patter in the population
    // NOTE(m2_sprite): we could also just add these attributes to each row instead of having this buffers
    // but that would prevent their reusablitiy

    for(size_t comp_i = 0; comp_i < population_size; ++comp_i)
    {
      ulcer_sq_sum[comp_i] = 0.0f;
      total_return[comp_i] = 0.0f;
      pattern_freq[comp_i] = 0;
      total_return_max[comp_i].max= 0;
      total_return_max[comp_i].initialized= 0;
    }

    for_every_candle_check_if_pattern_in_rule_table_is_present_accum_sums_for_matrin_ratio_calc( ð_hourly_candles,
        selection_table,
        total_return,
        total_return_max,
        ulcer_sq_sum,
        pattern_freq,
        population_size,
        pattern_lookback,
        number_of_rules_in_row);


    zero_u64f64_map( map );
    map.held = 0;

    f64 fitness_sum = 0.0f;
    size_t frequency_threshold = eth_hourly_candles.held*0.025;

    calculate_martin_ratio_for_every_non_zero_frequency_non_negative_return_pattern_and_push_to_map_in_decending_order_accum_fitness_sum
      (
       population_size,
       total_return,
       pattern_freq,
       frequency_threshold,
       ulcer_sq_sum,
       &fitness_sum,
       &map
      );


    printf("population fitness sum %f\n", fitness_sum);

    //copy rule_row with best martin ratio to the top of the breading table
    //ELITISM
    copy_rule_row_from_to( selection_table, map.key[0], breading_table, 0, number_of_rules_in_row);

    printf("OPTIMAL PATTERN FOUND\n");
    print_rule_row_with_objective_function_from_table( breading_table, 0,  number_of_rules_in_row,
                                                      map.value[0]);

    if(generation_count == total_generations)
    {
      break;
    }

    size_t new_population_created_count = 1;
    //ELITISM

    //PARENT SELECTION SPLICING AND BREADING
    while( new_population_created_count < population_size )
    {
      printf("%zu/%zu\n",new_population_created_count, population_size);
      u64 parent_idx_in_selection_table[2] = {0};
      u64 parent_idx_in_map[2] = {0};

      printf("pre function\n");
      //print_u64f64_map(map);
      find_two_non_equal_parent_rule_rows( parent_idx_in_selection_table, parent_idx_in_map,
          fitness_sum,
          map,
          population_size);
      printf("post function\n");
      //print_u64f64_map(map);
      printf("generation------------------------- %zu\n", generation_count);

      rule *parent_one = &selection_table[parent_idx_in_selection_table[0]*number_of_rules_in_row];
      rule *parent_two = &selection_table[parent_idx_in_selection_table[1]*number_of_rules_in_row];

      printf("post paretns asignment\n");
      //print_u64f64_map(map);
      printf("NEW PARETNS: \n");
      print_rule_row_with_objective_function_from_table( selection_table,  parent_idx_in_selection_table[0],
          number_of_rules_in_row, map.value[parent_idx_in_map[0]]);

      print_rule_row_with_objective_function_from_table( selection_table,  parent_idx_in_selection_table[1],
          number_of_rules_in_row, map.value[parent_idx_in_map[1]]);

      printf("pre split point\n");
      //print_u64f64_map(map);
      size_t split_point = (size_t)rand_f(number_of_rules_in_row-1);

      for(size_t rule_i = 0; rule_i < number_of_rules_in_row; ++rule_i)
      {
        if(rule_i <= split_point)
        {
          work_row[rule_i] = parent_one[rule_i];
          work_row_two[rule_i] = parent_two[rule_i];
        }
        else
        {
          work_row[rule_i] = parent_two[rule_i];
          work_row_two[rule_i] = parent_one[rule_i];
        }
      }


      printf("new_population_count %zu\n",new_population_created_count);
      //print_rule_table(breading_table, number_of_rules_in_row, new_population_created_count);

      printf("CHILDREN: \n");
      printf("Child one\n");
      push_work_row_to_table_if_unique( work_row, breading_table, &new_population_created_count, number_of_rules_in_row);
      print_rule_row(work_row, number_of_rules_in_row);


      if(new_population_created_count != population_size)
      {
        printf("Child two\n");
        push_work_row_to_table_if_unique( work_row_two, breading_table, &new_population_created_count, number_of_rules_in_row);
        print_rule_row(work_row_two, number_of_rules_in_row);
      }
      else
      {
        printf("PUSHING LAST CHILD\n");
        break;
      }

    }
    printf("fintness sum %f\n", fitness_sum);
    //PARENT SELECTION SPLICING AND BREADING

    //MUTATION
    b32 do_we_apply_fresh_pattern_change = rand_f(1.0f) <= mutation_attrib_change_cance ? 1 : 0;

    printf("before mutation\n");
    if(do_we_apply_fresh_pattern_change)
    {
      while(1)
      {
        size_t random_row_idx_that_isnt_the_optimal = rand_f_lower_bound(1.0f, (f64)population_size);
        assert(random_row_idx_that_isnt_the_optimal < population_size && "[GENETIC_LAYER_ERROR]: TRYING TO INDEX FALSE ROW FOR MUTATION\n");
        assert(random_row_idx_that_isnt_the_optimal != 0 && "[GENETIC_LAYER_ERROR]: TRYING TO INDEX FALSE ROW FOR MUTATION\n");
        printf("[GENETIC_LAYER_LOG]: Trying to apply fresh pattern muation\n");
        generate_rule_row( work_row, number_of_rules_in_row, pattern_lookback );
        if(replace_work_row_to_table_if_unique(work_row, breading_table, population_size, number_of_rules_in_row, random_row_idx_that_isnt_the_optimal))
        {
          printf("[GENETIC_LAYER_LOG]: Applied mutation successfully\n");
          break;
        }
      }
    }

    //MUTATION
    //flip selection table and breading table pointers
    rule *temp_selection_table = selection_table;
    selection_table = breading_table;
    breading_table = temp_selection_table;
    generation_count++;
    printf("generation_count %zu\n",generation_count);
  }

  free(rule_rows);
  free_u64f64_map(map);
  free(work_row);
  free(work_row_two);
  free(total_return);
  free(ulcer_sq_sum);
  free(total_return_max);
  free(pattern_freq);

  u64 end = nanos();
  perf_log_runtime_seconds(start, end);
  return(0);
}