"""ASX revision 08: protected capability cassettes and external audio instruments."""

def stroke(g,name,pts,mat=WHITE,width=.018):
    for a,b in zip(pts,pts[1:]):
        a,b=Vector(a),Vector(b);delta=b-a
        o=cube(name,(a+b)/2,(width,width,delta.length),mat,width/3)
        o.rotation_euler=delta.to_track_quat('Z','Y').to_euler();parent(o,g)

def arrow(g,name,x,z,size=.65,y=-.02,angle=0,mat=WHITE):
    p=[(-.13,-.48),(.13,-.48),(.13,.08),(.38,.08),(0,.48),(-.38,.08),(-.13,.08)]
    ca,sa=math.cos(angle),math.sin(angle)
    pts=[(x+size*(u*ca-v*sa),y,z+size*(u*sa+v*ca)) for u,v in p]
    return parent(mesh(name,pts,[tuple(range(len(pts)))],mat,0,False),g)

def nozzle(g,name,w,h,z=0):
    """Recessed rectangular thruster: open bevelled throat, vanes, orange registers."""
    root=group(name);parent(root,g);root.location.z=z
    def octagon(ww,hh,yy):
        c=min(ww,hh)*.19
        return [(-ww/2+c,yy,-hh/2),(ww/2-c,yy,-hh/2),(ww/2,yy,-hh/2+c),(ww/2,yy,hh/2-c),(ww/2-c,yy,hh/2),(-ww/2+c,yy,hh/2),(-ww/2,yy,hh/2-c),(-ww/2,yy,-hh/2+c)]
    rings=[octagon(w,h,-.16),octagon(w-.20,h-.20,-.20),octagon(w-.44,h-.40,.29)]
    verts=sum(rings,[]);faces=[]
    for j in range(2):
        for i in range(8):faces.append((j*8+i,j*8+(i+1)%8,(j+1)*8+(i+1)%8,(j+1)*8+i))
    parent(mesh(name+' / titanium throat',verts,faces,ALUMINUM,.018),root)
    parent(cube(name+' / dark chamber',(0,.34,0),(w-.35,.07,h-.34),BLACK,.10),root)
    count=max(5,int(h/.31))
    for i in range(count):
        zz=-h/2+.34+i*(h-.68)/max(1,count-1)
        parent(cube(name+' / turbine vane',(0,.12,zz),(w-.48,.37,.09),BASE,.018,rot=(.34,0,0)),root)
    for side in (-1,1):
        for k in (-1,1):
            parent(cube(name+' / orange register',(side*(w/2-.15),-.225,k*(h/2-.24)),(.105,.025,.34),ORANGE,.018),root)
    return root

def side_thruster(g,body,title,w,d,top):
    left,panel=side_panel(g,title.lower()+'_side_left',-1,w,d,top)
    bpy.context.view_layer.update()
    for target in (body,panel):
        cut(target,cube('Thruster / cut',left.matrix_world@Vector((0,.13,3.10)),(d-.40,.92,4.08),None,.11,rot=(0,0,-math.pi/2)))
    nozzle(left,title+' / thermal exhaust',d-.30,4.20,3.10)
    face_text(left,title+' / caution','CAUTION',0,5.65,.21,WHITE,y=-.09,align='CENTER',bold=True)
    face_text(left,title+' / hot','HOT',0,5.34,.26,ORANGE,y=-.09,align='CENTER',bold=True)
    face_text(left,title+' / exhaust ID','EXHAUST / 01',0,.63,.15,y=-.09,align='CENTER')
    return left

