#include "base/base_inc.h"
#include "os/os_inc.h"
#include "base/base_inc.c"
#include "base/base_big_functions.c"
#include "os/os_inc.c"

#include <stdio.h>

////////////////////////////////
// NOTE(allen): Types

typedef struct Array_U8{
  U8 *v;
  U64 count;
} Array_U8;

typedef struct EulerData{
  U8 *v;
  U64 count;
  U64 line_count;
} EulerData;

typedef struct ListNode_U32{
  struct ListNode_U32 *next;
  Array_U32 array;
} ListNode_U32;

typedef struct List_U32{
  ListNode_U32 *first;
  ListNode_U32 *last;
  U64 node_count;
  U64 total_count;
} List_U32;

typedef struct Pair_U64{
  U64 a;
  U64 b;
} Pair_U64;

typedef struct Pair_U32{
  U32 a;
  U32 b;
} Pair_U32;

typedef struct U64x3{
  U64 v[3];
} U64x3;

typedef struct U64x3_DivR{
  U64x3 q;
  U64 r;
} U64x3_DivR;

typedef U64 U64xN;

////////////////////////////////
// NOTE(allen): Assembly Function Declarations

c_linkage U64 triangle_number(U64 n);
c_linkage U64 sum_of_squares(U64 n);

c_linkage void fibonacci_stepper_mul(void *dst, void *src);
c_linkage void fibonacci_stepper_sqr(void *dst);
c_linkage U64 fibonacci_number(U64 n);
c_linkage U64 fibonacci_sigma(U64 n);

c_linkage U64 choose__asm(U64 *buffer, U64 n, U64 m);

c_linkage void fill_factorial_table(U64 *table, U64 count);

c_linkage U32 prime_sieve__asm(B8 *table_memory,
                               U32 *primes_list,
                               U32 first, U32 opl,
                               ListNode_U32 *node);

c_linkage void factorization_table__asm(U32 *table_memory, U32 count);

c_linkage U32  get_prime_factor(U32 *fact_tbl, U32 count, U32 n);
c_linkage Pair_U32 max_divide(U32 n, U32 f);
c_linkage U32  divisor_count(U32 *fact_tbl, U32 count, U32 n);
c_linkage U64  divisor_sum(U32 *fact_tbl, U32 count, U32 n);

c_linkage Pair_U64 find_bounded_factors(U64 min, U64 max, U64 target_product);

c_linkage U64 gcd_euclidean(U64 a, U64 b);
c_linkage U64 lcm_euclidean(U64 a, U64 b);

c_linkage EulerData euler_data_from_text__asm(String8 text, U8 *memory);
c_linkage EulerData euler_data_from_text_2dig__asm(String8 text, U8 *memory);

c_linkage U64x3      add_u64x3(U64x3 a, U64x3 b);
c_linkage U64x3      add_small_u64x3(U64x3 a, U64 b);
c_linkage U64x3      mul_small_u64x3(U64x3 a, U64 b);
c_linkage U64x3_DivR div_small_u64x3(U64x3 n, U64 d);

c_linkage U64x3      u64x3_from_dec(U8 *digits, U64 count);
c_linkage U64        dec_from_u64x3__asm(U8 *out, U64x3 x);

c_linkage void add_small_in_place_u64xn(U64xN *a, U64 b);
c_linkage void mul_small_in_place_u64xn(U64xN *a, U64 b);
c_linkage U64  div_small_in_place_u64xn(U64xN *n, U64 d);

c_linkage U64 dec_from_u64xn__asm(U8 *out, U64xN *x); // x is "destroyed" by this call

////////////////////////////////
// NOTE(allen): Helpers/Wrappers with C/C++ Niceness

function U64
choose(U64 n, U64 m){
  U64 result = 0;
  if (m <= n){
    ArenaTemp scratch = arena_get_scratch(0, 0);
    U64 *buffer = push_array(scratch.arena, U64, n + 1);
    result = choose__asm(buffer, n, m);
    arena_end_temp(&scratch);
  }
  return(result);
}

