#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>

#include "../src/param.h"
#include "../src/tdef.h"
#include "../src/common.c"

#include "../src/libutility/utility.h"

#define NGRID   100
//#define ASCII

/*============================================================================
*                              TIPSY DEFINITION
*============================================================================*/
#define MAXDIM  3
typedef float   Real;

struct dump {
   double time ;
   int nbodies ;
   int ndim ;
   int nsph ;
   int ndark ;
   int nstar ;
} TIPSYheader;

struct gas_particle {
   Real mass;
   Real pos[MAXDIM];
   Real vel[MAXDIM];
   Real rho;
   Real temp;
   Real hsmooth;
   Real metals ;
   Real phi ;
} *gas_particles;

struct dark_particle {
   Real mass;
   Real pos[MAXDIM];
   Real vel[MAXDIM];
   Real eps;
   Real phi ;
} *dark_particles;

struct star_particle {
   Real mass;
   Real pos[MAXDIM];
   Real vel[MAXDIM];
   Real metals ;
   Real tform ;
   Real eps;
   Real phi ;
} *star_particles;


void get_density(long unsigned npart, float *x, float *y, float *z, float *rdens, int LGRID);
void assign(long unsigned npart, float *x, float *y, float *z, int LGRID,
            float dens[LGRID][LGRID][LGRID]);
void interp(long unsigned npart, float *x, float *y, float *z,  
            int LGRID, float dens[LGRID][LGRID][LGRID], float *rdens);

