// SPDX-License-Identifier: AGPL-3.0-or-later
package preservation;

/** Selected US rebound parent/helpers and BP10 profile0 particles from pinned
 * 84B88.c,rebound_stone.h,7E4BC.c. Original arrays, source fixed-point probes,
 * asymmetric reflections, signed angles, line fades and radial children are
 * ported. World-camera/next-tick factories, shared256 budget/current overlap
 * base8 damage and isolated selected RNG are explicit local integration.
 */
public final class ReboundWorld {
    static final int ONE=65536,STONE=20,PARTICLES=5,PRIORITY=150;
    interface RandomSource {int next();}
    interface Environment {ReboundTerrain.Collision query(int x,int y);int hit(Actor actor,int x,int y,int halfWidth,int halfHeight,int sequence,int damage);}
    static final class Line {int x0,y0,x1,y1,gray=255,timer=20,drawMode=51;}
    static final class Tile {int x,y,vx,vy,gray=127,width=2,height=2,primitiveType=17,drawMode=51;}
    static final class Actor {
        final int entityId,slot,serial,sequence;final Object[] targets=new Object[256];int x,y,face,step,angle,segment,marker,embeddedDelay,life=64,hitboxState,hitCount,targetCount,damageRoll,params,flags,unsupportedQueries,queryCalls;
        boolean active=true,allocated;String destroyedReason="";Line[] lines=new Line[0];Tile[] tiles=new Tile[0];
        Actor(int id,int index,int serial,int sequence,int x,int y,int face){entityId=id;slot=index+32;this.serial=serial;this.sequence=sequence;this.x=x;this.y=y;this.face=face;}
        boolean hasHit(Object target){for(int i=0;i<targetCount;i++)if(targets[i]==target)return true;return false;}boolean rememberHit(Object target){if(target==null||hasHit(target)||targetCount==targets.length)return false;targets[targetCount++]=target;return true;}
    }
    static final class Job {final int x,y,face,sequence;int next,remaining=3,param;Job(Actor a,int frame){x=a.x;y=a.y;face=a.face;sequence=a.sequence;next=frame+1;}}
    private final DaggerWorld.SharedPool pool;private final RandomSource randomSource;private final Actor[] actors=new Actor[32];private final Job[] jobs=new Job[16];
    private int[] fields,quarter,factories,profile;private int serial,frameCounter,randomState=1,randomCalls,allocationFailures,factoryFailures,particleBirths,bounces,soundEvents,peakPrimitiveUsage;
    public ReboundWorld(){this(new DaggerWorld.SharedPool(),null);}ReboundWorld(DaggerWorld.SharedPool shared,RandomSource random){if(shared==null)throw new IllegalArgumentException("Missing pool");pool=shared;randomSource=random;}
    public boolean register(int[] f,int[] q,int[] bp,int[] p){
        if(f==null||f.length!=13||q==null||q.length!=1025||bp==null||bp.length!=12||p==null||p.length!=10)return false;
        int[] ef={8,2,4128,3,32,4,93,24,2,1,0,0,0},eb={20,1,193,0,34,0,5,3,193,3,0,0},ep={8,127,127,127,2,2,2,51,0,142606336};for(int i=0;i<13;i++)if(f[i]!=ef[i])return false;for(int i=0;i<12;i++)if(bp[i]!=eb[i])return false;for(int i=0;i<10;i++)if(p[i]!=ep[i])return false;if(q[0]!=0||q[1024]!=4096)return false;for(int i=0;i<1025;i++)if(q[i]<0||q[i]>4096||i>0&&q[i]<q[i-1])return false;
        fields=f.clone();quarter=q.clone();factories=bp.clone();profile=p.clone();reset();return true;
    }
    public boolean registered(){return fields!=null;}public int heartCost(){return fields==null?0:fields[1];}public int activeCount(){int n=0;for(int i=0;i<16;i++)if(actors[i]!=null&&actors[i].active&&actors[i].entityId==STONE)n++;return n;}public int refusal(){return !registered()?1:activeCount()>=fields[3]||pool.freeSlot()<0?2:0;}
    public boolean spawn(int x,int y,int face,int sequence){if(refusal()!=0||face!=1&&face!=-1||sequence<1||x<Integer.MIN_VALUE+64*ONE||x>Integer.MAX_VALUE-64*ONE||y<Integer.MIN_VALUE+64*ONE||y>Integer.MAX_VALUE-64*ONE)return false;create(STONE,pool.freeSlot(),x,y,face,sequence);return true;}
    private Actor create(int id,int index,int x,int y,int face,int seq){Actor a=new Actor(id,index,++serial,seq,x,y,face);pool.reserve(index,a);actors[index]=a;if(id==PARTICLES)particleBirths++;return a;}
    public void reset(){for(int i=0;i<32;i++){if(actors[i]!=null)pool.release(actors[i]);actors[i]=null;}for(int i=0;i<jobs.length;i++)jobs[i]=null;serial=frameCounter=randomCalls=allocationFailures=factoryFailures=particleBirths=bounces=soundEvents=peakPrimitiveUsage=0;randomState=1;}
    private int random(){randomCalls++;if(randomSource!=null)return randomSource.next();randomState=randomState*1103515245+12345;return(randomState>>>16)&32767;}
    private boolean allocate(Actor a,int cost){if(!pool.allocate(a,cost)){allocationFailures++;destroy(a,"primitive-allocation-failed");return false;}a.allocated=true;peakPrimitiveUsage=Math.max(peakPrimitiveUsage,pool.usedPrimitives());return true;}
    private void destroy(Actor a,String reason){a.active=a.allocated=false;a.hitboxState=0;a.destroyedReason=reason;for(Line l:a.lines)l.drawMode|=8;for(Tile t:a.tiles)t.drawMode|=8;pool.release(a);}
    private void queue(Actor a){for(int i=0;i<jobs.length;i++)if(jobs[i]==null){jobs[i]=new Job(a,frameCounter);return;}factoryFailures++;}
    void beginFrame(){frameCounter++;for(int i=0;i<jobs.length;i++){Job j=jobs[i];if(j==null||j.next>frameCounter)continue;int index=pool.freeReverse();if(index<0){factoryFailures++;jobs[i]=null;continue;}Actor a=create(PARTICLES,index,j.x,j.y,j.face,j.sequence);a.params=j.param++;if(--j.remaining==0)jobs[i]=null;else j.next=frameCounter+3;}}
    public void update(Environment e,int cameraX,int cameraY){beginFrame();for(int i=0;i<32;i++)updateSlot(i,e,cameraX,cameraY);}
    void updateSlot(int index,Environment e,int cameraX,int cameraY){if(!registered())return;Actor a=actors[index];if(a==null||!a.active)return;if(a.entityId==STONE)stone(a,e,cameraX,cameraY);else{particles(a);if(a.active&&((a.x>>16)-cameraX<-32||(a.x>>16)-cameraX>288||(a.y>>16)-cameraY<-16||(a.y>>16)-cameraY>256))destroy(a,"outside-source-camera");}}
    private int sin(int a){return CrossMath.sine(quarter,a);}private int cos(int a){return sin(a+1024);}
    static void bounce1(Actor a,int angle){a.angle=(short)(angle*2-a.angle);if(a.marker==0){a.segment=(short)(a.segment+1);a.marker=1;}}
    static void bounce2(Actor a,int angle){if(a.marker==0){a.angle=(short)(angle*2-a.angle);a.segment=(short)(a.segment+1);a.marker=1;}}
    private int probe(Actor a,Environment e,int x,int y){a.queryCalls++;ReboundTerrain.Collision c=e.query(x,y);if(c.unsupported)a.unsupportedQueries++;return c.effects;}
    private void stone(Actor a,Environment e,int cameraX,int cameraY){a.marker=0;int oldX=a.x>>16,oldY=a.y>>16;
        if(a.hitboxState!=0&&e.hit(a,oldX,oldY,4,4,a.sequence,fields[0])>0)a.hitCount++;
        switch(a.step){
        case 0:if(!allocate(a,16))return;a.lines=new Line[16];for(int i=0;i<16;i++){Line l=a.lines[i]=new Line();l.x0=l.x1=oldX;l.y0=l.y1=oldY;if(i>0)l.drawMode|=8;}a.flags=0x0C800000;a.angle=(short)((a.face<0?0x980:0xE80)+(random()&127)-64);a.life=64;a.damageRoll=random()%12;a.hitboxState=fields[8];if((probe(a,e,oldX,oldY)&1)!=0)a.embeddedDelay=4;a.step=1;soundEvents++;break;
        case 1:int dx=cos(a.angle)*16,dy=-sin(a.angle)*16,cx=a.x,cy=a.y;if(a.embeddedDelay==0){for(int i=0;i<6;i++){int eff=probe(a,e,cx>>16,(cy+dy)>>16)&0xF803;if((eff&1)!=0)vertical(a,eff&0xFF00,dy);eff=probe(a,e,(cx+dx)>>16,cy>>16)&0xF803;if((eff&1)!=0)horizontal(a,eff&0xFF00,dx);if(a.marker!=0)break;cx+=dx;cy+=dy;}}else a.embeddedDelay--;
            if(a.marker!=0){queue(a);bounces++;soundEvents++;}int sx=(a.x>>16)-cameraX,sy=(a.y>>16)-cameraY;if(sx<-64||sx>320||sy<-64||sy>320||a.segment==15)a.step=2;else{a.x+=((cos(a.angle)<<4)*0x400)>>8;a.y+=-((sin(a.angle)<<4)*0x400)>>8;}break;
        case 2:if(--a.life==0){destroy(a,"line-lifetime-ended");return;}if(a.life==32)a.hitboxState=0;for(Line l:a.lines)l.timer=0;break;
        default:throw new IllegalStateException("Invalid Rebound step");}
        for(int i=0;i<16;i++){Line l=a.lines[i];if(a.marker!=0&&i==a.segment){l.x0=oldX;l.y0=oldY;l.drawMode&=~8;}if(i==a.segment){l.x1=a.x>>16;l.y1=a.y>>16;}if((l.drawMode&8)==0){if(l.timer!=0)l.timer--;else{int fade=a.step==2?4:2;if(fade<l.gray)l.gray-=fade;}}}
    }
    private static void vertical(Actor a,int flags,int dy){if(dy>0){if(flags==0||(flags&0x800)!=0)bounce1(a,0x800);switch(flags){case 0x8000:bounce2(a,0x200);break;case 0x9000:bounce2(a,0x12E);break;case 0xA000:bounce2(a,0xA0);break;case 0xC000:bounce2(a,0x600);break;case 0xD000:bounce2(a,0x6D2);break;case 0xE000:bounce2(a,0x760);break;default:break;}}else if(dy<0){if(flags==0||(flags&0x8000)!=0)bounce1(a,0x800);switch(flags){case 0x800:bounce2(a,0xE00);break;case 0x1800:bounce2(a,0xED2);break;case 0x2800:bounce2(a,0xF60);break;case 0x4800:bounce2(a,0xA00);break;case 0x5800:bounce2(a,0x92E);break;case 0x6800:bounce2(a,0x8A0);break;default:break;}}}
    private static void horizontal(Actor a,int flags,int dx){if(dx>0){if(flags==0||(flags&0x4800)==0x4800||(flags&0xC000)==0xC000)bounce1(a,0x400);switch(flags){case 0x800:bounce2(a,0xE00);break;case 0x1800:bounce2(a,0xED2);break;case 0x2800:bounce2(a,0xF60);break;case 0x8000:bounce2(a,0x200);break;case 0x9000:bounce2(a,0x12E);break;case 0xA000:bounce2(a,0xA0);break;default:break;}}else if(dx<0){if(flags==0||(flags&0x4800)==0x800||(flags&0xC000)==0x8000)bounce1(a,0x400);switch(flags){case 0x4800:bounce2(a,0xA00);break;case 0x5800:bounce2(a,0x92E);break;case 0x6800:bounce2(a,0x8A0);break;case 0xC000:bounce2(a,0x600);break;case 0xD000:bounce2(a,0x6D2);break;case 0xE000:bounce2(a,0x760);break;default:break;}}}
    private void particles(Actor a){if(a.step==0){if(!allocate(a,9))return;a.flags=profile[9];a.tiles=new Tile[9];for(int i=0;i<9;i++){Tile t=a.tiles[i]=new Tile();t.x=(a.x>>16)*ONE;t.y=(a.y>>16)*ONE;if(i==8){t.width=t.height=0;t.primitiveType=1;t.x=t.y=0;t.gray=0;}else{int angle=random(),shift=(angle&1)+2;t.vx=cos(angle)<<shift;t.vy=-(sin(angle)<<shift);}}a.step=1;return;}for(int i=0;i<8;i++){Tile t=a.tiles[i];t.y+=t.vy;t.x+=t.vx;t.gray=(t.gray-6)&255;if(t.gray<8){destroy(a,"radial-fade-ended");return;}}}
    public String getState(){StringBuilder s=new StringBuilder("[");boolean comma=false;for(Actor a:actors)if(a!=null){if(comma)s.append(',');comma=true;s.append("{\"kind\":\"").append(a.entityId==STONE?"reboundStone":"reboundParticles").append("\",\"screenSpace\":false,\"entityId\":").append(a.entityId).append(",\"inventoryId\":").append(a.entityId==STONE?7:0).append(",\"slot\":").append(a.slot).append(",\"serial\":").append(a.serial).append(",\"sequence\":").append(a.sequence).append(",\"params\":").append(a.params).append(",\"x\":").append(a.x>>16).append(",\"y\":").append(a.y>>16).append(",\"xFixed\":").append(a.x).append(",\"yFixed\":").append(a.y).append(",\"face\":").append(a.face).append(",\"facingLeft\":").append(a.face<0?1:0).append(",\"step\":").append(a.step).append(",\"angle\":").append(a.angle).append(",\"segment\":").append(a.segment).append(",\"marker\":").append(a.marker).append(",\"embeddedDelay\":").append(a.embeddedDelay).append(",\"remaining\":").append(a.life).append(",\"damageRoll\":").append(a.damageRoll).append(",\"hitboxState\":").append(a.hitboxState).append(",\"hitboxWidth\":").append(a.entityId==STONE?4:0).append(",\"hitboxHeight\":").append(a.entityId==STONE?4:0).append(",\"hitTargetCount\":").append(a.targetCount).append(",\"hitCount\":").append(a.hitCount).append(",\"queryCalls\":").append(a.queryCalls).append(",\"unsupportedQueries\":").append(a.unsupportedQueries).append(",\"flags\":").append(a.flags).append(",\"active\":").append(a.active).append(",\"alive\":").append(a.active).append(",\"destroyedReason\":\"").append(a.destroyedReason).append("\",\"lines\":[");for(int i=0;i<a.lines.length;i++){if(i>0)s.append(',');Line l=a.lines[i];s.append("{\"index\":").append(i).append(",\"primitiveType\":2,\"x0\":").append(l.x0).append(",\"y0\":").append(l.y0).append(",\"x1\":").append(l.x1).append(",\"y1\":").append(l.y1).append(",\"gray\":").append(l.gray).append(",\"rgb\":[").append(l.gray).append(',').append(l.gray).append(',').append(l.gray).append(',').append(l.gray).append(',').append(l.gray).append(',').append(l.gray).append("],\"timer\":").append(l.timer).append(",\"drawMode\":").append(l.drawMode).append(",\"priority\":150,\"visible\":").append(a.active&&(l.drawMode&8)==0).append('}');}s.append("],\"tiles\":[");for(int i=0;i<a.tiles.length;i++){if(i>0)s.append(',');Tile t=a.tiles[i];s.append("{\"index\":").append(i).append(",\"x\":").append(t.x>>16).append(",\"y\":").append(t.y>>16).append(",\"xFixed\":").append(t.x).append(",\"yFixed\":").append(t.y).append(",\"vxFixed\":").append(t.vx).append(",\"vyFixed\":").append(t.vy).append(",\"width\":").append(t.width).append(",\"height\":").append(t.height).append(",\"primitiveType\":").append(t.primitiveType).append(",\"rgb\":[").append(t.gray).append(',').append(t.gray).append(',').append(t.gray).append("],\"drawMode\":").append(t.drawMode).append(",\"priority\":150,\"visible\":").append(a.active&&(t.drawMode&8)==0).append('}');}s.append("]}");}return s.append(']').toString();}
    public String getStatistics(){int n=0;for(Job j:jobs)if(j!=null)n++;return "{\"randomCalls\":"+randomCalls+",\"bounces\":"+bounces+",\"particleBirths\":"+particleBirths+",\"pendingFactories\":"+n+",\"allocationFailures\":"+allocationFailures+",\"factoryFailures\":"+factoryFailures+",\"soundEvents\":"+soundEvents+",\"primitiveUsage\":"+pool.usedPrimitives()+",\"primitiveCapacity\":"+pool.primitiveCapacity+",\"peakPrimitiveUsage\":"+peakPrimitiveUsage+"}";}
    Actor actor(int i){return actors[i];}int randomCalls(){return randomCalls;}int allocationFailures(){return allocationFailures;}void seedRandom(int seed){randomState=seed;randomCalls=0;}
}