def family_front(g,title,code,role,feature,detail,w,d,top):
    y=-d/2-.025;sy=y-.113
    parent(cube(title+' / front gasket',(0,y,3.39),(w-.36,.095,5.68),BLACK,.17),g)
    parent(cube(title+' / typography field',(-2.64,y-.06,3.43),(6.18,.09,5.38),GRAPHITE,.15),g)
    for i in range(13):
        parent(cube(title+' / signature grille',(.83+i*.315,y-.095,3.43),(.135,.28,5.12),BASE,.045),g)
    mark={'VECTOR':'GPU','COMPANION':'AGT','SOUND':'DSP','ORIGIN':'FPGA','BATT':'PWR'}[title]
    face_text(g,title+' / capability',mark,-5.22,4.10,1.44,y=sy,bold=True)
    face_text(g,title+' / identity',title+' / '+code,-5.20,5.25,.30,y=sy,bold=True)
    face_text(g,title+' / architecture',role,-5.20,2.93,.21,MOD,y=sy)
    face_text(g,title+' / specification',feature,-5.20,2.52,.265,y=sy)
    face_text(g,title+' / detail',detail,-5.20,2.10,.19,MOD,y=sy)
    face_text(g,title+' / family','ARTISAN',-5.20,.89,.20,y=sy)
    face_text(g,title+' / platform','ASX',-.46,.89,.27,y=sy,align='RIGHT',bold=True)
    stroke(g,title+' / ASX rule',[(-1.54,sy,.64),(-.45,sy,.64)],width=.014)
    parent(cube('activity_led_'+title.lower(),(-5.02,sy-.025,5.80),(.38,.05,.12),AMBER,.03),g)
    case_fasteners(g,w/2-.52,sy,.55)

def capability_body(title,bodymat,crownmat):
    g=group('cartridge_'+title.lower());cartridge_shoe(g,title=='BATT')
    body=parent(cube(title+' / body',(0,0,3.30),(12.20,2.12,6.20),bodymat,.32),g)
    parent(cube(title+' / crown',(0,0,6.27),(12.33,2.23,.58),crownmat,.23),g)
    # A distinct, keyed contact bank communicates the ASX output/control extension.
    contacts=next(ob for ob in g.children if ob.name.endswith('_connector_contacts'))
    parent(cube(title+' / ASX output tongue',(0,.59,-.39),(4.35,.13,.94),PCB,.025),contacts)
    for i in range(34):
        for side in (-1,1):
            parent(cube(title+' / ASX contact',(-1.96+i*.119,.59+side*.075,-.48),(.062,.014,.49),GOLD,.007),contacts)
    return g,body

def make_vector():
    g,body=capability_body('VECTOR',BASE,RED_CAP)
    family_front(g,'VECTOR','AT-V01','GRAPHICS PROCESSING ENGINE','24 GB / DEDICATED MEMORY','PCIe x16 / ASX DISPLAY ROUTING',12.2,2.12,6.4)
    side_thruster(g,body,'VECTOR',12.2,2.12,6.4)
    right,_=side_panel(g,'vector_side_right',1,12.2,2.12,6.4)
    face_text(right,'Vector / port route','ASX',0,4.85,.48,y=-.09,align='CENTER',bold=True)
    face_text(right,'Vector / output caption','OUTPUT',0,4.18,.20,y=-.09,align='CENTER')
    arrow(right,'Vector / routing arrow',0,3.42,.72,angle=math.pi)
    face_text(right,'Vector / output detail','VIA ALTO',0,2.60,.20,y=-.09,align='CENTER')
    rear_identity(g,'VECTOR','AT-V01 / ASX',12.2,2.12,6.4)
    return g

def make_sound():
    g,body=capability_body('SOUND',CREAM,RED_CAP)
    family_front(g,'SOUND','AT-S01','LOCAL SIGNAL PROCESSOR','32 CH / 32-BIT FLOAT','4 GB SAMPLE MEMORY / DSP',12.2,2.12,6.4)
    side_thruster(g,body,'SOUND',12.2,2.12,6.4)
    sy=-1.215
    parent(cube('Sound / VU bezel',(-2.88,sy-.005,1.49),(4.66,.10,.63),BLACK,.09),g)
    parent(cube('Sound / VU glass',(-2.88,sy-.061,1.49),(4.44,.025,.45),GLASS,.06),g)
    for row in range(2):
        face_text(g,'Sound / meter channel','L' if row==0 else 'R',-4.97,1.62-row*.25,.13,y=sy-.079)
        for i in range(20):
            parent(cube('sound_vu_'+str(row)+'_'+str(i),(-4.57+i*.191,sy-.086,1.61-row*.24),(.12,.012,.115),AMBER if i<(14 if row==0 else 11) else AMBER_DIM,.013),g)
    right,_=side_panel(g,'sound_side_right',1,12.2,2.12,6.4)
    face_text(right,'Sound / route','DSP',0,4.86,.44,y=-.09,align='CENTER',bold=True)
    face_text(right,'Sound / route sub','LOCAL',0,4.23,.21,y=-.09,align='CENTER')
    face_text(right,'Sound / route detail','ASX / 01',0,1.1,.18,y=-.09,align='CENTER')
    rear_identity(g,'SOUND','AT-S01 / ASX',12.2,2.12,6.4)
    return g