/*============================================================================
*                               MAIN
*============================================================================*/
int main(argc,argv)
int argc;
char **argv;
{
   long unsigned int i,j,k,no_part,idim,ipart;
   int               BUFSIZE, SWAPBYTES;
   int               no_timestep;
   float             *fx, *fy, *fz, *fvx, *fvy, *fvz, *fw;
   float             *rdens;
   double            dread[7];
   float             fread[7];
   double            box, a_current, pmass, x_fac, v_fac;
   char              indata[MAXSTRING], outdata[MAXSTRING];
   FILE              *fpin, *fpout;
   double            shift;
   
   /* TIPSY stuff */
   int ndim;
   int nbodies;
   int ngas;
   int ndark;
   int nstar;
   int count;
   struct gas_particle  *gp, *lastgp;
   struct dark_particle *dp, *lastdp;
   struct star_particle *sp, *lastsp;
   
   
   if(argc<2)
     {
      fprintf(stderr,"Use: %s AMIGA_file\n",*argv);
      exit(1);
     }
   
   printf("\n");
   printf("\n=================================================\n");
   printf("\n  Read AMIGA output and convert to TIPSY format\n");
   printf("\n=================================================\n\n");
   
   strcpy(outdata,argv[1]);
   strcat(outdata,".tipsy");
   
   if((fpin=fopen(argv[1],"rb"))==NULL)
     {
      printf("I cannot open %s\n", argv[1]);
      exit(1);
     }
   
   if((fpout=fopen(outdata,"w"))==NULL)
     {
      printf("I cannot open %s\n", outdata);
      exit(1);
     }
   
   /*====================================================
      *               READ AMIGA HEADER
      *====================================================*/
   read_amiga_header(fpin, &io, &SWAPBYTES);
   
   
   /* print some information */
   printf("some information about input file:\n");
   printf("==================================\n");
   printf("%s\n\n",io.header.header);
   printf("multi-mass       = %d\n",io.header.multi_mass);
   printf("double precision = %d\n",io.header.double_precision);
   printf("no_part          = %ld\n",io.header.no_part);
   printf("no_vpart         = %g\n",io.header.no_vpart);
   printf("boxsize          = %g\n",io.header.boxsize);
   printf("omega0           = %g\n",io.header.omega0);
   printf("omegab           = %g\n",io.header.omegab);
   printf("lambda0          = %g\n",io.header.lambda0);   
   printf("pmass            = %g\n",io.header.pmass);   
   printf("gamma            = %g\n",io.header.gamma);
   printf("H_frac           = %g\n",io.header.H_frac);
   printf("T_init           = %g\n",io.header.T_init);
   printf("z_initial        = %g (a=%lf)\n",
          1.0/io.header.a_initial-1.0,io.header.a_initial);
   printf("z_current        = %g (a=%lf)\n",
          1.0/io.header.a_current-1.0,io.header.a_current);
   printf("no_timestep      = %d\n",io.header.no_timestep);
   
   
   /*====================================================
    *            SET CONVERSION FACTORS
    *====================================================*/
   no_part   = io.header.no_part;
   box       = io.header.boxsize;
   a_current = io.header.a_current;
   pmass     = io.header.pmass;
   x_fac     = box;
   v_fac     = box * H0 / a_current;
#ifdef XDOT
   v_fac     = box * H0 / a_current / a_current;
#endif
   shift     = 0.0;
   //shift     = 0.5*1./64.; /* half-a-cell */
   
   
   /*====================================================
    *               READ AMIGA BINARY
    *====================================================*/
   if(io.header.multi_mass == 1)
      BUFSIZE = 7;
   else
      BUFSIZE = 6;
   
   /* build arrays to hold data */
   fx  = malloc(no_part*sizeof(float));
   fy  = malloc(no_part*sizeof(float));
   fz  = malloc(no_part*sizeof(float));
   fvx = malloc(no_part*sizeof(float));
   fvy = malloc(no_part*sizeof(float));
   fvz = malloc(no_part*sizeof(float));
   fw  = malloc(no_part*sizeof(float));
   
   for(i=0;i<io.header.no_part;i++)
     {
      
      if(io.header.double_precision == 1)
        {		  
         for(j=0; j<BUFSIZE; j++)
           {
            ReadDouble(fpin,&(dread[j]),SWAPBYTES);
            fread[j] = (float)dread[j];
           }
        }
      else
        {
         for(j=0; j<BUFSIZE; j++)
           {
            ReadFloat(fpin,&(fread[j]),SWAPBYTES);
           }
        }
      
      /* transfer to relevant arrays */
      fx[i]  = fread[0] + shift;
      fy[i]  = fread[2] + shift;
      fz[i]  = fread[4] + shift;
      fvx[i] = fread[1];
      fvy[i] = fread[3];
      fvz[i] = fread[5];
      if(io.header.multi_mass == 1)
         fw[i] = fread[6];
      else
         fw[i] = 1.0;
     }
   
   fclose(fpin);
   
   /* get density at particle positions */
   rdens = (float *) malloc(no_part*sizeof(float));
   get_density(no_part, fx, fy, fz, rdens, NGRID);
   
   
   TIPSYheader.nbodies = (int) no_part;
   TIPSYheader.nsph    = (int) 0;
   TIPSYheader.nstar   = (int) 0;
   TIPSYheader.ndim    = (int) 3;
   TIPSYheader.time    = (double) a_current;
   
#ifdef ASCII
   /* write tipsy (ascii) format data file */
   
   /* write no_part, no_gas, no_star */
   fprintf(fpout, "%d %d %d\n", no_part, 0, 0);
   
   /* write ndimensions */
   fprintf(fpout, "%d\n", 3);
   
   /* write time */
   fprintf(fpout, "%f\n" , a_current);
   
   /* write the masses */
   for(i = 0; i < no_part; i++)
      fprintf(fpout, "%f\n", fw[i]*pmass);
   
   /* write the coords */
   
   for(i = 0; i < no_part; i++)
      fprintf(fpout, "%f\n", fmod(fx[i]*x_fac+1.0,1.0));
   for(i = 0; i < no_part; i++)
      fprintf(fpout, "%f\n", fmod(fy[i]*x_fac+1.0,1.0));
   for(i = 0; i < no_part; i++)
      fprintf(fpout, "%f\n", fmod(fz[i]*x_fac+1.0,1.0));
   
   /* write the velocities */
   for(i = 0; i < no_part; i++)
      fprintf(fpout, "%f\n", fvx[i]*v_fac);
   for(i = 0; i < no_part; i++)
      fprintf(fpout, "%f\n", fvy[i]*v_fac);
   for(i = 0; i < no_part; i++)
      fprintf(fpout, "%f\n", fvz[i]*v_fac);
   
   /* gravitational softening length -- don't know so put zero */
   for(i = 0; i < no_part; i++)
      fprintf(fpout, "%f\n", 0.0);
   
   /* potential energy -- don't know so WRITE DENSITY INSTEAD */
   for(i = 0; i < no_part; i++)
      fprintf(fpout, "%f\n", rdens[i]);
   
#else   /* WRITE TIPSY BINARY */
   
   ndim    = TIPSYheader.ndim;
   nbodies = TIPSYheader.nbodies;
   ngas    = TIPSYheader.nsph;
   nstar   = TIPSYheader.nstar;
   ndark   = TIPSYheader.ndark = nbodies - nstar - ngas;
   
   
   /* allocate memory for particles */
   if(gas_particles != NULL) free(gas_particles);
   if(ngas != 0) 
     {
      gas_particles =
      (struct gas_particle *) malloc(ngas*sizeof(*gas_particles));
      if(gas_particles == NULL) {
         printf("<sorry, no memory for gas particles>\n") ;
         return(0) ;
      }
     }
   else
      gas_particles = NULL;
   
   if(dark_particles != NULL) free(dark_particles);
   if(ndark != 0)
     {
      dark_particles =
      (struct dark_particle *) malloc(ndark*sizeof(*dark_particles));
      if(dark_particles == NULL) {
         printf("<sorry, no memory for dark particles>\n") ;
         return(0) ;
      }
     }
   else
      dark_particles = NULL;
   
   if(star_particles != NULL) free(star_particles);
   if(nstar != 0)
     {
      star_particles =
      (struct star_particle *)malloc(nstar*sizeof(*star_particles));
      if(star_particles == NULL) {
         printf("<sorry, no memory for star particles>\n") ;
         return(0) ;
      }
     }
   else
      star_particles = NULL;
   
   lastgp = gas_particles  + ngas ;
   lastdp = dark_particles + ndark ;
   lastsp = star_particles + nstar ;
   
   /* transfer to TIPSY arrays */
   for(dp=dark_particles,ipart=0; dp<lastdp; dp++,ipart++)
     {
      dp->pos[0] = fx[ipart] * x_fac;
      dp->pos[1] = fy[ipart] * x_fac;
      dp->pos[2] = fz[ipart] * x_fac;
      dp->vel[0] = fvx[ipart]* v_fac;
      dp->vel[1] = fvy[ipart]* v_fac;
      dp->vel[2] = fvz[ipart]* v_fac;
      dp->mass   = fw[ipart] * pmass;
      dp->phi    = rdens[ipart];   /* actually potential  */
      dp->eps    = 0.001;          /* resolution in Mpc/h */
     }
   
   for(gp=gas_particles,ipart=0; gp<lastgp; gp++,ipart++)
     {
      gp->pos[0] = 0.0;
      gp->pos[1] = 0.0;
      gp->pos[2] = 0.0;
      gp->vel[0] = 0.0;
      gp->vel[1] = 0.0;
      gp->vel[2] = 0.0;
      gp->mass   = 0.0;
      gp->phi    = 0.0;
      gp->hsmooth= 0.0;
      gp->rho    = 0.0;
      gp->temp   = 0.0;
      gp->metals = 0.0;
     }
   
   for(sp=star_particles,ipart=0; sp<lastsp; sp++,ipart++)
     {
      sp->pos[0] = 0.0;
      sp->pos[1] = 0.0;
      sp->pos[2] = 0.0;
      sp->vel[0] = 0.0;
      sp->vel[1] = 0.0;
      sp->vel[2] = 0.0;
      sp->mass   = 0.0;
      sp->metals = 0.0;
      sp->eps    = 0.0;
      sp->phi    = 0.0;
      sp->tform  = 0.0;
     }
   
   
   
   
   fwrite((char *)&TIPSYheader,sizeof(TIPSYheader),1,fpout) ;
   fwrite((char *)gas_particles,sizeof(struct gas_particle),  ngas,fpout) ;
   fwrite((char *)dark_particles,sizeof(struct dark_particle),ndark,fpout) ;
   fwrite((char *)star_particles,sizeof(struct star_particle),nstar,fpout) ;
#endif
   
   
   /* delete arrays */
   free(fx);
   free(fy);
   free(fz);
   free(fvx);
   free(fvy);
   free(fvz);
   
   fclose(fpout);
}

