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

/** Selected US EntitySubwpnThrownAxe (84B88.c) from reference commit
 * 0622fcfd37311fd21baf2938c6bd74cea63005ba. No terrain query is performed.
 * Original integer motion/spin, hitFlags2 bounce, palette/trail history and
 * camera-Y expiry are retained. Shared local slots/primitive budget replace
 * factory/GPU allocation. Base10 damage and once-per-current-target overlap
 * are authored integration, not the original invincibility/battle system.
 */
public final class AxeWorld {
    static final int ONE=65536,POOL=16;
    interface Environment {int hit(Axe axe,int x,int y,int halfWidth,int halfHeight,int sequence,int damage);}
    static final class Primitive {
        final int[] quad=new int[8],rgb=new int[3];int palette=427,drawMode;
        Primitive(int index){drawMode=index==0?0x108:0x13D;}
    }
    static final class Axe {
        final int slot,serial,sequence;final Primitive[] primitives=new Primitive[5];
        final int[] age=new int[4];final boolean[] trailActive=new boolean[4],fresh=new boolean[4];
        final Object[] hitTargets=new Object[256];
        int x,y,vx,vy,step,face,facingLeft,rotation,pose,poseTimer,hitboxState,hitFlags,hitCount,targetCount,soundVolume=127,spinSoundEvents;
        boolean active=true,crouching,allocated;String destroyedReason="";
        Axe(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;for(int i=0;i<5;i++)primitives[i]=new Primitive(i);}
        boolean hasHit(Object target){for(int i=0;i<targetCount;i++)if(hitTargets[i]==target)return true;return false;}
        boolean rememberHit(Object target){if(target==null||hasHit(target)||targetCount==hitTargets.length)return false;hitTargets[targetCount++]=target;return true;}
    }
    private final DaggerWorld.SharedPool pool;
    private final Axe[] axes=new Axe[POOL];
    private int[] fields,quarter,trailRgb;private int serial,allocationFailures;
    public AxeWorld(){this(new DaggerWorld.SharedPool());}
    AxeWorld(DaggerWorld.SharedPool shared){if(shared==null)throw new IllegalArgumentException("Missing shared pool");pool=shared;}
    public boolean register(int[] definition,int[] sineQuarter,int[] colors){
        if(definition==null||definition.length!=13||sineQuarter==null||sineQuarter.length!=1025||colors==null||colors.length!=40)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]!=4||definition[3]!=2||definition[6]!=93||definition[7]!=9||definition[8]!=2)return false;
        if(sineQuarter[0]!=0||sineQuarter[1024]!=4096)return false;
        for(int i=0;i<1025;i++)if(sineQuarter[i]<0||sineQuarter[i]>4096||i>0&&sineQuarter[i]<sineQuarter[i-1])return false;
        for(int i=0;i<40;i++)if(colors[i]<0||colors[i]>255||i%4==3&&colors[i]!=0)return false;
        fields=definition.clone();quarter=sineQuarter.clone();trailRgb=colors.clone();reset();return true;
    }
    public void reset(){for(int i=0;i<POOL;i++){if(axes[i]!=null)pool.release(axes[i]);axes[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 activeCount(){int count=0;for(Axe axe:axes)if(axe!=null&&axe.active)count++;return count;}
    public int refusal(){return fields==null?1:activeCount()>=fields[3]||pool.freeSlot()<0?2:0;}
    public boolean spawn(int x,int originY,int face,boolean crouch,int sequence){
        if(refusal()!=0||face!=1&&face!=-1||sequence<1||x<Integer.MIN_VALUE+32*ONE||x>Integer.MAX_VALUE-32*ONE||originY<Integer.MIN_VALUE+32*ONE||originY>Integer.MAX_VALUE-32*ONE)return false;
        int index=pool.freeSlot();if(index<0)return false;Axe axe=new Axe(32+index,++serial,sequence,x,originY,face,crouch);pool.reserve(index,axe);axes[index]=axe;return true;
    }
    private int sin(int angle){int a=angle&4095;if(a<=1024)return quarter[a];if(a<=2048)return quarter[2048-a];if(a<=3072)return -quarter[a-2048];return -quarter[4096-a];}
    private int cos(int angle){return sin(angle+1024);}
    private void destroy(Axe axe,String reason){axe.active=axe.allocated=false;axe.hitboxState=0;axe.destroyedReason=reason;for(Primitive primitive:axe.primitives)primitive.drawMode|=8;pool.release(axe);}
    public void update(Environment environment,int cameraX,int cameraY,int gameTimer){
        for(int i=0;i<POOL;i++)updateSlot(i,environment,cameraX,cameraY,gameTimer);
    }
    void updateSlot(int index,Environment environment,int cameraX,int cameraY,int gameTimer){
        if(fields==null)return;Axe axe=axes[index];if(axe==null||!axe.active)return;
            switch(axe.step){
            case 0:
                if(!pool.allocate(axe,5)){allocationFailures++;destroy(axe,"primitive-allocation-failed");return;}
                axe.allocated=true;axe.facingLeft=axe.face==1?1:0;axe.vx=axe.face*2*ONE;axe.vy=-6*ONE;axe.rotation=axe.facingLeft==1?1024:3072;
                if(!axe.crouching)axe.y-=12*ONE;axe.hitboxState=fields[8];axe.step=1;break;
            case 1:
                if(axe.hitboxState!=0){int feedback=environment.hit(axe,axe.x>>16,axe.y>>16,12,12,axe.sequence,fields[0]);if(feedback==1||feedback==2){axe.hitFlags=feedback;axe.hitCount++;}}
                axe.rotation+=axe.facingLeft==1?-128:128;
                if((axe.rotation&1023)==0){axe.spinSoundEvents++;axe.soundVolume=Math.max(0,axe.soundVolume-8);}
                axe.vy=Math.min(8*ONE,axe.vy+17408);axe.x+=axe.vx;axe.y+=axe.vy;
                if((axe.y>>16)-cameraY>256){destroy(axe,"below-source-camera");return;}
                if(axe.hitFlags==2){axe.hitboxState=0;axe.vx=-(axe.vx/2);axe.vy=-3*ONE;axe.step=2;}break;
            case 2:
                axe.rotation+=axe.facingLeft==1?192:-192;axe.vy=Math.min(8*ONE,axe.vy+9216);axe.x+=axe.vx;axe.y+=axe.vy;
                if((axe.y>>16)-cameraY>256){destroy(axe,"below-source-camera");return;}break;
            default:throw new IllegalStateException("Invalid axe step");
            }
            graphics(axe,gameTimer);
    }
    private void graphics(Axe axe,int gameTimer){
        if(axe.poseTimer==0){int index=axe.pose;axe.age[index]=0;axe.trailActive[index]=axe.fresh[index]=true;axe.pose=(index+1)&3;axe.poseTimer=2;}else axe.poseTimer--;
        int palette=427+((gameTimer>>1)&1),x=axe.x>>16,y=axe.y>>16;
        int[] angles=axe.facingLeft==1?new int[]{1376,672,2720,3424}:new int[]{672,1376,3424,2720};
        Primitive head=axe.primitives[0];head.palette=palette;
        for(int i=0;i<4;i++){int angle=angles[i]+axe.rotation;head.quad[2*i]=x+((cos(angle)*336)>>16);head.quad[2*i+1]=y-((sin(angle)*336)>>16);}head.drawMode&=~8;
        for(int i=0;i<4;i++){
            Primitive trail=axe.primitives[i+1];trail.palette=palette;
            if(axe.trailActive[i]){
                if(axe.fresh[i]){axe.fresh[i]=false;System.arraycopy(head.quad,0,trail.quad,0,8);}
                int index=axe.age[i]++;
                if(index<10){for(int channel=0;channel<3;channel++)trail.rgb[channel]=trailRgb[index*4+channel];trail.drawMode&=~8;}
                else{axe.trailActive[i]=false;trail.drawMode|=8;}
            }
        }
    }
    public String getState(){
        StringBuilder out=new StringBuilder("[");boolean comma=false;
        for(Axe axe:axes)if(axe!=null){if(comma)out.append(',');comma=true;
            out.append("{\"kind\":\"axe\",\"entityId\":10,\"inventoryId\":2,\"slot\":").append(axe.slot).append(",\"serial\":").append(axe.serial).append(",\"sequence\":").append(axe.sequence).append(",\"x\":").append(axe.x>>16).append(",\"y\":").append(axe.y>>16).append(",\"xFixed\":").append(axe.x).append(",\"yFixed\":").append(axe.y).append(",\"vxFixed\":").append(axe.vx).append(",\"vyFixed\":").append(axe.vy).append(",\"step\":").append(axe.step).append(",\"face\":").append(axe.face).append(",\"facingLeft\":").append(axe.facingLeft).append(",\"rotation\":").append(axe.rotation).append(",\"pose\":").append(axe.pose).append(",\"poseTimer\":").append(axe.poseTimer).append(",\"hitboxState\":").append(axe.hitboxState).append(",\"hitFlags\":").append(axe.hitFlags).append(",\"hitCount\":").append(axe.hitCount).append(",\"hitTargetCount\":").append(axe.targetCount).append(",\"hitboxWidth\":12,\"hitboxHeight\":12,\"baseAttack\":").append(fields[0]).append(",\"attackElement\":").append(fields[2]).append(",\"soundVolume\":").append(axe.soundVolume).append(",\"spinSoundEvents\":").append(axe.spinSoundEvents).append(",\"active\":").append(axe.active).append(",\"alive\":").append(axe.active).append(",\"destroyedReason\":\"").append(axe.destroyedReason).append("\",\"primitives\":[");
            for(int i=0;i<5;i++){if(i!=0)out.append(',');Primitive p=axe.primitives[i];out.append("{\"index\":").append(i).append(",\"quad\":[");for(int j=0;j<8;j++){if(j!=0)out.append(',');out.append(p.quad[j]);}out.append("],\"rgb\":[").append(p.rgb[0]).append(',').append(p.rgb[1]).append(',').append(p.rgb[2]).append("],\"palette\":").append(p.palette).append(",\"paletteIndex\":").append(p.palette-427).append(",\"drawMode\":").append(p.drawMode).append(",\"translucent\":").append(i!=0).append(",\"visible\":").append(axe.active&&(p.drawMode&8)==0).append('}');}
            out.append("]}");
        }return out.append(']').toString();
    }
    Axe axe(int index){return axes[index];}int allocationFailures(){return allocationFailures;}
    void setPrimitiveCapacity(int capacity){pool.setPrimitiveCapacity(capacity);}
    int sine(int angle){return sin(angle);}int cosine(int angle){return cos(angle);}
}