def make_companion():
    g,body=capability_body('COMPANION',BASE,CREAM)
    family_front(g,'COMPANION','AT-C01','PERSONAL AGENT SYSTEM','1 TB / PRIVATE MEMORY','RUNTIME / TOOLS / ENCRYPTED VAULT',12.2,2.12,6.4)
    side_thruster(g,body,'COMPANION',12.2,2.12,6.4)
    right,_=side_panel(g,'companion_side_right',1,12.2,2.12,6.4)
    face_text(right,'Companion / identity','AGT',0,4.85,.44,y=-.09,align='CENTER',bold=True)
    face_text(right,'Companion / host','ASX HOST',0,4.16,.20,y=-.09,align='CENTER')
    arrow(right,'Companion / compute path',0,3.42,.72,angle=math.pi)
    face_text(right,'Companion / vector pair','+ VECTOR',0,2.60,.19,y=-.09,align='CENTER')
    face_text(right,'Companion / local caption','LOCAL',0,2.11,.23,y=-.09,align='CENTER',bold=True)
    rear=rear_identity(g,'COMPANION','AT-C01 / ASX',12.2,2.12,6.4)
    face_text(rear,'Companion / continuity','IDENTITY / MEMORY / CONTINUITY',0,3.55,.25,y=-.03,align='CENTER')
    face_text(rear,'Companion / hold','PAUSE / INSPECT / RESUME',0,2.93,.22,ORANGE,y=-.03,align='CENTER')
    return g

def make_origin():
    g,body=capability_body('ORIGIN',CREAM,ORANGE)
    family_front(g,'ORIGIN','AT-O01','COMPLETE MSX HARDWARE','Z80 / R800 / VDP','PSG / FM / SCC / FOUR PROFILES',12.2,2.12,6.4)
    side_thruster(g,body,'ORIGIN',12.2,2.12,6.4)
    right,panel=side_panel(g,'origin_side_right',1,12.2,2.12,6.4)
    square_button(right,'origin_mode_button',0,5.21,ORANGE,.90,'MODE')
    for i,label in enumerate(('MSX','MSX2','MSX2+','Turbo R')):
        zz=3.98-i*.64
        parent(cube('origin_led_'+str(i),(-.69,-.098,zz),(.15,.04,.12),AMBER if i==0 else AMBER_DIM,.023),right)
        face_text(right,'Origin / engine',label,-.48,zz,.175,y=-.098,bold=True)
    bpy.context.view_layer.update()
    for target in (body,panel):
        cut(target,cube('Origin / SD tool',right.matrix_world@Vector((.71,.10,2.77)),(.23,.80,2.64),None,.05,rot=(0,0,math.pi/2)))
    parent(cube('Origin / SD throat',(.71,-.082,2.77),(.23,.16,2.64),BLACK,.04),right)
    face_text(right,'Origin / SD label','SD',.70,1.12,.19,y=-.098,align='CENTER')
    rear=rear_identity(g,'ORIGIN','AT-O01 / ASX',12.2,2.12,6.4)
    face_text(rear,'Origin / system','MSX / MSX2 / MSX2+ / Turbo R',0,3.40,.28,y=-.03,align='CENTER')
    face_text(rear,'Origin / transport','PAUSE / STATE REWIND / ACCELERATE',0,2.81,.20,ORANGE,y=-.03,align='CENTER')
    return g

