"""Independent reciprocal97% educational sessions, not provider observations.

Flat profiles start100, stake1, horizon100. Every wager is fundable until
the last settlement; only100 losses give zero. Martingale starts100, base1,
2x payout, hypothetical cap64, at most100 opportunities. Blocking is checked
after each settlement including the last, with residual cash retained.
"""
from fractions import Fraction as F
from math import comb
import json
import logging


def flat_profile(target_cents):
    if target_cents not in (110,200,1000):
        raise ValueError('Targets110/200/1000 cents only')
    p=F(97,target_cents);q=1-p
    masses=[F(comb(100,k))*p**k*q**(100-k) for k in range(101)]
    net=[F(k*target_cents,100)-100 for k in range(101)]
    assert sum(masses)==1 and sum(x*y for x,y in zip(net,masses))==-3
    return dict(target=target_cents/100,pWin=float(p),expectedWins=float(100*p),
                meanGross=97.0,meanFinal=97.0,turnover=100,
                profitProbability=float(sum(m for x,m in zip(net,masses) if x>0)),
                zeroBalanceProbability=float(q**100))


def run_probability(length,rounds,q=F(103,200)):
    if not isinstance(length,int) or length<1 or not isinstance(rounds,int) or rounds<0 or not 0<=q<=1:
        raise ValueError('Valid run length, horizon and probability required')
    active=[F(1)]+[F(0)]*(length-1);hit=F(0)
    for _ in range(rounds):
        hit+=active[-1]*q
        active=[sum(active)*(1-q)]+[active[j-1]*q for j in range(1,length)]
    assert hit+sum(active)==1
    return hit


def martingale(rounds=100,initial=100,cap=64,exact=False):
    if any(not isinstance(x,int) or x<1 for x in (rounds,initial,cap)):
        raise ValueError('Positive integer parameters required')
    p=F(97,200) if exact else .485;q=1-p;z=F(0) if exact else 0.0
    active={(initial,1):1-z};stopped={};turn=z
    for _ in range(rounds):
        following={}
        for (cash,bet),mass in active.items():
            turn+=mass*bet
            for won,prob in [(True,p),(False,q)]:
                after=cash+bet if won else cash-bet;next_bet=1 if won else bet*2
                if next_bet>after or next_bet>cap:
                    stopped[after]=stopped.get(after,z)+mass*prob
                else:following[(after,next_bet)]=following.get((after,next_bet),z)+mass*prob
        active=following
    terminal=dict(stopped)
    for (cash,_),mass in active.items():terminal[cash]=terminal.get(cash,z)+mass
    assert sum(terminal.values())==1 if exact else abs(sum(terminal.values())-1)<1e-12
    return dict(blocked=sum(stopped.values()),zeroBalance=stopped.get(0,z),expectedTurnover=turn,
                meanFinal=sum(cash*mass for cash,mass in terminal.items()),
                profitProbability=sum(mass for cash,mass in terminal.items() if cash>initial))


if __name__=='__main__':
    logging.basicConfig(level=logging.INFO,format='%(message)s')
    logging.info(json.dumps(dict(profiles=[flat_profile(c) for c in (110,200,1000)],
        run7Within100=float(run_probability(7,100)),martingale=martingale()),indent=2))
