#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
  **** anarchy.eco simulation ****
__________________________________________________________________
CONTACT: contact@anarchy.eco (http://anarchy.eco)
"""

import random, statistics, re, os, dbm
CLIPS = '/var/www/html/clips'

class eeco_space:
    def __init__(s, arg=(10, 10, 5, 100, 100, 1000)):
        (s.PEOPLE, s.BOTS, s.GRPS, s.OPS, s.MAXPRICE, s.DEBT)  = arg
        e3, e4 = [.0, .0, .0], [0, .0, .0, .0]
        s.bal, s.rbt, s.med, s.rev, s.grp, s.op = [.0]*s.PEOPLE, [e4]*s.BOTS, [e3]*s.GRPS, [{}]*s.PEOPLE, [list(range(s.PEOPLE))], []
        s.b1, s.b2 = [.0]*s.PEOPLE, [.0]*s.PEOPLE
        s.grp += [random.sample(range(1, s.PEOPLE), k=random.randint(3, max(3, s.PEOPLE//2))) for i in range(s.GRPS-1)]
        for i in range(s.PEOPLE):
            s.rev[i] = {j:[.0, .0, .0, 1+random.randint(0, 9)] for j, x in enumerate(s.grp) if i in x}
        s.rev[0][0] = [.0, .0, .0, 1]
        s.rbt[0] = [0, 1.0, .0, .0]
        for i in range(1, s.BOTS): s.rbt[i] = [random.randint(1, 1000), round(1+random.random()*100, 2), .0, .0] 
        s.vote(s.grp[0], True)

    def bound(s, v):
        if v < 0: return 0
        if v > 1: return 1
        return v

    def vote(s, popu, init=False):
        a = '%d humans vote'%len(popu) if len(popu)>1 else '1 vote'
        for u in popu:
            if not init:
                a += '\nHuman %03X votes:'%u
            for g in s.rev[u]:
                for i in range(3):
                    s.rev[u][g][i] = s.bound(random.normalvariate(.3, .1))
                if not init:
                    a += '\n for group G%02d: '%g
                    a += ' '.join(['%d%%'%round(s.rev[u][g][i]*100) for i in range(3)])
        s.op.append([False, a])
        
    def operation(s):
        if random.randint(0, 10) > 3: s.transaction()
        else: s.vote(random.sample(range(s.PEOPLE), k=random.randint(1, min(s.PEOPLE, 5))))
        
    def transaction(s):
        actors = [buyer, seller] = random.sample(range(1, s.PEOPLE), k=2)
        price, co2 = random.randint(1, s.MAXPRICE), random.randint(0, 100)
        for k, g in enumerate(s.grp):
            for i in range(3): s.med[k][i] = statistics.median([s.rev[x][k][i] for x in g])
        com = {l:s.med[l][0]+s.med[l][1]*co2/100 for l in s.rev[seller].keys() if l in s.rev[buyer].keys()}
        tax = sum(com.values())/len(com)
        if price*(1+tax) - s.bal[buyer] < s.DEBT:
            s.op.append([True, buyer, price*(1+tax), seller, price, co2, True])            
            s.bal[buyer]  -= price*(1+tax)
            s.bal[seller] += price
            s.b1[buyer]  -= price
            s.b1[seller] += price
            s.b2[buyer]  -= price
            for x in s.grp[0]: s.b2[x] += price/s.PEOPLE
            for y in com:
                for x in s.grp[y]:
                    s.bal[x] += price*s.rev[x][y][3]*com[y]/len(com)/sum([s.rev[x][y][3] for x in s.grp[y]])
        else:
            s.op.append([True, buyer, price*(1+tax), seller, 0, .0, False])
        assert int(sum(s.bal))==0 and int(sum(s.b1))==0 and int(sum(s.b2))==0
        
    def print(s):
        print ('Group:%s\nMedian:%s\nReverse%s\nBalance:%s\n'% (s.grp, s.med, s.rev, s.bal))

class eeco_time:
    def __init__(s): # bots[investment] -> (Efficiency([1,10]), Capacity([0,100]))
        s.t, s.TECHNO, s.p, s.b, s.debt, s.free, s.l = 0, 20, [0], 0, -1000, 0, 0
        s.m = [(1.0, 10.0)] + [(round(1+random.random()*9, 2), round(random.random()*10, 2)) for i in range(s.TECHNO)]
        
    def next(s):
        # 0 - Time
        s.t += 1
        # 1 - Production
        s.b += sum([s.m[x][0]*s.m[x][1] for x in s.p])
        s.f  = sum([s.m[x][0] for x in s.p]) - 1
        # 2 - Consumption
        if s.b>50: s.b *= .5 # proportional -- rich
        s.b -= 40            # constant     -- poor
        # 3 - Investment
        if s.b>s.debt: 
            c, ivmin = 0, min(int(s.b-s.debt), len(s.m)-1)
            for i, x in enumerate(s.m[1:ivmin+1]):
                if x[1] > c: c, s.l = x[1], i
            s.p.append(s.l)
            s.b -= s.l
        # 4 - Uninvestment
        # TBD
        # 5 - Evaluation (Balance, Capital, Freedom)
        print ('%04d bal:%07.2f lCap:%02d nCap:%03d tCap:%06d free:%07.2f'%(s.t, round(s.b, 2), s.l, len(s.p)-1, sum(s.p), round(s.f, 2)))
        
def reg(v):
    reg.v = v
    return v

def clientip(env):
    try: return env['HTTP_X_FORWARDED_FOR'].split(',')[-1].strip()
    except KeyError: return env['REMOTE_ADDR']

def application(env, response):
    arg = (100, 10, 10, 100, 100, 1000)
    if reg(re.match('^h=(\d{1,4})&r=(\d{1,3})&g=(\d{1,3})&o=(\d{1,4})&p=(\d{1,3})&d=(\d{3,6})$', env['QUERY_STRING'])):    
        arg = tuple(int(reg.v.group(i)) for i in range(1, 7))
    eco = eeco_space(arg)
    for t in range(eco.OPS-1): eco.operation()
    o = '<!DOCTYPE html><html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><meta charset="utf-8">\n'
    o += '<link rel="stylesheet" href="anarchy.css"/>\n'
    o += '<script type="text/javascript" src="anarchy.js"></script>\n'
    o += '<title>Anarchy.eco</title></head><body>\n'    
    o += '<div><form action="/sim"><table>\n<tr><td>Nb-of-humans</td><td><input name="h" type="number" min="3" max="1000" value="%d" size="10"/></td></tr>\n<tr><td>Nb-of-robots</td><td><input name="r" type="number" min="1" max="100" value="%d" size="10"/></td></tr>\n<tr><td>Nb-of-groups</td><td><input name="g" type="number" min="1" max="100" value="%d" size="10"/></td></tr>\n<tr><td>Nb-of-operations</td><td><input name="o" min="1" max="10000" type="number" value="%d" size="10"/></td></tr>\n<tr><td>Max-price</td><td><input name="p" type="number" min="10" max="1000" value="%d" size="10"/></td></tr>\n<tr><td>Max-debt</td><td><input name="d" type="number" min="1000" max="100000" value="%d" size="10"/></td></tr>\n<tr><td colspan="2"><input class="submit" type="submit" value="Simulation"/></td></tr>\n<table></form></div>\n'%arg
    o += '<div class="haut"><h1 class="haut" title="...under construction ;-)"><a href=".."><span class="blue">anarchy</span><span class="grey"> ● </span><span class="green">eco</span> <span class="grey">Simulation</span></a></h1>'
    
    o += '<h6>Move the pointer over circles to see simulation results</h6></div>'
    o += '<h6>Max Price: %d | Max Dept: %d</h6>'%(eco.MAXPRICE, eco.DEBT)
    # HUMANS
    o += '<h2 title="Only humans may have only one account each\nTotal green surface equal total blue surface anytime\nSum of all balances is null anytime.">%d Humans'%eco.PEOPLE
    o +=' <div class="i" full="Negative balance">-100</div> <span class="up">=</span> <div class="i" full="Positive Balance" name="">100</div></h2>\n'
    for i, p in enumerate(eco.bal):
        d0, d1, d2 = 'b0="%7.2f"'%round(p, 2), 'b1="%7.2f"'%round(eco.b1[i], 2), 'b2="%7.2f"'%round(eco.b2[i], 2)
        g = 'g="' + ' '.join(['G%02X(%s)'%(k, eco.rev[i][k][3]) for k in eco.rev[i].keys()]) + '"'
        o +='<div class="i" name="%03X" %s %s %s %s>%s</div>\n'%(i, g, d0, d1, d2, p*100/eco.DEBT)
    # GROUPS
    o += '<h2 title="Each group is proposed for membership with predefined check-in check-out rules">'
    o += '%d Groups'%eco.GRPS if eco.GRPS>1 else '1 Group'
    o += '</h2>'
    for i, g in enumerate(eco.grp):
        gx = '%d Members: '%len(g)
        gx += 'all' if i == 0 else ' '.join(['%03X'%k for k in g])
        lab = ['Solidarity', 'Ecology', 'Social']
        gm = 'Medians:\t'+'\t'.join(['%s: %s %%'%(lab[j], round(100*k, 2)) for j, k in enumerate(eco.med[i])])
        o += '<div class="g" name="G%02d" nb="%d">%s\n%s</div>\n'%(i, len(g)*100//eco.PEOPLE, gm, gx)
    # OPERATIONS
    o += '<h2 title="One operation may be a transaction or a set of votes">'
    o += '%d Operations'%eco.OPS if eco.OPS>1 else '1 Operation'
    o += '</h2>'
    for i, t in enumerate(eco.op):
        if t[0] == False:
            o += '<div class="t" op="vote" name="V%03d">%s</div>\n'%(i, t[1])
        else:
            dt = 'Human %03X pays\t\t%7.2f\nHuman %03X receives\t%7.2f'%(t[1], round(t[2], 2), t[3], round(t[4], 2))
            dt += "\nCO₂ Equiv. Tax Ratio:\t%d %%"% round(t[5])
            ivl = 'op="invalid"' if t[6]==False else ''
            co = ' co2="%d"'%round(t[5])
            if t[6]==False:
                dt = "Human %03X requested to pay\t%7.2f\nOver Debt, transaction not validated"%(t[1], round(t[2], 2))
            o += '<div class="t" name="T%03d" %s%s>%s</div>\n'%(i, ivl, co, dt)
    # ROBOTS
    o += '<h2 title="TBD">'
    o += '%d Robots'%eco.BOTS if eco.BOTS>1 else '1 Operation'
    o += '</h2>'
    for i, t in enumerate(eco.rbt): o += '<div class="r" name="R%02d">%s</div>\n'%(i, t)
    if os.path.exists(CLIPS):
        with dbm.open(CLIPS, 'c') as b: 
            ip = clientip(env).encode('utf-8')
            if ip not in b.keys(): b[ip] = b'yes'
            o += '<h6>%d clients</h6>'%len(b.keys())
    o += '<a href="mailto:contact@anarchy.eco"><div class="contact">contact<span class="helv">@</span>anarchy.eco&nbsp;</div></a>\n'
    o += '</body></html>\n'
    response('200 ok', [('Content-Type', 'text/html;charset=utf-8')])
    return [o.encode('utf-8')]

def test():
    a = eeco_time()
    for i in range(100): a.next()

import wsgiref.simple_server
if __name__ == "__main__":
    #test()
    wsgiref.simple_server.make_server('', 8000, application).serve_forever()

# End ⊔net!