def make_batt():
    g,body=capability_body('BATT',RED,GRAPHITE)
    family_front(g,'BATT','AT-B01','MANAGED PORTABLE POWER','80 Wh / CAPACITY TARGET','CELL BALANCING / POWER CONTROL',12.2,2.12,6.4)
    side_thruster(g,body,'BATT',12.2,2.12,6.4)
    right,_=side_panel(g,'batt_side_right',1,12.2,2.12,6.4)
    square_button(right,'batt_check_button',0,4.91,MOD,.90,'CHECK')
    for i in range(5):
        zz=1.35+i*.49
        parent(cube('batt_led_'+str(i),(-.3,-.11,zz),(.60,.04,.17),AMBER_DIM,.03),right)
        face_text(right,'Batt / percent',str((i+1)*20),.25,zz,.18,y=-.10)
    rear_identity(g,'BATT','AT-B01 / ASX',12.2,2.12,6.4)
    return g

_original_world=make_world
def make_world():
    g=_original_world()
    for ob in list(g.children_recursive):
        if ob.type=='FONT':
            if ob.name=='World / title':ob.data.body='SSD';ob.data.size=.83
            elif ob.name=='World / role':ob.data.body='WORLD / NVMe'
            elif ob.name=='World / family':ob.data.body='AT-W01 / ASX'
    return g

def make_audio():
    g=group('cartridge_audio')
    parent(cube('Audio / mini carrier',(0,0,0),(7.58,5.37,.82),BASE,.18),g)
    parent(cube('Audio / ivory grip',(3.06,0,0),(1.50,5.43,1.22),CREAM,.18),g)
    for i in range(12):parent(cube('Audio / signature rib',(-.1+i*.19,0,.48),(.085,4.55,.12),BASE,.025),g)
    parent(text('Audio / capability','MIDI',(-3.37,1.26,.43),.68,WHITE,bold=True),g)
    parent(text('Audio / title','AUDIO PROFESSIONAL',(-3.35,.34,.43),.18,WHITE),g)
    parent(text('Audio / specification','IN / OUT / ISOLATED INPUT',(-3.35,-.31,.43),.16,MOD),g)
    parent(text('Audio / model','AT-A01 / ASX MINI',(-3.35,-1.84,.43),.18,WHITE),g)
    end=group('Audio / exposed I O');parent(end,g);end.location.x=3.8;end.rotation_euler.z=math.pi/2
    for u,label in ((-1.25,'MIDI IN'),(1.25,'MIDI OUT')):
        round_socket(end,'Audio / '+label,u,0,.27,y=-.03)
        parent(text('Audio / silk',label,(2.96,u,.636),.20,INK,rotation=(0,0,math.pi/2),align='CENTER'),g)
    parent(cube('activity_led_audio',(3.05,2.39,.635),(.30,.10,.025),AMBER,.024),g)
    parent(cube('Audio / keyed board',(-3.94,0,0),(.56,2.12,.13),PCB,.03),g)
    for i in range(22):parent(cube('Audio / contact',(-4.12,-.95+i*.09,.085),(.30,.052,.014),GOLD,.004),g)
    return g

