"""Finite Bernoulli runs and bounded unit-stake ruin, educational only.

Run probabilities use a first-occurrence recurrence. Longest-run mean is
the finite tail sum; truncation is bounded by a geometric union bound.
No seeds, casino records or funding policy is inferred from this model.
"""
from fractions import Fraction as F
import json
import logging


def run_tail(length,rounds,q,exact=False):
    if not isinstance(length,int) or length<1 or not isinstance(rounds,int) or rounds<0 or not 0<q<1:
        raise ValueError('Positive length, nonnegative horizon and probability in(0,1) required')
    q=F(q) if exact else float(q);one=F(1) if exact else 1.0
    if rounds<length:return one-one
    power=q**length;survival=[one]*length+[one-power]
    for n in range(length+1,rounds+1):
        value=survival[-1]-(one-q)*power*survival[n-length-1]
        survival.append(value if exact else max(0.0,min(1.0,value)))
    return one-survival[-1]


def longest_mean(rounds,q,tolerance=1e-11):
    if not isinstance(rounds,int) or rounds<1 or not 0<q<1 or not 0<tolerance<1:
        raise ValueError('Valid horizon, probability and tolerance required')
    q=float(q);mean=0.0
    for k in range(1,rounds+1):
        mean+=run_tail(k,rounds,q)
        # Sum_{j=k+1}^N P(L>=j) <= N*q^(k+1)/(1-q).
        bound=rounds*q**(k+1)/(1-q)
        if bound<tolerance:return dict(mean=mean,omittedTailBound=bound,throughLength=k)
    return dict(mean=mean,omittedTailBound=0.0,throughLength=rounds)


def ruin_before_target(bank,target,p=F(97,200)):
    if not isinstance(bank,int) or not isinstance(target,int) or not 0<bank<target or not 0<p<1:
        raise ValueError('Integer0<bank<target and0<p<1 required')
    p=F(p);q=1-p
    if p==q:return F(target-bank,target)
    ratio=q/p
    return (ratio**target-ratio**bank)/(ratio**target-1)


if __name__=='__main__':
    logging.basicConfig(level=logging.INFO,format='%(message)s')
    logging.info(json.dumps(dict(run10Within1000=run_tail(10,1000,F(103,200)),
        meanLongestLoss1000=longest_mean(1000,F(103,200)),
        ruin100Before200=float(ruin_before_target(100,200))),indent=2))
