//Selah Lynch  hbug2.cc  Jun 2004

//Class HBug definitions
//(see declarations for description of class)


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "hbug.h"

HBug::HBug(int size, int numberofbugs, int seed){
  err=0; //error output rank

  bool bool1, bool2, variablesgood;
  bool1=0<=size&&size<=700000;
  bool2=0<=numberofbugs&&numberofbugs<=700000;
  check(bool1,"Invalid Constructor Variable gridsize");
  check(bool2,"Invalid Constructor Variable numbugs");
  //keeps variables within a reasonable range

  //initialize the cluster variables
  MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
  MPI_Comm_size(MPI_COMM_WORLD, &sizeofcluster);

  MakeGrids(size);

  time=0;

  SetVar(10, 0, .1);
  //setting heatperbug=10;idealtemp=0;coolcoeff=.1;

  dataout.open("overtime.data");
  //this is a file for saving the average unhappiness over time etc.

  MakeBugs(numberofbugs,seed);
}


HBug::~HBug(){
  for(int n=0; n<widthr; n++) {

  delete [] heatgrid[n];
  delete [] bugrid[n];
  delete [] dgrid[n];
  delete [] numtomove[n];

  //if(n<width-2) delete [] dgrid[n];
}

delete [] heatgrid;
delete [] bugrid;
delete [] dgrid;
delete [] numtomove;

}


void HBug::SetVar(int hpb, int it,double cc){
  heatperbug=hpb;
  idealtemp=it;
  coolcoeff=cc;
}



void HBug::MakeGrids(int size){

  //initialize grid sizes
  sizeofgrid=size;
  widthv=Widthv(myrank);
  widthr=widthv+2;
  length=size;

  AllocGrids();

  //Set to zero
  for(int x=0; x<widthr; x++){ 
    for(int y=0; y<length; y++){
      bugrid[x][y]=0;
      heatgrid[x][y]=0;
    }
  }

  howmanybugs=0;
}


void HBug::AllocGrids(){
  heatgrid= new int* [widthr];
  bugrid=new int* [widthr];
  dgrid=new int* [widthr];
  numtomove=new int* [widthr];

  for(int n=0; n<widthr; n++){
    heatgrid[n]= new int [length];
    bugrid[n]= new int [length];
    dgrid[n]= new int [length];
    numtomove[n]= new int [length];
  }
}



void HBug::MakeBugs(int howmany, int seed){
  tempseed=seed;
  howmanybugs=howmany;
  check(howmanybugs<=sizeofgrid*sizeofgrid, "Too Many Bugs!");

  int counter=0;
  DistrBugs(counter);
  MakeSubBugs(counter);
  //printf("Computer %d gets %d bugs.\n",myrank,counter);
}



void HBug::DistrBugs(int& counter){
  srand(70+tempseed);
  int whichsubgrid;
  int n=sizeofgrid%sizeofcluster; //number of computers with strip size ss+1
  int ss=sizeofgrid/sizeofcluster;
  int sprows=n*(ss+1);
  //sprows = "special rows", number of rows with strip size ss+1

  for(int nn=0; nn<howmanybugs; nn++){
    int rownum=rand()%sizeofgrid;
    //figure out which computer's subgrid to put a bug on:
    if(rownum < sprows) whichsubgrid=rownum/(ss+1);
    else whichsubgrid=n+(rownum-sprows)/ss;
    //if(myrank==ROOT)cout<<"Give a bug to computer "<<whichsubgrid<<endl;
    if(myrank==whichsubgrid) counter++;
  }
}


void HBug::MakeSubBugs(int numbugs)
{
  srand(myrank*7+23+tempseed); 
  //gives each computer a unique string of random numbers
  int x,y;
  for(int n=0; n<numbugs; n++){
    x=rand()%(widthv)+1;
    y=rand()%length;
    bugrid[x][y]++;
  }

}



void HBug::Display(int whichgrid, ostream& out, bool forvisual){


  bool isgood = (whichgrid==BUGS||whichgrid==HEAT||whichgrid==DELT);
  check(isgood,"Invalid Grid Specifier");

  char gridname[11];
  if(whichgrid==BUGS) strcpy(gridname,"Bug Grid");
  if(whichgrid==HEAT) strcpy(gridname,"Heat Grid");
  if(whichgrid==DELT) strcpy(gridname,"Delta Grid");

  if(myrank==ROOT && !forvisual)
    out<<"\nHere is the "<<gridname<<" at time "<<time<<"\n\n";

  if(forvisual && whichgrid==HEAT) out.precision(4);

  for(int r=0; r<sizeofcluster; r++){
    DisplaySub(r,whichgrid,out, forvisual);
    MPI_Barrier(MPI_COMM_WORLD);
  }

  if(myrank==ROOT) out<<endl;
}



