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

/** Selected US EntityPlayerBlinkWhite params5200/profile18. Normal Alucard
 * frames, eight GT4 fans, source phase/color ordering, gates, duplicate checks
 * and critical entity/primitive retries are retained. Copied scale flags0..3
 * use the literal rectangle-oriented signed helper; applied rotation and other
 * animation sets are explicitly outside this selected branch. Next-tick jobs,
 * final current player pose, world coordinates and shared256 primitives remain
 * local factory/player/compositor/GPU bridges. No RNG, combat or other blink
 * profile is invented. Reset/load/scenes/seams clear all volatile jobs/actors.
 */
public final class PlayerBlinkWorld {
    static final int ENTITY=11,PARAMS=0x5200,PRIORITY=150;
    static final class Player {
        int x,y,face=1,frame=1,animSet=1,status,step,palette,transition6C,transition6E;
        int drawFlags,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[12];int palette=351,drawMode=8;
    }
    static final class Actor {
        final int slot,serial,sequence,params=PARAMS;final Primitive[] primitives=new Primitive[8];
        int x,y,face,frame,sprite,step,brightness,holdRemaining,phase,phaseStep,scaleX,scaleY,pivotX,pivotY,rotate,drawFlags,flags;
        boolean active=true,allocated;String destroyedReason="";
        Actor(int slot,int serial,int seq,int x,int y,int face){this.slot=slot;this.serial=serial;sequence=seq;this.x=x;this.y=y;this.face=face;for(int i=0;i<8;i++)primitives[i]=new Primitive();}
    }
    private static final class Job {final int sequence,x,y,face;Job(int seq,int x,int y,int face){sequence=seq;this.x=x;this.y=y;this.face=face;}}
    private final DaggerWorld.SharedPool pool;private final Actor[] actors=new Actor[32];private final Job[] jobs=new Job[128];
    private final int[] colorAngles=new int[8];private int[] profile,quarter,blueprint;private Frame[] frames;
    private int jobCount,serial,requests,births,factoryRetries,factoryFailures,duplicateSuppressed,allocationRetries,sourceGates,unsupportedPose,afterimageSuppressionRequests,peakPrimitiveUsage;
    public PlayerBlinkWorld(){this(new DaggerWorld.SharedPool());}
    PlayerBlinkWorld(DaggerWorld.SharedPool pool){if(pool==null)throw new IllegalArgumentException("Missing shared pool");this.pool=pool;}
    public boolean register(int[] values,int[] sine,int[] bp,int[] frameRecords){
        if(values==null||values.length!=10||sine==null||sine.length!=1025||bp==null||bp.length!=6||frameRecords==null||frameRecords.length!=263*6)return false;
        int[] expected={0,0,0,351,512,256,1024,0x7003,49,512},factory={11,1,65,0,0,0};for(int i=0;i<10;i++)if(values[i]!=expected[i])return false;for(int i=0;i<6;i++)if(bp[i]!=factory[i])return false;
        if(sine[0]!=0||sine[1024]!=4096)return false;for(int i=0;i<1025;i++)if(sine[i]<0||sine[i]>4096||i>0&&sine[i]<sine[i-1])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();quarter=sine.clone();blueprint=bp.clone();frames=proposed;reset();return true;
    }
    public boolean registered(){return profile!=null;}
    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<jobCount;i++)jobs[i]=null;for(int i=0;i<8;i++)colorAngles[i]=0;jobCount=serial=requests=births=factoryRetries=factoryFailures=duplicateSuppressed=allocationRetries=sourceGates=unsupportedPose=afterimageSuppressionRequests=peakPrimitiveUsage=0;}
    /** BP44 origin0 retains parent birth fractions; callback high halves follow PLAYER. */
    public boolean request(int sequence,int x,int y,int face){if(!registered()||sequence<1||face!=1&&face!=-1)return false;requests++;if(jobCount==jobs.length){factoryFailures++;return false;}jobs[jobCount++]=new Job(sequence,x,y,face);return true;}
    /** Critical BP44 retries the next callback when the reverse entity range is full. */
    public void beginFrame(){if(!registered())return;int kept=0;for(int i=0;i<jobCount;i++){Job j=jobs[i];int slot=pool.freeReverse();if(slot<0){factoryRetries++;jobs[kept++]=j;continue;}Actor a=new Actor(slot+32,++serial,j.sequence,j.x,j.y,j.face);pool.reserve(slot,a);actors[slot]=a;births++;}for(int i=kept;i<jobCount;i++)jobs[i]=null;jobCount=kept;}
    private int sin(int angle){return CrossMath.sine(quarter,angle);}private int cos(int angle){return sin(angle+1024);}
    private static int high(int fixed,int value){return((short)value<<16)|(fixed&65535);}
    private static boolean suppressed(Player p){if((p.status&(2|0x1000000))!=0){p.transition6C=p.transition6E=0;return true;}if(p.transition6C!=0){p.transition6E=0;return true;}if(p.transition6E!=0){p.transition6C=0;return true;}if(p.step==34&&p.palette==0x810D||p.animSet==0||(p.frame&0x7FFF)==0){p.transition6C=p.transition6E=0;return true;}return false;}
    private void destroy(Actor a,String reason){a.active=a.allocated=false;a.destroyedReason=reason;for(Primitive q:a.primitives)q.drawMode|=8;pool.release(a);}
    private boolean duplicate(Actor self){for(Actor other:actors)if(other!=null&&other!=self&&other.active&&other.params==self.params)return true;return false;}
    public void update(Player p,int primaryAguneaCount){for(int i=0;i<32;i++)updateSlot(i,p,primaryAguneaCount);}
    void updateSlot(int index,Player p,int primaryAguneaCount){if(!registered())return;Actor a=actors[index];if(a==null||!a.active)return;
        if(suppressed(p)){sourceGates++;destroy(a,"source-player-gate");return;}
        int frame=p.frame&0x7FFF;if(p.animSet!=1||(p.drawFlags&4)!=0||frame>=frames.length||frames[frame]==null){unsupportedPose++;destroy(a,"outside-selected-normal-unrotated-pose");return;}
        Frame f=frames[frame];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;a.drawFlags=p.drawFlags&255;a.scaleX=(short)p.scaleX;a.scaleY=(short)p.scaleY;a.pivotX=(short)p.pivotX;a.pivotY=(short)p.pivotY;
        switch(a.step){
        case 0:
            if(duplicate(a)){duplicateSuppressed++;destroy(a,"source-duplicate-id-params");return;}
            // Unlike outlines, original US failure leaves step0/entity alive for retry.
            if(!pool.allocate(a,8)){allocationRetries++;return;}a.allocated=true;peakPrimitiveUsage=Math.max(peakPrimitiveUsage,pool.usedPrimitives());a.flags=0x04870000;
            for(int i=0;i<8;i++){colorAngles[i]=i<<9;Primitive q=a.primitives[i];q.palette=profile[3];q.drawMode=profile[8]+0x304;for(int c=0;c<12;c++)q.rgb[c]=128;}
            a.phaseStep=profile[9];a.brightness=0;a.step=1;break;
        case 1:a.brightness=(short)(a.brightness+10);if(a.brightness>256){a.brightness=256;a.holdRemaining=profile[7];a.step=2;}break;
        case 2:a.holdRemaining=8;if(primaryAguneaCount==0){a.holdRemaining=20;a.step=3;}if(--a.holdRemaining==0)a.step++;break;
        case 3:a.brightness=(short)(a.brightness-10);if(a.brightness<0){destroy(a,"source-brightness-ended");return;}break;
        default:throw new IllegalStateException("Outside selected blink profile18 states");}
        a.phase=(short)(a.phase+a.phaseStep);geometry(a,f);afterimageSuppressionRequests++;
    }
    private void geometry(Actor a,Frame f){int x=(short)((a.x>>16)+(a.face<0?-f.offsetX:f.offsetX)),y=(short)((a.y>>16)+f.offsetY),w=f.width,h=f.height,hw=w/2,hh=h/2;
        int centerX=(short)(x+(a.face<0?-hw:hw)+(((cos(a.phase)>>4)*3)>>12)),centerY=(short)(y+hh-(((sin(a.phase)>>4)*6)>>8));
        for(int i=0;i<8;i++){Primitive q=a.primitives[i];int ax=0,ay=0,bx=0,by=0;switch(i){case 0:bx=hw;break;case 1:ax=hw;bx=w;break;case 2:ax=bx=w;by=hh;break;case 3:ax=bx=w;ay=hh;by=h;break;case 4:ax=w;bx=hw;ay=by=h;break;case 5:ax=hw;ay=by=h;break;case 6:ay=h;by=hh;break;case 7:ay=hh;break;}
            q.quad[0]=(short)(x+(a.face<0?-ax:ax));q.quad[1]=(short)(y+ay);q.quad[2]=(short)(x+(a.face<0?-bx:bx));q.quad[3]=(short)(y+by);q.quad[4]=q.quad[6]=centerX;q.quad[5]=q.quad[7]=centerY;
            q.uv[0]=(4+ax)&255;q.uv[1]=(1+ay)&255;q.uv[2]=(4+bx)&255;q.uv[3]=(1+by)&255;q.uv[4]=q.uv[6]=(4+hw)&255;q.uv[5]=q.uv[7]=(1+hh)&255;
            scaleFan(a,q);
            for(int c=0;c<3;c++){int offset=profile[c==0?0:c==1?2:1],divisor=profile[c==0?4:c==1?6:5];q.rgb[c]=((((sin((short)colorAngles[(i+offset)%8])+4096)>>6)*a.brightness/divisor)&255);q.rgb[3+c]=((((sin((short)colorAngles[(i+offset+1)%8])+4096)>>6)*a.brightness/divisor)&255);q.rgb[6+c]=q.rgb[9+c]=0;}
            // Shared original phase table changes inside this loop, not as a snapshot.
            colorAngles[i]+=a.phaseStep;
        }
    }
    private static void scaleFan(Actor a,Primitive q){int px=(short)((a.x>>16)+(a.face<0?-a.pivotX:a.pivotX)),py=(short)((a.y>>16)+a.pivotY);if((a.drawFlags&1)!=0){q.quad[0]=q.quad[4]=PlayerOutlineWorld.scaleCoordinate(q.quad[0],px,a.scaleX);q.quad[2]=q.quad[6]=PlayerOutlineWorld.scaleCoordinate(q.quad[2],px,a.scaleX);}if((a.drawFlags&2)!=0){q.quad[1]=q.quad[3]=PlayerOutlineWorld.scaleCoordinate(q.quad[1],py,a.scaleY);q.quad[5]=q.quad[7]=PlayerOutlineWorld.scaleCoordinate(q.quad[5],py,a.scaleY);}}
    private static void append(StringBuilder s,int[] values){for(int i=0;i<values.length;i++){if(i>0)s.append(',');s.append(values[i]);}}
    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\":\"aguneaBlink\",\"screenSpace\":false,\"entityId\":11,\"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(",\"brightness\":").append(a.brightness).append(",\"holdRemaining\":").append(a.holdRemaining).append(",\"phase\":").append(a.phase).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("\",\"primitives\":[");for(int i=0;i<8;i++){if(i>0)s.append(',');Primitive q=a.primitives[i];s.append("{\"index\":").append(i).append(",\"quad\":[");append(s,q.quad);s.append("],\"uv\":[");append(s,q.uv);s.append("],\"rgb\":[");append(s,q.rgb);s.append("],\"palette\":").append(q.palette).append(",\"drawMode\":").append(q.drawMode).append(",\"tpage\":24,\"priority\":150,\"visible\":").append(a.active&&a.allocated&&(q.drawMode&8)==0).append('}');}s.append("]}");}return s.append(']').toString();}
    public String getStatistics(){return "{\"registered\":"+registered()+",\"requests\":"+requests+",\"pendingJobs\":"+jobCount+",\"births\":"+births+",\"factoryRetries\":"+factoryRetries+",\"factoryFailures\":"+factoryFailures+",\"duplicateSuppressed\":"+duplicateSuppressed+",\"allocationRetries\":"+allocationRetries+",\"sourceGates\":"+sourceGates+",\"unsupportedPose\":"+unsupportedPose+",\"afterimageSuppressionRequests\":"+afterimageSuppressionRequests+",\"randomCalls\":0,\"primitiveUsage\":"+pool.usedPrimitives()+",\"primitiveCapacity\":"+pool.primitiveCapacity+",\"peakPrimitiveUsage\":"+peakPrimitiveUsage+"}";}
    Actor actor(int index){return actors[index];}int colorAngle(int i){return colorAngles[i];}int births(){return births;}int allocationRetries(){return allocationRetries;}
}
