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

/** Selected US EntityPlayerOutline upperparams2 and BP61 three/period5 factory.
 * Raw profile/current Alucard frame geometry are registered as data. Source
 * gates, scalar signed16 helper, current-pose UVs, 8 hold/38 growth callbacks,
 * priorities and no-RNG behavior are retained. Selected next-tick jobs, shared
 * reverse32..63/default256 primitives and final-current-pose world coordinates
 * replace full factory/player/compositor/GPU phase. Only Agunea outlines are
 * ported; other outline profiles and BP44/52 blink remain unfinished.
 */
public final class PlayerOutlineWorld {
    static final int ONE=65536,ENTITY=31,PRIORITY=150,STATUS_MASK=0x01040087;
    static final class Player {
        int x,y,face=1,frame=1,animSet=1,status,step,palette,scaleX=256,scaleY=256,pivotX,pivotY,rotate;
    }
    static final class Frame {
        final int id,sprite,width,height,offsetX,offsetY;
        Frame(int[] records,int p){id=records[p];sprite=records[p+1];width=records[p+2];height=records[p+3];offsetX=records[p+4];offsetY=records[p+5];}
    }
    static final class Primitive {
        final int[] quad=new int[8],uv=new int[8],rgb=new int[3];
        int palette=259,drawMode=0x335;
    }
    static final class Actor {
        final int slot,serial,sequence,params;final Primitive primitive=new Primitive();
        int x,y,face,frame,sprite,step,holdRemaining,brightness,scaleX,scaleY,pivotX,pivotY,rotate,drawFlags=3,flags;
        boolean active=true,allocated;String destroyedReason="";
        Actor(int slot,int serial,int seq,int params,int x,int y,int face){this.slot=slot;this.serial=serial;sequence=seq;this.params=params;this.x=x;this.y=y;this.face=face;}
    }
    private static final class Job {final int sequence;int remaining=3,delay,index;Job(int seq){sequence=seq;}}
    private final DaggerWorld.SharedPool pool;private final Actor[] actors=new Actor[32];private final Job[] jobs=new Job[128];
    private int[] profile,blueprint;private Frame[] frames;
    private int jobCount,serial,factoryRequests,births,factoryFailures,allocationFailures,unsupportedFrames,afterimageSuppressionRequests,peakPrimitiveUsage;
    private boolean blinkImplemented;
    public PlayerOutlineWorld(){this(new DaggerWorld.SharedPool());}
    PlayerOutlineWorld(DaggerWorld.SharedPool pool){if(pool==null)throw new IllegalArgumentException("Missing shared pool");this.pool=pool;}
    public boolean register(int[] values,int[] bp,int[] frameRecords){
        if(values==null||values.length!=5||bp==null||bp.length!=6||frameRecords==null||frameRecords.length!=263*6)return false;
        int[] expected={63,31,255,259,49},factory={31,3,193,5,32,0};for(int i=0;i<5;i++)if(values[i]!=expected[i])return false;for(int i=0;i<6;i++)if(bp[i]!=factory[i])return false;
        Frame[] proposed=new Frame[264];for(int i=0;i<263;i++){Frame f=new Frame(frameRecords,i*6);if(f.id<1||f.id>263||proposed[f.id]!=null||f.sprite<0||f.sprite>=218||f.width<1||f.width>255||f.height<1||f.height>255||f.offsetX<-32768||f.offsetX>32767||f.offsetY<-32768||f.offsetY>32767)return false;proposed[f.id]=f;}
        profile=values.clone();blueprint=bp.clone();frames=proposed;reset();return true;
    }
    public boolean registered(){return profile!=null;}
    void setBlinkImplemented(boolean implemented){blinkImplemented=implemented;}
    public void reset(){for(int i=0;i<actors.length;i++){if(actors[i]!=null)pool.release(actors[i]);actors[i]=null;}for(int i=0;i<jobCount;i++)jobs[i]=null;jobCount=serial=factoryRequests=births=factoryFailures=allocationFailures=unsupportedFrames=afterimageSuppressionRequests=peakPrimitiveUsage=0;}
    /** BP61 has no parent-ID guard: selected jobs outlive released Agunea bolts. */
    public boolean request(int sequence){if(!registered()||sequence<1)return false;factoryRequests++;if(jobCount==jobs.length){factoryFailures++;return false;}jobs[jobCount++]=new Job(sequence);return true;}
    /** Called before accepting this frame's pulses: births are next tick, then5. */
    public void beginFrame(int playerX,int playerY,int face){if(!registered())return;int kept=0;for(int i=0;i<jobCount;i++){Job job=jobs[i];if(job.delay>0&&--job.delay>0){jobs[kept++]=job;continue;}int slot=pool.freeReverse();if(slot<0){factoryFailures++;continue;}Actor a=new Actor(slot+32,++serial,job.sequence,0x0200+job.index,playerX,playerY,face);pool.reserve(slot,a);actors[slot]=a;births++;job.index++;if(--job.remaining>0){job.delay=5;jobs[kept++]=job;}}for(int i=kept;i<jobCount;i++)jobs[i]=null;jobCount=kept;}
    private static boolean sourceSuppressed(Player p){return(p.status&STATUS_MASK)!=0||(p.frame&0x7FFF)==0||p.animSet==0||p.step==34&&p.palette==0x810D;}
    private static int high(int fixed,int value){return((short)value<<16)|(fixed&65535);}
    private void destroy(Actor a,String why){a.active=a.allocated=false;a.destroyedReason=why;a.primitive.drawMode|=8;pool.release(a);}
    /** Literal selected US scalar-helper signed16 intermediates and SRL8. */
    static int scaleCoordinate(int coordinate,int pivot,int scale){int delta=(short)(coordinate-pivot);int scaled=(short)((((short)scale)*delta+0x80000000)>>>8);return(short)(scaled+(short)pivot);}
    public void update(Player p){for(int i=0;i<32;i++)updateSlot(i,p);}
    void updateSlot(int index,Player p){if(!registered())return;Actor a=actors[index];if(a==null||!a.active)return;
        if(sourceSuppressed(p)){destroy(a,"source-player-gate");return;}
        int frame=p.frame&0x7FFF;Frame f=frame<frames.length?frames[frame]:null;if(f==null){unsupportedFrames++;destroy(a,"unregistered-player-frame");return;}
        a.x=high(a.x,p.x>>16);a.y=high(a.y,p.y>>16);a.face=p.face;a.frame=frame;a.sprite=f.sprite;a.rotate=(short)p.rotate; // No rotation flag is added by the source.
        switch(a.step){
        case 0:
            if(!pool.allocate(a,1)){allocationFailures++;destroy(a,"primitive-allocation-failed");return;}a.allocated=true;peakPrimitiveUsage=Math.max(peakPrimitiveUsage,pool.usedPrimitives());a.flags=0x04870000;a.brightness=192;a.holdRemaining=8;
            a.scaleX=(short)p.scaleX;a.scaleY=(short)p.scaleY;a.pivotX=(short)p.pivotX;a.pivotY=(short)p.pivotY;a.step=1;break;
        case 1:if(--a.holdRemaining==0)a.step=2;break;
        case 2:a.scaleX=(short)(a.scaleX+8);a.scaleY=(short)(a.scaleY+8);a.brightness=(short)(a.brightness-5);if(a.brightness<0){destroy(a,"outline-brightness-ended");return;}break;
        default:throw new IllegalStateException("Outside selected outline profile2 states");}
        geometry(a,f);afterimageSuppressionRequests++;
    }
    private void geometry(Actor a,Frame f){Primitive q=a.primitive;int originX=(short)(a.x>>16),originY=(short)(a.y>>16);
        int selfX=(short)(originX+(a.face<0?-f.offsetX:f.offsetX)),selfY=(short)(originY+f.offsetY);
        int left=(short)(a.face<0?selfX-f.width+1:selfX),right=(short)(a.face<0?selfX+1:selfX+f.width),top=selfY,bottom=(short)(selfY+f.height);
        int pivotX=(short)(originX+(a.face<0?-a.pivotX:a.pivotX)),pivotY=(short)(originY+a.pivotY);
        left=scaleCoordinate(left,pivotX,a.scaleX);right=scaleCoordinate(right,pivotX,a.scaleX);top=scaleCoordinate(top,pivotY,a.scaleY);bottom=scaleCoordinate(bottom,pivotY,a.scaleY);
        q.quad[0]=q.quad[4]=left;q.quad[2]=q.quad[6]=right;q.quad[1]=q.quad[3]=top;q.quad[5]=q.quad[7]=bottom;
        q.uv[0]=q.uv[4]=(a.face<0?4+f.width-1:4)&255;q.uv[2]=q.uv[6]=(a.face<0?3:4+f.width)&255;q.uv[1]=q.uv[3]=1;q.uv[5]=q.uv[7]=(1+f.height)&255;
        for(int i=0;i<3;i++)q.rgb[i]=(profile[i]*a.brightness/256)&255;q.palette=profile[3];q.drawMode=profile[4]+0x304;
    }
    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\":\"aguneaOutline\",\"screenSpace\":false,\"entityId\":31,\"subweaponId\":0,\"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(",\"frame\":").append(a.frame).append(",\"spriteIndex\":").append(a.sprite).append(",\"step\":").append(a.step).append(",\"holdRemaining\":").append(a.holdRemaining).append(",\"brightness\":").append(a.brightness).append(",\"scaleX\":").append(a.scaleX).append(",\"scaleY\":").append(a.scaleY).append(",\"pivotX\":").append(a.pivotX).append(",\"pivotY\":").append(a.pivotY).append(",\"rotate\":").append(a.rotate).append(",\"drawFlags\":").append(a.drawFlags).append(",\"flags\":").append(a.flags).append(",\"active\":").append(a.active).append(",\"alive\":").append(a.active).append(",\"destroyedReason\":\"").append(a.destroyedReason).append("\",\"primitive\":{\"index\":0,\"quad\":[");append(s,a.primitive.quad);s.append("],\"uv\":[");append(s,a.primitive.uv);s.append("],\"rgb\":[");append(s,a.primitive.rgb);s.append("],\"palette\":").append(a.primitive.palette).append(",\"drawMode\":").append(a.primitive.drawMode).append(",\"tpage\":24,\"priority\":150,\"visible\":").append(a.active&&a.allocated&&(a.primitive.drawMode&8)==0).append("}}");}return s.append(']').toString();}
    private static void append(StringBuilder s,int[] data){for(int i=0;i<data.length;i++){if(i>0)s.append(',');s.append(data[i]);}}
    public String getStatistics(){return "{\"registered\":"+registered()+",\"factoryRequests\":"+factoryRequests+",\"pendingJobs\":"+jobCount+",\"births\":"+births+",\"factoryFailures\":"+factoryFailures+",\"allocationFailures\":"+allocationFailures+",\"unsupportedFrames\":"+unsupportedFrames+",\"afterimageSuppressionRequests\":"+afterimageSuppressionRequests+",\"randomCalls\":0,\"primitiveUsage\":"+pool.usedPrimitives()+",\"primitiveCapacity\":"+pool.primitiveCapacity+",\"peakPrimitiveUsage\":"+peakPrimitiveUsage+",\"blinkPending\":"+(!blinkImplemented)+"}";}
    Actor actor(int index){return actors[index];}int allocationFailures(){return allocationFailures;}int births(){return births;}int factoryFailures(){return factoryFailures;}
}