void HBug::DisplaySub(int displayrank, int whichgrid, ostream& out, bool forvisual){
  int tag;
  MPI_Status stat;

  int** grid;
  if(whichgrid==BUGS) grid=bugrid;
  else if(whichgrid==HEAT) grid=heatgrid;
  else if(whichgrid==DELT) grid=dgrid;

  if(displayrank==ROOT&&myrank==ROOT) {
    for(int x=0; x<widthv; x++)
     DisplayLine(grid[x+1], whichgrid, out, forvisual);  
  }

  else if(displayrank!=ROOT && myrank==displayrank){
    //send appropriate lines with diff tags:
    for(int x=0; x<widthv; x++){
      tag=x*sizeofcluster + displayrank;
      MPI_Send(grid[x+1],sizeofgrid,MPI_INT,ROOT,tag,MPI_COMM_WORLD);
    }
  }

  else if(displayrank!=ROOT && myrank==ROOT){
    //recieve appropriate lines with diff tags
    //and display these lines as I get them in order
    for(int x=0; x<Widthv(displayrank); x++){
      int rmess[sizeofgrid];
      tag=x*sizeofcluster + displayrank;
      //cout<<"tag "<<tag<<"\twidthv "<<Widthv(displayrank)<<endl;
      MPI_Recv(rmess,sizeofgrid,MPI_INT,displayrank,tag,MPI_COMM_WORLD, &s);
      DisplayLine(rmess,whichgrid,out, forvisual);
    }
  }

}



void HBug::DisplayLine(int* lineptr, int whichgrid, ostream& out, bool forvisual){
  check(lineptr!=NULL, "Line Pointer equals NULL!!");
  for(int n=0; n<sizeofgrid; n++){
    if (forvisual&&whichgrid!=BUGS) out<<(double)(lineptr[n])/20.0<<' ';
    else if(forvisual&&whichgrid==BUGS) out<<lineptr[n]<<' ';
    else out<<IntToPix(lineptr[n],whichgrid)<<' ';
  }
  out<<endl;
}



char HBug::IntToPix(int i, int whichgrid){
  check( whichgrid==BUGS||whichgrid==HEAT||whichgrid==DELT, "Bad grid specifier :0");

  if(whichgrid==BUGS){
    if(i>=1) return '0'+i;
    else if(i==0) return '.';
    else check(true, "invalid bugrid value");
  }

  if(whichgrid==HEAT||whichgrid==DELT){
    if(i==0) return '.';
    else return '0'+i;
  }

/*
  if(whichgrid==HEAT){
    if(0<=i&&i<10) return '.';
    else if(10<=i && i<20) return ',';
    else if(20<=i &&  i<30) return 'x';
    else if(30<=i) return 'X';
    else check(true, "invalid heatgrid value");
  }
*/

}


int HBug::Widthv(int rank){
  int n=sizeofgrid%sizeofcluster;
  //how many computers with widthv ss+1

  int ss=sizeofgrid/sizeofcluster;
  //prelim value for widthv

  if (rank<n) return ss+1;
  else return ss;
}



void HBug::TimeStep(){
  //if(myrank==err) printf("BEGIN TimeStep() %d \n", time);
  EmittHeat();
  Diffuse();
  MoveBugs();
  time++;
  GatherData();
  CoolDown();
  //if(myrank==err) printf("END TimeStep() %d \n", time);

}


void HBug::EmittHeat(){
  for(int x=1; x<=widthv; x++){
    for(int y=0; y<length; y++){
      heatgrid[x][y]=heatgrid[x][y]+bugrid[x][y]*heatperbug;
    }
  }
}



void HBug::Diffuse(double coF){
  //if(myrank==0) printf("BEGIN Diffuse()  \n");

  CopyEdges(HEAT); //copys info from other grid to grid edges

  //if(myrank==0) printf("CHECK1 Diffuse()  \n");


  for(int x=1; x<=widthv; x++){
    for(int y=0; y<length; y++){

      //figure out coords, allow to wrap around
      int up, down, left, right;  //up and right are the direction of increase
      if (y==length-1) up=heatgrid[x][0];
      else up= heatgrid[x][y+1];
      if(y==0) down=heatgrid[x][length-1];
      else down=heatgrid[x][y-1];
      left=heatgrid[x-1][y];
      right=heatgrid[x+1][y];

      //store value to be added 
      dgrid[x][y]=(int)(coF*(double)(up+down+left+right-4*heatgrid[x][y]));
    }
  }

  for(int x=1; x<=widthv; x++){
    for(int y=0; y<length; y++){
      heatgrid[x][y]+=dgrid[x][y];
    }
  }

  //if(myrank==0) printf("END Diffuse()  \n");

}