/*=============================================================================
* get_density.c:   calculate the density at each particle position
*                  using a grid with LGRID^3 grid points
*
*                  particle positions are assumed to lie in
*                  the range [0,1] !
*
* 
*     INPUT:    npart, x[npart], y[npart], z[npart], LGRID
*
*     OUTPUT:   rdens[npart]
*
*============================================================================*/
void get_density(long unsigned npart, float *x, float *y, float *z, float *rdens,
                 int LGRID)
{
   float dens[LGRID][LGRID][LGRID], rho_mean;
   int   ix, iy, iz;
   
   rho_mean = (float)npart / (float) (LGRID*LGRID*LGRID);
   
   /* assign particles to the grid */
   assign(npart, x, y, z, LGRID, dens);
   
   /* get density in terms of mean density */
   for(ix=0; ix<LGRID; ix++)
      for(iy=0; iy<LGRID; iy++)
         for(iz=0; iz<LGRID; iz++)
            dens[ix][iy][iz] /= rho_mean;
   
   /* interpolate density contrast back to particle positions */ 
   interp(npart, x, y, z, LGRID, dens, rdens);
}

/*
 * assign:   assign particles to the grid points using a TSC scheme
 */
void assign(long unsigned npart, float *x, float *y, float *z, int LGRID,
       float dens[LGRID][LGRID][LGRID])
{
   int           ix, iy, iz, ixp1, iyp1, izp1, ixm1, iym1, izm1;
   long unsigned ipart;
   float         rrx, rry, rrz;
   float         hx, hy, hz;
   float         hx0, hy0, hz0, hxp1, hyp1, hzp1, hxm1, hym1, hzm1;
   
   for(ix=0; ix<LGRID; ix++)
      for(iy=0; iy<LGRID; iy++)
         for(iz=0; iz<LGRID; iz++)
            dens[ix][iy][iz]=0.0;
   
   for(ipart=0; ipart<npart; ipart++)
     {
      /* coordinates in grid units */
      rrx = x[ipart] * (float)(LGRID);
      rry = y[ipart] * (float)(LGRID);
      rrz = z[ipart] * (float)(LGRID);
      
      /* index of nearest grid point */
      ix  = (int)(rrx+0.5);
      iy  = (int)(rry+0.5);
      iz  = (int)(rrz+0.5);
      
      /* distance to nearest grid point */
      hx  = rrx - (float)ix;
      hy  = rry - (float)iy;
      hz  = rrz - (float)iz;
      
      /* keep track of peridoc boundaries */
      ix=(int)fmod(ix,LGRID);
      iy=(int)fmod(iy,LGRID);
      iz=(int)fmod(iz,LGRID);
      
      /* calculate TSC weights */
      hx0=0.75 - hx*hx;
      hxp1=0.5* pow2(0.5 + hx);
      hxm1=0.5* pow2(0.5 - hx);
      hy0=0.75 - hy*hy;
      hyp1=0.5* pow2(0.5 + hy);
      hym1=0.5* pow2(0.5 - hy);
      hz0= 0.75 - hz*hz;
      hzp1=0.5* pow2(0.5 + hz);
      hzm1=0.5* pow2(0.5 - hz);
      
      /* keep track of peridoc boundaries */
      ixp1=(int)fmod(ix+1,LGRID);
      iyp1=(int)fmod(iy+1,LGRID);
      izp1=(int)fmod(iz+1,LGRID);
      ixm1=(int)fmod(ix-1+LGRID,LGRID);
      iym1=(int)fmod(iy-1+LGRID,LGRID);
      izm1=(int)fmod(iz-1+LGRID,LGRID);
      
      /* assign particle according to weights to 27 neighboring nodes */
      dens[ixm1][iym1][izm1]   = dens[ixm1][iym1][izm1]+ hxm1*hym1 *hzm1;
      dens[ix  ][iym1][izm1]   = dens[ix  ][iym1][izm1]+ hx0 *hym1 *hzm1;
      dens[ixp1][iym1][izm1]   = dens[ixp1][iym1][izm1]+ hxp1*hym1 *hzm1;
      dens[ixm1][iy  ][izm1]   = dens[ixm1][iy  ][izm1]+ hxm1*hy0  *hzm1;
      dens[ix  ][iy  ][izm1]   = dens[ix  ][iy  ][izm1]+ hx0 *hy0  *hzm1;
      dens[ixp1][iy  ][izm1]   = dens[ixp1][iy  ][izm1]+ hxp1*hy0  *hzm1;
      dens[ixm1][iyp1][izm1]   = dens[ixm1][iyp1][izm1]+ hxm1*hyp1 *hzm1;
      dens[ix  ][iyp1][izm1]   = dens[ix  ][iyp1][izm1]+ hx0 *hyp1 *hzm1;
      dens[ixp1][iyp1][izm1]   = dens[ixp1][iyp1][izm1]+ hxp1*hyp1 *hzm1;
      dens[ixm1][iym1][iz  ]   = dens[ixm1][iym1][iz  ]+ hxm1*hym1 *hz0;
      dens[ix  ][iym1][iz  ]   = dens[ix  ][iym1][iz  ]+ hx0 *hym1 *hz0;
      dens[ixp1][iym1][iz  ]   = dens[ixp1][iym1][iz  ]+ hxp1*hym1 *hz0;
      dens[ixm1][iy  ][iz  ]   = dens[ixm1][iy  ][iz  ]+ hxm1*hy0  *hz0;
      dens[ix  ][iy  ][iz  ]   = dens[ix  ][iy  ][iz  ]+ hx0 *hy0  *hz0;
      dens[ixp1][iy  ][iz  ]   = dens[ixp1][iy  ][iz  ]+ hxp1*hy0  *hz0;
      dens[ixm1][iyp1][iz  ]   = dens[ixm1][iyp1][iz  ]+ hxm1*hyp1 *hz0;
      dens[ix  ][iyp1][iz  ]   = dens[ix  ][iyp1][iz  ]+ hx0 *hyp1 *hz0;
      dens[ixp1][iyp1][iz  ]   = dens[ixp1][iyp1][iz  ]+ hxp1*hyp1 *hz0;
      dens[ixm1][iym1][izp1]   = dens[ixm1][iym1][izp1]+ hxm1*hym1 *hzp1;
      dens[ix  ][iym1][izp1]   = dens[ix  ][iym1][izp1]+ hx0 *hym1 *hzp1;
      dens[ixp1][iym1][izp1]   = dens[ixp1][iym1][izp1]+ hxp1*hym1 *hzp1;
      dens[ixm1][iy  ][izp1]   = dens[ixm1][iy  ][izp1]+ hxm1*hy0  *hzp1;
      dens[ix  ][iy  ][izp1]   = dens[ix  ][iy  ][izp1]+ hx0 *hy0  *hzp1;
      dens[ixp1][iy  ][izp1]   = dens[ixp1][iy  ][izp1]+ hxp1*hy0  *hzp1;
      dens[ixm1][iyp1][izp1]   = dens[ixm1][iyp1][izp1]+ hxm1*hyp1 *hzp1;
      dens[ix  ][iyp1][izp1]   = dens[ix  ][iyp1][izp1]+ hx0 *hyp1 *hzp1;
      dens[ixp1][iyp1][izp1]   = dens[ixp1][iyp1][izp1]+ hxp1*hyp1 *hzp1;
     }
}

