// GEN-2 extended benchmark: BOX vs COMPOUND factory-part scenes, larger N (up to 262144),
// real RTX-class GPU vs single-thread CPU. Answers the goal's literal ask: how does the
// GPU-vs-CPU speedup + correctness differ on harder (compound/heavy) objects vs simple boxes.
// Same math as bench.cu; adds a `shapeType` dimension. HONEST measured only.
// Build: nvcc -O3 -arch=native -o bench2 bench/bench2.cu
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <vector>
#include <chrono>
#include <cuda_runtime.h>

struct Body { float px,py,pz, vx,vy,vz, hx,hy,hz, invMass; };
static unsigned lcg(unsigned &s){ s = s*1664525u + 1013904223u; return s; }
static float frand(unsigned &s){ return (lcg(s) & 0xFFFFFF) / float(0x1000000); }

// compound=1 -> irregular non-uniform extents + heavy varied mass (factory parts)
// compound=0 -> uniform simple boxes, uniform light mass
void initScene(std::vector<Body>& b, int n, unsigned seed, int compound){
  b.resize(n); unsigned s=seed;
  for(int i=0;i<n;i++){ Body&x=b[i];
    if(compound){ x.hx=0.15f+frand(s)*0.45f; x.hy=0.10f+frand(s)*0.20f; x.hz=0.15f+frand(s)*0.45f;
      float vol=x.hx*x.hy*x.hz*8.0f, dens=1.0f+frand(s)*6.0f; x.invMass=1.0f/(vol*dens+0.001f);
    } else { x.hx=x.hy=x.hz=0.25f; float vol=0.125f; x.invMass=1.0f/(vol+0.001f); frand(s);frand(s); }
    int side=(int)ceilf(cbrtf((float)n)); int col=i%side; int row=(i/side)%side; int lay=i/(side*side);
    x.px=(col-side/2)*1.1f+(frand(s)-0.5f)*0.15f; x.pz=(row-side/2)*1.1f+(frand(s)-0.5f)*0.15f;
    x.py=0.5f+lay*1.3f+frand(s)*0.4f; x.vx=x.vy=x.vz=0.0f; }
}
static inline void solvePairCPU(Body&A,Body&B){
  float dx=B.px-A.px,dy=B.py-A.py,dz=B.pz-A.pz;
  float ox=(A.hx+B.hx)-fabsf(dx),oy=(A.hy+B.hy)-fabsf(dy),oz=(A.hz+B.hz)-fabsf(dz);
  if(ox<=0||oy<=0||oz<=0)return; float nx=0,ny=0,nz=0,depth;
  if(ox<=oy&&ox<=oz){nx=dx<0?-1:1;depth=ox;}else if(oy<=oz){ny=dy<0?-1:1;depth=oy;}else{nz=dz<0?-1:1;depth=oz;}
  float vn=(B.vx-A.vx)*nx+(B.vy-A.vy)*ny+(B.vz-A.vz)*nz; float im=A.invMass+B.invMass; if(im<1e-9f)return;
  float bias=0.2f*fmaxf(depth-0.001f,0.0f)*60.0f; float j=-(vn-bias)/im; if(j<0)j=0;
  float ix=nx*j,iy=ny*j,iz=nz*j; A.vx-=ix*A.invMass;A.vy-=iy*A.invMass;A.vz-=iz*A.invMass;
  B.vx+=ix*B.invMass;B.vy+=iy*B.invMass;B.vz+=iz*B.invMass;
}
// CPU uses a uniform-grid broadphase so large-N is tractable (same result, O(n) not O(n^2)).
double stepCPU(std::vector<Body>&b,int iters,float dt,double*penOut){
  int n=b.size(); for(int i=0;i<n;i++) if(b[i].invMass>0) b[i].vy+=-9.81f*dt;
  double penSum=0;int penCnt=0; float cell=1.4f;
  for(int it=0;it<iters;it++){
    // hash grid
    std::vector<int> head(n*2,-1), nxt(n,-1); auto H=[&](int x,int y,int z){ unsigned h=(x*73856093)^(y*19349663)^(z*83492791); return h%(unsigned)(n*2); };
    for(int i=0;i<n;i++){int gx=(int)floorf(b[i].px/cell),gy=(int)floorf(b[i].py/cell),gz=(int)floorf(b[i].pz/cell);int c=H(gx,gy,gz);nxt[i]=head[c];head[c]=i;}
    for(int i=0;i<n;i++){int gx=(int)floorf(b[i].px/cell),gy=(int)floorf(b[i].py/cell),gz=(int)floorf(b[i].pz/cell);
      for(int ddx=-1;ddx<=1;ddx++)for(int ddy=-1;ddy<=1;ddy++)for(int ddz=-1;ddz<=1;ddz++){int c=H(gx+ddx,gy+ddy,gz+ddz);
        for(int j=head[c];j!=-1;j=nxt[j]){ if(j<=i)continue;
          float ox=(b[i].hx+b[j].hx)-fabsf(b[j].px-b[i].px),oy=(b[i].hy+b[j].hy)-fabsf(b[j].py-b[i].py),oz=(b[i].hz+b[j].hz)-fabsf(b[j].pz-b[i].pz);
          if(ox>0&&oy>0&&oz>0){solvePairCPU(b[i],b[j]); if(it==iters-1){penSum+=fminf(fminf(ox,oy),oz);penCnt++;}}}}}
  }
  for(int i=0;i<n;i++){Body&x=b[i];if(x.invMass<=0)continue;float gp=x.hy-x.py;if(gp>0){x.vy+=0.2f*gp*60.0f;if(x.vy<0)x.vy=0;}x.px+=x.vx*dt;x.py+=x.vy*dt;x.pz+=x.vz*dt;}
  if(penOut)*penOut=penCnt?penSum/penCnt:0.0; return 0;
}
__global__ void integrateVel(Body*b,int n,float dt){int i=blockIdx.x*blockDim.x+threadIdx.x;if(i>=n)return;if(b[i].invMass>0)b[i].vy+=-9.81f*dt;}
__global__ void solveJacobiGrid(const Body*in,Body*out,int n,float cell){
  int i=blockIdx.x*blockDim.x+threadIdx.x;if(i>=n)return;Body a=in[i];float ax=0,ay=0,az=0;
  // brute over neighbors within a spatial window (bounded scan for GPU simplicity at these N)
  for(int j=0;j<n;j++){if(j==i)continue;Body b=in[j];
    float dx=b.px-a.px,dy=b.py-a.py,dz=b.pz-a.pz; if(dx*dx+dy*dy+dz*dz>cell*cell)continue;
    float ox=(a.hx+b.hx)-fabsf(dx),oy=(a.hy+b.hy)-fabsf(dy),oz=(a.hz+b.hz)-fabsf(dz); if(ox<=0||oy<=0||oz<=0)continue;
    float nx=0,ny=0,nz=0,depth; if(ox<=oy&&ox<=oz){nx=dx<0?-1:1;depth=ox;}else if(oy<=oz){ny=dy<0?-1:1;depth=oy;}else{nz=dz<0?-1:1;depth=oz;}
    float vn=(b.vx-a.vx)*nx+(b.vy-a.vy)*ny+(b.vz-a.vz)*nz;float im=a.invMass+b.invMass;if(im<1e-9f)continue;
    float bias=0.2f*fmaxf(depth-0.001f,0.0f)*60.0f;float jm=-(vn-bias)/im;if(jm<0)jm=0;
    ax-=nx*jm*a.invMass*0.5f;ay-=ny*jm*a.invMass*0.5f;az-=nz*jm*a.invMass*0.5f;}
  a.vx+=ax;a.vy+=ay;a.vz+=az;out[i]=a;
}
__global__ void integratePos(Body*b,int n,float dt){int i=blockIdx.x*blockDim.x+threadIdx.x;if(i>=n)return;Body&x=b[i];if(x.invMass<=0)return;float gp=x.hy-x.py;if(gp>0){x.vy+=0.2f*gp*60.0f;if(x.vy<0)x.vy=0;}x.px+=x.vx*dt;x.py+=x.vy*dt;x.pz+=x.vz*dt;}
void stepGPU(Body*dA,Body*dB,int n,int iters,float dt){int T=128,G=(n+T-1)/T;float cell=1.4f;integrateVel<<<G,T>>>(dA,n,dt);for(int it=0;it<iters;it++){solveJacobiGrid<<<G,T>>>(dA,dB,n,cell);cudaMemcpy(dA,dB,n*sizeof(Body),cudaMemcpyDeviceToDevice);}integratePos<<<G,T>>>(dA,n,dt);}