void HBug::CoolDown(){

  //in each computer

  int tot=0;
  for(int x=1; x<=widthv; x++){
    for(int y=0; y<length; y++){
      tot+=heatgrid[x][y];
    }
  }
  int tocool=(int)((double)tot*coolcoeff);
  for(int n=0; n<tocool; n++){
    int x=rand()%widthv+1;
    int y=rand()%length;
    if(heatgrid[x][y]>0) heatgrid[x][y]--;
  }
}




void HBug::MoveBugs(){

  CopyEdges(HEAT);
  
  //imprint num of bugs to be moved in each virtual space
  for(int x=1; x<=widthv; x++){
    for(int y=0; y<length; y++){
      numtomove[x][y]=bugrid[x][y];
    }
  }
 
  for(int x=1; x<=widthv; x++){
    for(int y=0; y<length; y++){
      while(numtomove[x][y]!=0){
	MoveABug(x,y);
	numtomove[x][y]--;
      }
    }
  }
  
  CopyEdges(BUGS);
  DeleteEdges();
}

void HBug::MoveABug(int x, int y){
  //printf("I'm computer %d and I'm moving a bug!\n",myrank);

  int prob[4];
  ProbMove(prob, x,y,idealtemp);
  //at this point all seeds have already been set to different values
  //for the different computers -- that is good

  int range=prob[UP]+prob[DOWN]+prob[LEFT]+prob[RIGHT];
  int whichway=rand()%range;

  if (whichway < prob[UP]) MvBg(x,y,UP);
  else if (whichway < prob[UP]+prob[DOWN]) MvBg(x,y,DOWN);
  else if (whichway < prob[UP]+prob[DOWN]+prob[LEFT]) MvBg(x,y,LEFT);
  else MvBg(x,y,RIGHT);

}


void HBug::MvBg(int x, int y, int dir){
  check(dir==UP||dir==DOWN||dir==LEFT||dir==RIGHT,"Invalid direction integer.");

  char direction[6];
  if (dir==UP)strcpy(direction,"up");
  if (dir==DOWN)strcpy(direction,"down");
  if (dir==LEFT)strcpy(direction,"left");
  if (dir==RIGHT)strcpy(direction,"right");
  //printf("I'm computer %d and I'm moving my bug %s!\n",myrank, direction);

  int* moveto[4];
  FindUDLR(bugrid,x,y,moveto);
  bugrid[x][y]--; //take one away from here
  (*moveto[dir])++;  //and move it to here
}


void HBug::ProbMove(int (&prob)[4], int x, int y, int idealtempx){

  int* dir[4];
  FindUDLR(heatgrid,x,y,dir);
  double tempprob[4];

  //these are difficult choices, i graphed it on an excell file to choose
  //what looked intuitively best
  double smoother =6;
  double pwr=6;

  double sum=0;
  double temp1, temp2;

  for(int n=0; n<4; n++){
    temp1=fabs(idealtempx-*dir[n]) + smoother;
    temp2=pow(temp1,pwr);
    tempprob[n]=1/(temp2);
    sum+=tempprob[n];
  }

  for(int n=0; n<4; n++){
    tempprob[n]=tempprob[n]*1000/sum;
    prob[n]=(int)tempprob[n];
  }
}

void HBug::FindUDLR( int** grid, int x, int y, int* (&dir)[4]){
  //I'm passing by reference, an array of pointers

  check(1<=x&&x<=widthv,"Invalid x value for FindUDLR()");
  check(0<=y&&y<length,"Invalid y value for FindUDLR()");
  
  if (y==0) dir[DOWN]=&(grid[x][length-1]);
  else dir[DOWN]=&(grid[x][y-1]);

  if(y==length-1)dir[UP]=&(grid[x][0]);
  else dir[UP]=&(grid[x][y+1]);

  dir[LEFT]=&(grid[x-1][y]);
  dir[RIGHT]=&(grid[x+1][y]);
 
}


void HBug::DeleteEdges(){
  //for bugrid only
  for (int y=0; y<length; y++){
    bugrid[0][y]=0;
    bugrid[widthr-1][y]=0;
  }

}