/*
 * interp:  interpolate the density the grid points back to the 
 *          particle positions using again a TSC scheme
 */
void interp(long unsigned npart, float *x, float *y, float *z,  
       int LGRID, float dens[LGRID][LGRID][LGRID], float *rdens)
{
   int           ix, iy, iz, ixp1, iyp1, izp1, ixm1, iym1, izm1;
   long unsigned ipart;
   float         rrx, rry, rrz;
   float         hx, hy, hz;
   float         hx0, hy0, hz0, hxp1, hyp1, hzp1, hxm1, hym1, hzm1;
   float         ac;
   
   for(ipart=0; ipart<npart; ipart++)
     {
      /* coordinates in grid units */
      rrx = x[ipart] * (float)(LGRID);
      rry = y[ipart] * (float)(LGRID);
      rrz = z[ipart] * (float)(LGRID);
      
      /* index of nearest grid point */
      ix  = (int)(rrx+0.5);
      iy  = (int)(rry+0.5);
      iz  = (int)(rrz+0.5);
      
      /* distance to nearest grid point */
      hx  = rrx - (float)ix;
      hy  = rry - (float)iy;
      hz  = rrz - (float)iz;
      
      /* keep track of peridoc boundaries */
      ix=(int)fmod(ix,LGRID);
      iy=(int)fmod(iy,LGRID);
      iz=(int)fmod(iz,LGRID);
      
      /* calculate TSC weights */
      hx0=0.75 - hx*hx;
      hxp1=0.5* pow2(0.5 + hx);
      hxm1=0.5* pow2(0.5 - hx);
      hy0=0.75 - hy*hy;
      hyp1=0.5* pow2(0.5 + hy);
      hym1=0.5* pow2(0.5 - hy);
      hz0= 0.75 - hz*hz;
      hzp1=0.5* pow2(0.5 + hz);
      hzm1=0.5* pow2(0.5 - hz);
      
      /* keep track of peridoc boundaries */
      ixp1=(int)fmod(ix+1,LGRID);
      iyp1=(int)fmod(iy+1,LGRID);
      izp1=(int)fmod(iz+1,LGRID);
      ixm1=(int)fmod(ix-1+LGRID,LGRID);
      iym1=(int)fmod(iy-1+LGRID,LGRID);
      izm1=(int)fmod(iz-1+LGRID,LGRID);
      
      
      ac =   dens[ixm1][iym1][izm1]* hxm1*hym1 *hzm1
         +dens[ix  ][iym1][izm1]* hx0 *hym1 *hzm1
         +dens[ixp1][iym1][izm1]* hxp1*hym1 *hzm1
         +dens[ixm1][iy  ][izm1]* hxm1*hy0  *hzm1
         +dens[ix  ][iy  ][izm1]* hx0 *hy0  *hzm1
         +dens[ixp1][iy  ][izm1]* hxp1*hy0  *hzm1
         +dens[ixm1][iyp1][izm1]* hxm1*hyp1 *hzm1
         +dens[ix  ][iyp1][izm1]* hx0 *hyp1 *hzm1
         +dens[ixp1][iyp1][izm1]* hxp1*hyp1 *hzm1
         +dens[ixm1][iym1][iz  ]* hxm1*hym1 *hz0
         +dens[ix  ][iym1][iz  ]* hx0 *hym1 *hz0
         +dens[ixp1][iym1][iz  ]* hxp1*hym1 *hz0
         +dens[ixm1][iy  ][iz  ]* hxm1*hy0  *hz0
         +dens[ix  ][iy  ][iz  ]* hx0 *hy0  *hz0;
      ac +=  dens[ixp1][iy  ][iz  ]* hxp1*hy0  *hz0
         +dens[ixm1][iyp1][iz  ]* hxm1*hyp1 *hz0
         +dens[ix  ][iyp1][iz  ]* hx0 *hyp1 *hz0
         +dens[ixp1][iyp1][iz  ]* hxp1*hyp1 *hz0
         +dens[ixm1][iym1][izp1]* hxm1*hym1 *hzp1
         +dens[ix  ][iym1][izp1]* hx0 *hym1 *hzp1
         +dens[ixp1][iym1][izp1]* hxp1*hym1 *hzp1
         +dens[ixm1][iy  ][izp1]* hxm1*hy0  *hzp1
         +dens[ix  ][iy  ][izp1]* hx0 *hy0  *hzp1
         +dens[ixp1][iy  ][izp1]* hxp1*hy0  *hzp1
         +dens[ixm1][iyp1][izp1]* hxm1*hyp1 *hzp1
         +dens[ix  ][iyp1][izp1]* hx0 *hyp1 *hzp1
         +dens[ixp1][iyp1][izp1]* hxp1*hyp1 *hzp1;
      rdens[ipart] = ac;
      
     }
}
