#Juggler sequence brute force calculator
#2026-05-12, Ulrich Neuenschwander


import sys
import os
from math import isqrt, floor, log10

sys.set_int_max_str_digits(10000000)

CACHE_FILE = "juggler_cache.txt"
RESULTS_FILE = "juggler_results.txt"
MAX_CACHE_BYTES = 3 * 1024 ** 3  # 3 GB

def load_cache():
    cache = {}
    if os.path.exists(CACHE_FILE):
        with open(CACHE_FILE) as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                n_str, steps, digits, pos = line.split(",")
                cache[int(n_str)] = (int(steps), int(digits), int(pos))
    return cache

def cache_full():
    if not os.path.exists(CACHE_FILE):
        return False
    return os.path.getsize(CACHE_FILE) >= MAX_CACHE_BYTES

def save_to_cache(cache, n, steps, digits, pos):
    cache[n] = (steps, digits, pos)
    if not cache_full():
        with open(CACHE_FILE, "a") as f:
            f.write(f"{n},{steps},{digits},{pos}\n")

def digit_count(n):
    if n == 0:
        return 1
    return floor(log10(n)) + 1

def juggler(n, cache):
    n = int(n)
    temp, i, max_val, pos = n, 0, n, 0
    history = []

    while temp > 1:
        if digit_count(temp) > 10000 and temp in cache:
            cached_steps, cached_digits, _ = cache[temp]
            total_steps = i + cached_steps
            final_digits = max(digit_count(max_val), cached_digits)
            for val, step_at, mv, mp in history:
                if digit_count(val) > 10000 and val not in cache:
                    remaining = total_steps - step_at
                    save_to_cache(cache, val, remaining, max(digit_count(mv), cached_digits), mp)
            return total_steps, final_digits, pos

        if digit_count(temp) > 10000:
            history.append((temp, i, max_val, pos))

        i += 1
        if i % 1000 == 0:
            print(f"  step {i}, digits={digit_count(temp)}")
        if temp % 2:
            temp = isqrt(temp ** 3)
        else:
            temp = isqrt(temp)
        if temp > max_val:
            max_val, pos = temp, i

    for val, step_at, mv, mp in history:
        if val not in cache:
            remaining = i - step_at
            save_to_cache(cache, val, remaining, digit_count(max_val), mp)

    return i, digit_count(max_val), pos

def get_range():
    while True:
        try:
            start = int(input("Start value: "))
            end = int(input("End value:   "))
            if start < 1:
                print("Start must be at least 1.")
            elif end < start:
                print("End must be >= start.")
            else:
                return start, end + 1
        except ValueError:
            print("Please enter whole numbers only.")

def format_result(n, steps, digits, peak_pos):
    if n < 40:
        return f"{n}: l[n] = {steps}, h[n] = {digits}, i[n] = {peak_pos}"
    else:
        return f"{n}: l[n] = {steps}, d[n] = {digits}, i[n] = {peak_pos}"

cache = load_cache()
print(f"Loaded {len(cache)} cached values")
if cache_full():
    print(f"Cache is at or over 3 GB limit — no new entries will be written")

start, end = get_range()

with open(RESULTS_FILE, "a") as results:
    results.write(f"# Range {start} to {end - 1}\n")
    results.flush()
    for n in range(start, end):
        steps, digits, peak_pos = juggler(n, cache)
        line = format_result(n, steps, digits, peak_pos)
        print(line)
        results.write(line + "\n")
        results.flush()