void HBug::CopyEdges(int whichgrid){
  //if(myrank==0) printf("BEGIN CopyEdges()  \n");

  MPI_Status stat;

  int **grid;
  check(whichgrid==BUGS||whichgrid==HEAT, "Bad grid specifier int ;0");
  if(whichgrid==BUGS)grid=bugrid;
  if(whichgrid==HEAT)grid=heatgrid;

  //if(myrank==0) printf("CHECK1 CopyEdges()  \n");

  MPI_Barrier(MPI_COMM_WORLD);//keep tags from messing up

  int aboverank; //if a<b a is above b
  int belowrank;
  if (myrank==0) aboverank=sizeofcluster-1;
  else aboverank=myrank-1;
  if (myrank==sizeofcluster-1) belowrank=0;
  else belowrank=myrank+1;

  //if(myrank==0) printf("CHECK2 CopyEdges()  \n");

  //what lines is info being send from(F), 
  //and what line is info being sent to(T)
  //1 is the sending down process, 2 is the sending up process
  int dir;
  if(whichgrid==BUGS) dir=FROM;
  if(whichgrid==HEAT) dir=TO;
  int *F1, *F2, *T1, *T2;
  check(dir==FROM||dir==TO,"Invalid direction specifier int");
  if(dir==FROM)
    {F1=grid[widthr-1]; F2=grid[0]; T1=grid[1]; T2=grid[widthv];}
  if(dir==TO)
    {F1=grid[widthv]; F2=grid[1]; T1=grid[0]; T2=grid[widthr-1];}


  //if(myrank==0) printf("CHECK3 CopyEdges()  \n");


  //send line to the computer below
  MPI_Send(F1,sizeofgrid,MPI_INT,belowrank,myrank,MPI_COMM_WORLD);
 
  //send line to the computer above
  MPI_Send(F2,sizeofgrid,MPI_INT,aboverank,myrank,MPI_COMM_WORLD);

  int mess[sizeofgrid];
  //recieve line from above and copy
  MPI_Recv(mess,sizeofgrid,MPI_INT,aboverank,aboverank,MPI_COMM_WORLD, &stat);
  for(int y=0; y<sizeofgrid; y++) {
    if(whichgrid==HEAT) T1[y]=mess[y];
    if(whichgrid==BUGS) T1[y]+=mess[y];
  }

  //if(myrank==0) printf("CHECK4 CopyEdges()  \n");

  //recieve line from below and copy
  MPI_Recv(mess,sizeofgrid,MPI_INT,belowrank,belowrank,MPI_COMM_WORLD, &stat);
  for(int yy=0; yy<sizeofgrid; yy++) {
    if(whichgrid==HEAT) T2[yy]=mess[yy];
    if(whichgrid==BUGS) (T2[yy]) += (mess[yy]); 
  }
  MPI_Barrier(MPI_COMM_WORLD);//keep tags contained for send and recieve

  //if(myrank==0) printf("END CopyEdges()  \n");

}



void HBug::GatherData(){
  double un=Unhappiness();  //average unhappiness
  double heat=TotHeat();

  if(myrank==ROOT) dataout<<time<<'\t'<<un<<'\t'<<heat<<endl;
}


double HBug::TotHeat(){
  int sumheat=0;
  for(int x=1; x<=widthv; x++){
    for(int y=0; y<length; y++){
      sumheat+=heatgrid[x][y];
    }
  }

  int sumsumheat;
  MPI_Reduce(&sumheat,&sumsumheat,1,MPI_INT,MPI_SUM,ROOT,MPI_COMM_WORLD);

  if(myrank==ROOT) return (double)sumsumheat/(double)(sizeofgrid*sizeofgrid);
  else return -999;
}


double HBug::Unhappiness(){
  //return average unhappiness of all bugs for the root computer only
  //all else return -999 or something

  int sumdiff=0;
  //the sum of the differences of the ideal temp and actual temp for each bug
  
  //span the virtual grid only, not the edges!
  for(int x=1; x<=widthv; x++){
    for(int y=0; y<length; y++){
      int diff=(bugrid[x][y])*(abs((heatgrid[x][y])-idealtemp));
      if(diff!=0){
        //printf("Computer %d adding a difference of %d\n", myrank,diff);
        //printf("ideal temp = %d", idealtemp);
      }

      sumdiff+=diff;
    }
  }

  int sumsumdiff=0;
  MPI_Reduce(&sumdiff,&sumsumdiff,1,MPI_INT,MPI_SUM,ROOT,MPI_COMM_WORLD);

  //printf("rank: %d sumdiff %d sumsumdiff: %d\n", myrank, sumdiff, sumsumdiff);
  
  if(myrank==ROOT) return (double)sumsumdiff/(double)howmanybugs;
  else return -999;

}


void HBug::check(bool b, char* mess){
  if(!b) {
    printf("\n\nERROR[HBug] - %s \n\n\n", mess);
    exit(0);
  }
}