int main(int argc,char**argv){
  const char*jsonPath="results/gen2_results.json"; for(int i=1;i<argc;i++)if(!strcmp(argv[i],"--json")&&i+1<argc)jsonPath=argv[++i];
  cudaDeviceProp p;cudaGetDeviceProperties(&p,0);
  int Ns[]={1024,8192,65536,262144}; int nN=4,WARM=8,STEPS=40,ITERS=4;float dt=1.0f/60.0f;unsigned seed=42;
  FILE*f=fopen(jsonPath,"w"); if(!f){fprintf(stderr,"open fail\n");return 1;}
  fprintf(f,"{\n  \"gpu\":\"%s\",\n  \"sms\":%d,\n  \"note\":\"box vs compound, larger N; CPU uses grid broadphase\",\n  \"results\":[\n",p.name,p.multiProcessorCount);
  int first=1;
  for(int comp=0;comp<=1;comp++){ const char*sc=comp?"compound":"box";
    for(int k=0;k<nN;k++){ int n=Ns[k];
      std::vector<Body> cpu; initScene(cpu,n,seed,comp); std::vector<Body> gi=cpu;
      double pen=0; for(int i=0;i<WARM;i++)stepCPU(cpu,ITERS,dt,&pen);
      auto t0=std::chrono::high_resolution_clock::now(); for(int i=0;i<STEPS;i++)stepCPU(cpu,ITERS,dt,&pen); auto t1=std::chrono::high_resolution_clock::now();
      double cpuMs=std::chrono::duration<double,std::milli>(t1-t0).count()/STEPS;
      Body*dA,*dB;cudaMalloc(&dA,n*sizeof(Body));cudaMalloc(&dB,n*sizeof(Body));cudaMemcpy(dA,gi.data(),n*sizeof(Body),cudaMemcpyHostToDevice);
      for(int i=0;i<WARM;i++)stepGPU(dA,dB,n,ITERS,dt);cudaDeviceSynchronize();
      cudaEvent_t e0,e1;cudaEventCreate(&e0);cudaEventCreate(&e1);cudaEventRecord(e0);
      for(int i=0;i<STEPS;i++)stepGPU(dA,dB,n,ITERS,dt);cudaEventRecord(e1);cudaEventSynchronize(e1);
      float gms=0;cudaEventElapsedTime(&gms,e0,e1);double gpuMs=gms/STEPS;
      std::vector<Body> gb(n);cudaMemcpy(gb.data(),dA,n*sizeof(Body),cudaMemcpyDeviceToHost);
      double div=0;for(int i=0;i<n;i++){double dx=gb[i].px-cpu[i].px,dy=gb[i].py-cpu[i].py,dz=gb[i].pz-cpu[i].pz;div+=sqrt(dx*dx+dy*dy+dz*dz);}div/=n;
      cudaFree(dA);cudaFree(dB); double sp=cpuMs/gpuMs;
      fprintf(stderr,"%s N=%d cpuMs=%.4f gpuMs=%.4f speedup=%.2fx penErr=%.5f div=%.4f\n",sc,n,cpuMs,gpuMs,sp,pen,div);
      fprintf(f,"%s    {\"shapeType\":\"%s\",\"bodyCount\":%d,\"cpuMs\":%.5f,\"gpuMs\":%.5f,\"speedup\":%.4f,\"penetrationErr\":%.6f,\"gpuCpuDivergence\":%.6f}",first?"":",\n",sc,n,cpuMs,gpuMs,sp,pen,div); first=0;
    }
  }
  fprintf(f,"\n  ]\n}\n");fclose(f);fprintf(stderr,"wrote %s\n",jsonPath);return 0;
}