def make_mix():
    g=group('cartridge_mix')
    pts=[(-13.3,-8.1),(13.3,-8.1),(14.0,-7.4),(14.0,7.4),(13.3,8.1),(-13.3,8.1),(-14,7.4),(-14,-7.4)]
    parent(prism('MIX / vermilion wedge',pts,.25,lambda x,y:2.2+.095*y,RED,.24),g)
    parent(cube('MIX / base',(0,0,.28),(27.3,15.5,.4),BASE,.3),g)
    deck=group('MIX / control deck');parent(deck,g);deck.rotation_euler.x=math.atan(.095);deck.location.z=2.29
    parent(cube('MIX / charcoal inset',(0,-.15,0),(24.5,13.8,.17),GRAPHITE,.24),deck)
    parent(text('MIX / brand','ARTISAN',(-11.5,-6.07,.104),.43,WHITE,bold=True),deck)
    parent(text('MIX / identity','MIX / AT-M01 ASX',(11.3,-6.07,.104),.39,WHITE,align='RIGHT',bold=True),deck)
    for i in range(6):
        x=-9.94+i*3.15
        parent(text('MIX / channel',f'0{i+1}',(x,4.91,.12),.34,WHITE,align='CENTER',bold=True),deck)
        for j in range(12):
            parent(cube('MIX / meter',(x-.40+j*.073,3.97,.14),(.042,.45,.026),AMBER if j<(7+i%4) else AMBER_DIM,.008),deck)
        parent(cube('MIX / fader slot',(x,-.73,.12),(.19,6.3,.04),BLACK,.065),deck)
        for j in range(9):
            parent(cube('MIX / fader scale',(x+.44,-3.50+j*.69,.13),(.21 if j%2 else .32,.018,.005),WHITE,0),deck)
        f=parent(cube('MIX / ivory fader',(x,-1.30+(i%3)*.84,.34),(1.57,.80,.43),CREAM,.15),deck)
        parent(cube('MIX / fader index',(x,f.location.y,.56),(1.08,.055,.015),INK,.01),deck)
        for dy,label,mat in ((2.74,'M',MOD),(1.91,'S',ORANGE)):
            parent(cube('MIX / '+label,(x,dy,.25),(.68,.63,.27),mat,.11),deck)
            parent(text('MIX / key silk',label,(x,dy,.393),.20,INK,align='CENTER',bold=True),deck)
    parent(cube('MIX / master well',(9.04,-.5,.12),(3.16,8.5,.05),BLACK,.20),deck)
    parent(cube('MIX / master fader',(9.04,-.30,.33),(2.2,.92,.42),CREAM,.16),deck)
    parent(text('MIX / master silk','MASTER',(9.04,4.88,.14),.30,WHITE,align='CENTER',bold=True),deck)
    parent(text('MIX / direct monitor','DIRECT MONITOR',(9.04,-5.13,.13),.18,WHITE,align='CENTER'),deck)
    rear=group('MIX / rear connections');parent(rear,g);rear.location=(0,8.04,1.61);rear.rotation_euler.z=math.pi
    parent(cube('MIX / rear bank',(0,.015,0),(25.8,.16,2.18),GRAPHITE,.15),rear)
    for i,x in enumerate((-10.8,-8.05,-5.30,-2.55)):
        face_ring(rear,'MIX / combo flange',x,0,.76,.62,-.12,.13)
        parent(cyl('MIX / combo insert',(x,-.14,0),.62,.12,BLACK,32,rot=(math.pi/2,0,0)),rear)
        round_socket(rear,'MIX / instrument',x,0,.22,y=-.24)
        for pin,(dx,dz) in enumerate(((-.34,.30),(.34,.30),(0,-.43))):
            round_socket(rear,f'MIX / XLR contact {i+1}.{pin+1}',x+dx,dz,.075,y=-.235)
        face_text(rear,'MIX / input label',f'MIC / LINE {i+1}',x,.95,.17,y=-.13,align='CENTER')
    for x,label in ((.0,'OUT L'),(1.8,'OUT R'),(3.6,'AUX 1'),(5.4,'AUX 2')):
        round_socket(rear,'MIX / '+label,x,0,.32,y=-.12,label=label,label_z=.91)
    midi_socket(rear,'MIX / MIDI IN',7.6,0,'MIDI IN')
    usb_c_socket(rear,'MIX / USB',10.3,0,y=-.12,label='USB-C',label_z=.90)
    face_text(rear,'MIX / USB data','USB 3 / HOST',10.3,-.73,.16,y=-.13,align='CENTER')
    phones=group('MIX / headphones');parent(phones,g);phones.location=(10,-8.06,.85)
    round_socket(phones,'MIX / phones',0,0,.32,y=-.03,label='PHONES',label_z=.5)
    return g

CARTRIDGE_BUILDERS={'vector':make_vector,'companion':make_companion,'sound':make_sound,'origin':make_origin,'batt':make_batt,'world':make_world,'audio':make_audio,'mix':make_mix}
CONTROL_PREFIXES=CONTROL_PREFIXES+('sound_vu_','route_led_')
