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

/**
 * Selected US EntitySubwpnKnife states from the pinned sotn-decomp reference
 * 0622fcfd37311fd21baf2938c6bd74cea63005ba, src/dra/84B88.c and subwpn_dagger.h.
 * Definition and exact quarter-sine data are registered from the disc. Source
 * slots32..47, chain3, flight probes, bounce/hit timers and primitive geometry
 * are preserved. One bounded two-primitive allocation replaces the shared PSX
 * allocator; factory/player ordering, terrain coverage and enemy overlap/damage
 * are explicit local integration proxies. Base attack excludes original stats,
 * equipment, resistance and RNG. Sound and rebound-stone hit effect are pending.
 * Projectiles are volatile and are cleared on scene/reset/load/room seams.
 */
public final class DaggerWorld {
    static final int ONE=65536,POOL=16,UNKNOWN=0x10000000;
    /** Shared local slot/budget proxy, separate from the original GPU allocator. */
    static final class SharedPool {
        // Indices0..15 are source slots32..47;16..31 extend to48..63 for
        // reverse-allocated Holy Water glass. Primary chains still scan16.
        final Object[] owners=new Object[32];final int[] costs=new int[32];
        int primitiveCapacity=256;
        int freeSlot(){for(int i=0;i<POOL;i++)if(owners[i]==null)return i;return -1;}
        int freeReverse(){for(int i=owners.length-1;i>=0;i--)if(owners[i]==null)return i;return -1;}
        void reserve(int index,Object owner){if(index<0||index>=owners.length||owners[index]!=null||owner==null)throw new IllegalArgumentException("Occupied local subweapon slot");owners[index]=owner;}
        boolean allocate(Object owner,int cost){int used=0,index=-1;for(int i=0;i<owners.length;i++){used+=costs[i];if(owners[i]==owner)index=i;}if(index<0||cost<1||used+cost>primitiveCapacity)return false;costs[index]=cost;return true;}
        void release(Object owner){for(int i=0;i<owners.length;i++)if(owners[i]==owner){owners[i]=null;costs[i]=0;return;}}
        int usedPrimitives(){int total=0;for(int cost:costs)total+=cost;return total;}
        void setPrimitiveCapacity(int capacity){if(capacity<0||capacity>256)throw new IllegalArgumentException("Invalid shared primitive budget");primitiveCapacity=capacity;}
    }
    interface Environment {
        int effectsAt(int x,int y);
        /** Authored pre-flight overlap feeds source hitFlags1/2. */
        int hit(int x,int y,int halfWidth,int halfHeight,int sequence,int damage);
    }
    static final class Dagger {
        final int slot,serial,sequence;
        int x,y,vx,vy,step,remaining,flightTimer,rotation,face,hitboxState,hitFlags,hitCount,unsupportedQueries;
        int gray=128,lineX0,lineY0,lineX1,lineY1;
        final int[] quad=new int[8];
        boolean crouching,active=true,visible,translucent,lineVisible,allocated;
        String destroyedReason="";
        Dagger(int slot,int serial,int sequence,int x,int y,int face,boolean crouch){this.slot=slot;this.serial=serial;this.sequence=sequence;this.x=x;this.y=y;this.face=face;crouching=crouch;}
    }
    private int[] fields,sineQuarter;
    private final SharedPool sharedPool;
    private final Dagger[] projectiles=new Dagger[POOL];
    private int serial,primitiveCapacity=32,allocationFailures;
    public DaggerWorld(){this(new SharedPool());}
    DaggerWorld(SharedPool pool){if(pool==null)throw new IllegalArgumentException("Missing shared pool");sharedPool=pool;}
    public boolean register(int[] definition,int[] quarter){
        if(definition==null||definition.length!=13||quarter==null||quarter.length!=1025)return false;
        for(int i=0;i<definition.length;i++)if(definition[i]<0||definition[i]>(i<3?65535:255))return false;
        if(definition[0]<1||definition[0]>9999||definition[1]!=1||definition[3]!=3||definition[6]!=93||definition[7]!=3||definition[8]!=2)return false;
        if(quarter[0]!=0||quarter[1024]!=4096)return false;
        for(int i=0;i<quarter.length;i++)if(quarter[i]<0||quarter[i]>4096||i>0&&quarter[i]<quarter[i-1])return false;
        fields=definition.clone();sineQuarter=quarter.clone();reset();return true;
    }
    public void reset(){for(int i=0;i<POOL;i++){if(projectiles[i]!=null)sharedPool.release(projectiles[i]);projectiles[i]=null;}serial=allocationFailures=0;}
    public boolean registered(){return fields!=null;}
    public int heartCost(){return fields==null?0:fields[1];}
    public int attack(){return fields==null?0:fields[0];}
    public int animation(){return fields==null?93:fields[6];}
    public int activeCount(){int count=0;for(Dagger dagger:projectiles)if(dagger!=null&&dagger.active)count++;return count;}
    /** Returns0 allowed,1 unregistered,2 chain limit/pool exhausted. */
    public int refusal(){return fields==null?1:activeCount()>=fields[3]||sharedPool.freeSlot()<0?2:0;}
    public boolean spawn(int xFixed,int originYFixed,int face,boolean crouching,int sequence){
        if(refusal()!=0||face!=1&&face!=-1||sequence<1||xFixed<Integer.MIN_VALUE+16*ONE||xFixed>Integer.MAX_VALUE-16*ONE||originYFixed<Integer.MIN_VALUE+16*ONE||originYFixed>Integer.MAX_VALUE-16*ONE)return false;
        int i=sharedPool.freeSlot();if(i<0)return false;Dagger dagger=new Dagger(32+i,++serial,sequence,xFixed,originYFixed,face,crouching);sharedPool.reserve(i,dagger);projectiles[i]=dagger;return true;
    }
    private int sin(int angle){int a=angle&4095;if(a<=1024)return sineQuarter[a];if(a<=2048)return sineQuarter[2048-a];if(a<=3072)return -sineQuarter[a-2048];return -sineQuarter[4096-a];}
    private int cos(int angle){return sin(angle+1024);}
    private void destroy(Dagger dagger,String reason){dagger.active=dagger.visible=dagger.lineVisible=dagger.allocated=false;dagger.hitboxState=0;dagger.destroyedReason=reason;sharedPool.release(dagger);}
    public void update(Environment environment,int cameraX,int cameraY){
        for(int i=0;i<POOL;i++)updateSlot(i,environment,cameraX,cameraY);
    }
    void updateSlot(int index,Environment environment,int cameraX,int cameraY){
        if(fields==null)return;Dagger dagger=projectiles[index];if(dagger==null||!dagger.active)return;
            switch(dagger.step){
            case 0:{
                int used=0;for(Dagger other:projectiles)if(other!=null&&other.active&&other.allocated)used+=2;
                if(used+2>primitiveCapacity||!sharedPool.allocate(dagger,2)){allocationFailures++;destroy(dagger,"primitive-allocation-failed");break;}
                dagger.allocated=true;dagger.hitboxState=fields[8];if(!dagger.crouching)dagger.y-=9*ONE;
                dagger.vx=dagger.face*8*ONE;dagger.step=1;break;
            }
            case 1:
                dagger.flightTimer++;
                if(dagger.hitFlags==0&&dagger.hitboxState!=0){int feedback=environment.hit((dagger.x>>16)+dagger.face*4,dagger.y>>16,4,2,dagger.sequence,fields[0]);if(feedback==1||feedback==2){dagger.hitFlags=feedback;dagger.hitCount++;}}
                if(dagger.hitFlags==1){dagger.remaining=4;dagger.step=3;dagger.hitboxState=0;break;}
                for(int micro=0;micro<8;micro++){
                    dagger.x+=dagger.face*ONE;
                    int effects=environment.effectsAt((dagger.x>>16)+dagger.face*8,dagger.y>>16);
                    if((effects&UNKNOWN)!=0)dagger.unsupportedQueries++;
                    if(dagger.hitFlags==2||(effects&3)!=0){dagger.remaining=64;dagger.vx=-(dagger.vx>>3);dagger.vy=-5*ONE/2;dagger.step=2;dagger.hitboxState=0;break;}
                }
                // Source collision returns before changing either primitive.
                if(dagger.step!=1)break;
                flyingQuad(dagger);break;
            case 2:
                if(--dagger.remaining==0){destroy(dagger,"bounce-expired");break;}
                if(dagger.remaining==32){dagger.translucent=true;dagger.gray=96;}
                dagger.x+=dagger.vx;dagger.y+=dagger.vy;dagger.vy+=ONE/8;
                dagger.rotation+=dagger.face==1?-128:128;
                bouncedQuad(dagger);if(dagger.remaining<33)dagger.gray-=2;
                dagger.visible=true;dagger.lineVisible=false;break;
            case 3:if(--dagger.remaining==0)destroy(dagger,"enemy-hit-expired");break;
            default:throw new IllegalStateException("Invalid dagger step");
            }
            // Original DRA entity dispatcher runs this inclusive-bound check
            // after the callback. World minus camera replaces camera-local XY.
            if(dagger.active){int sx=(dagger.x>>16)-cameraX,sy=(dagger.y>>16)-cameraY;if(sx>288||sx<-32||sy>256||sy<-16)destroy(dagger,"outside-source-camera-bounds");}
    }
    private static void flyingQuad(Dagger dagger){
        int x=dagger.x>>16,y=dagger.y>>16,dx=dagger.face*12;
        dagger.quad[0]=x-dx;dagger.quad[1]=y-4;dagger.quad[2]=x+dx;dagger.quad[3]=y-4;
        dagger.quad[4]=x-dx;dagger.quad[5]=y+4;dagger.quad[6]=x+dx;dagger.quad[7]=y+4;
        dagger.visible=dagger.lineVisible=true;dagger.lineX0=x-dagger.face*8;dagger.lineY0=dagger.lineY1=y-1;dagger.lineX1=x-dagger.face*12*(dagger.flightTimer/2);
    }
    private void bouncedQuad(Dagger dagger){
        int[] angles=dagger.face==1?new int[]{2048-210,210,2048+210,-210}:new int[]{210,2048-210,-210,2048+210};
        int x=dagger.x>>16,y=dagger.y>>16;
        for(int i=0;i<4;i++){int angle=angles[i]+dagger.rotation;int modX=(cos(angle)*0xCA0)>>20,modY=(-sin(angle)*0xCA0)>>20;dagger.quad[i*2]=x+modX;dagger.quad[i*2+1]=y-modY;}
    }
    public String getState(){
        StringBuilder out=new StringBuilder("[");boolean comma=false;
        for(Dagger d:projectiles)if(d!=null){if(comma)out.append(',');comma=true;
            out.append("{\"kind\":\"dagger\",\"slot\":").append(d.slot).append(",\"serial\":").append(d.serial).append(",\"sequence\":").append(d.sequence).append(",\"entityId\":4,\"inventoryId\":1,\"x\":").append(d.x>>16).append(",\"y\":").append(d.y>>16).append(",\"xFixed\":").append(d.x).append(",\"yFixed\":").append(d.y).append(",\"vxFixed\":").append(d.vx).append(",\"vyFixed\":").append(d.vy).append(",\"step\":").append(d.step).append(",\"remaining\":").append(d.remaining).append(",\"flightTimer\":").append(d.flightTimer).append(",\"rotation\":").append(d.rotation).append(",\"face\":").append(d.face).append(",\"facingLeft\":").append(d.face==1?0:1);
            out.append(",\"frame\":0,\"hitboxState\":").append(d.hitboxState).append(",\"hitboxWidth\":4,\"hitboxHeight\":2,\"hitboxOffX\":4,\"hitboxOffY\":0,\"hitFlags\":").append(d.hitFlags).append(",\"hitCount\":").append(d.hitCount).append(",\"baseAttack\":").append(fields[0]).append(",\"attackElement\":").append(fields[2]).append(",\"quad\":[");
            for(int i=0;i<8;i++){if(i!=0)out.append(',');out.append(d.quad[i]);}
            out.append("],\"visible\":").append(d.visible).append(",\"gray\":").append(d.gray).append(",\"translucent\":").append(d.translucent).append(",\"line\":{\"x0\":").append(d.lineX0).append(",\"y0\":").append(d.lineY0).append(",\"x1\":").append(d.lineX1).append(",\"y1\":").append(d.lineY1).append(",\"visible\":").append(d.lineVisible).append("},\"active\":").append(d.active).append(",\"alive\":").append(d.active).append(",\"unsupportedTerrainProbes\":").append(d.unsupportedQueries).append(",\"destroyedReason\":\"").append(d.destroyedReason).append("\"}");
        }
        return out.append(']').toString();
    }
    Dagger dagger(int index){return projectiles[index];}int allocationFailures(){return allocationFailures;}
    void setPrimitiveCapacity(int capacity){if(capacity<0||capacity>32)throw new IllegalArgumentException("Invalid primitive capacity");primitiveCapacity=capacity;}
    int sine(int angle){return sin(angle);}int cosine(int angle){return cos(angle);}
}