function void
prime_sieve(Arena *arena, List_U32 *primes, U32 first, U32 opl){
  Assert(Implies(primes->node_count == 0, first == 3));
  Assert(first < opl);
  
  // allocate memory
  ArenaTemp scratch = arena_get_scratch(&arena, 1);
  U32 max_count = opl - first;
  U32 *primes_list = push_array(arena, U32, max_count);
  B8  *sieve_table = push_array(scratch.arena, B8, max_count);
  
  // run sieve
  U32 count = prime_sieve__asm(sieve_table, primes_list, first, opl,
                               primes->first);
  
  // put back memory we didn't use in the primes list
  arena_pop_amount(arena, sizeof(*primes_list)*(max_count - count));
  arena_align(arena, 8);
  
  // insert new primes block onto the list
  Array_U32 array = {0};
  array.vals = primes_list;
  array.count = count;
  
  ListNode_U32 *node = push_array(arena, ListNode_U32, 1);
  node->array = array;
  SLLQueuePush(primes->first, primes->last, node);
  primes->node_count += 1;
  primes->total_count += array.count;
  
  arena_end_temp(&scratch);
}

function Array_U32
factorization_table(Arena *arena, U32 count){
  U32 *tbl = push_array(arena, U32, count);
  factorization_table__asm(tbl, count);
  
  Array_U32 result = {0};
  result.vals = tbl;
  result.count = count;
  
  return(result);
}

function EulerData
euler_data_from_text(Arena *arena, String8 text, U64 num_digits){
  Assert(num_digits == 1 || num_digits == 2);
  
  U8 *memory = push_array(arena, U8, text.size);
  EulerData result = {0};
  if (num_digits == 1){
    result = euler_data_from_text__asm(text, memory);
  }
  else if (num_digits == 2){
    result = euler_data_from_text_2dig__asm(text, memory);
  }
  arena_pop_amount(arena, text.size - result.count);
  arena_align(arena, 8);
  
  return(result);
}

function Array_U8
dec_from_u64x3(Arena *arena, U64x3 x){
  Array_U8 result = {0};
  
  if (x.v[0] == 0 && x.v[1] == 0 && x.v[2] == 0){
    local U8 zbuffer[1] = {0};
    result.v = zbuffer;
    result.count = 1;
  }
  else{
    U64 cap = 60;
    
    U8 *buffer = push_array(arena, U8, cap);
    U64 count = dec_from_u64x3__asm(buffer, x);
    arena_pop_amount(arena, cap - count);
    arena_align(arena, 8);
    
    result.v = buffer;
    result.count = count;
  }
  
  return(result);
}

function U64xN*
copy_u64xn(Arena *arena, U64xN *x){
  U64 size = (*x) + 1;
  U64xN *result = push_array(arena, U64, size);
  MemoryCopy(result, x, sizeof(*x)*size);
  return(result);
}

function Array_U8
dec_from_u64xn(Arena *arena, U64xN *x){
  Array_U8 result = {0};
  
  if (*x == 0){
    local U8 zbuffer[1] = {0};
    result.v = zbuffer;
    result.count = 1;
  }
  else{
    ArenaTemp scratch = arena_get_scratch(&arena, 1);
    
    U64 cap = (*x)*20;
    
    U64xN *x_copy = copy_u64xn(scratch.arena, x);
    
    U8 *buffer = push_array(arena, U8, cap);
    U64 count = dec_from_u64xn__asm(buffer, x_copy);
    arena_pop_amount(arena, cap - count);
    arena_align(arena, 8);
    
    result.v = buffer;
    result.count = count;
    
    arena_release_scratch(&scratch);
  }
  
  return(result);
}

function U64
dec_repeating_cycle_score(U64 d){
  U64 result = 0;
  
  if (d > 0){
    // divide out 2s
    for (;(d & 1) == 0;){
      d >>= 1;
    }
    
    // divide out 5s
    for (;(d % 5) == 0;){
      d /= 5;
    }
    
    // check 9s
    if (d > 1){
      U64 score = 1;
      U64 buf1[60] = {1, 9};
      U64 buf2[60] = {0};
      for (;;){
        memcpy(buf2, buf1, sizeof(buf1));
        U64 remainder = div_small_in_place_u64xn(buf1, d);
        if (remainder == 0){
          break;
        }
        score += 1;
        memcpy(buf1, buf2, sizeof(buf1));
        mul_small_in_place_u64xn(buf1, 10);
        add_small_in_place_u64xn(buf1, 9);
      }
      
      result = score;
    }
  }
  
  return(result);
}

int
main(void){
  Arena *arena = arena_alloc();
  
  U64 max_score = 0;
  for (U64 d = 1; d < 1000; d += 1){
    U64 score = dec_repeating_cycle_score(d);
    max_score = Max(score, max_score);
    printf("score(%llu) = %llu\n", d, score);
  }
  
  
  return(0);
}

//$ console //
