/* cluster: basic functions for multi process support */

#include "defs.h"
#ifdef USE_CLUSTER

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>


int global_pnum=0;
int global_pid=-1;

int tomaster;
int* toslave;
int frommaster;
int* fromslave;

/*internal functions*/

inline int writeall(int handle, void * buffer, size_t count) {
/*writes a whole buffer to the handle*/

   size_t restlength,retval;
   void * pointer;

   restlength=count;
   pointer=buffer;

   while (restlength>0) {
      retval=write(handle,pointer,restlength);
      if (retval<1) return retval;
      restlength-=retval;
      pointer+=retval;

   }
   return count;
}

inline int readall(int handle, void * buffer, size_t count) {
/*reads all from handle into buffer*/

   size_t restlength,retval;
   void * pointer;

   restlength=count;
   pointer=buffer;

   while (restlength>0) {
      retval=read(handle,pointer,restlength);
      if (retval<1) return retval;
      restlength-=retval;
      pointer+=retval;
   }
   return count;
}



/*external functions*/

int cluster_setpnum(int num) {
/*sets number of processes to num*/
   if (global_pnum>0) {
      fprintf(stderr,"Panic: ProcessNumber already set!\n");
      exit(-1);
   }
   if (num>1) {
      toslave=(int*)malloc(num*sizeof(int));
      fromslave=(int*)malloc(num*sizeof(int));
      global_pnum=num-1;
      return 0;
   } else {
      global_pnum=0;
      return -1;
   }
}

inline int pnum() {
/* returns the set number of processes */
   return global_pnum+1;
}

inline int pid() {
/* returns the process (internal) pid */
   return global_pid;
}

inline size_t cluster_split_min(size_t min, size_t max) {
/*if a working region is spilt amongst processes, return boundaries
  for each process */
   size_t interval,ret;

   if (global_pnum<1) return(min);
   interval=(max-min)/(global_pnum+1);
   if (interval==0) interval=1;
   ret=min+(global_pid*interval);
   if (ret>max) return max-1;
   return min+(global_pid*interval);
   
}

inline size_t cluster_split_max(size_t min, size_t max) {
/*if a working region is spilt amongst processes, return boundaries
  for each process */
   size_t interval,ret;

   if (global_pnum<1) return(max);
   if (global_pid==global_pnum) return (max);
   interval=(max-min)/(global_pnum+1);
   if (interval==0) interval=1;
   ret=min+((global_pid+1)*interval);
   if (ret>max) return max;
   return ret;

}

inline int cluster_split() {
/* Split execution into multiple processes */

   int t;
   int tp[2];
   
   if (global_pnum<1) return(0);
   if (global_pid>-1) {
      fprintf(stderr,"Panic: Tried to split multiple times!\n");
      exit(-1);
   }
   
   #ifdef USE_DEBUG
      fprintf(stderr,"cluster: splitting into %i processes\n",global_pnum+1);
   #endif
   
   t=0;
   while (t<global_pnum) {
      pipe(tp);
      tomaster=tp[1];
      fromslave[t]=tp[0];
      pipe(tp);
      toslave[t]=tp[1];
      frommaster=tp[0];
      if (fork()==0) {
	 close(fromslave[t]);
	 close(toslave[t]);
	 global_pid=t+1;
         #ifdef USE_DEBUG
            fprintf(stderr,"cluster: process %i ready.\n",global_pid);
         #endif
	 return t+1;
      }
      close(frommaster);
      close(tomaster);
      t++;
   }
   global_pid=0;
   #ifdef USE_DEBUG
      fprintf(stderr,"cluster: master process ready.\n");
   #endif
   return 0;
}

inline int cluster_sync_add(int value) {
/* return value added up from all processes */
   int t,tt,sum;

   if (global_pnum<1) return(value);

   if (global_pid<0) {
      fprintf(stderr,"Panic: Tried to sync_add while single threaded!\n");
      exit(-1);
   }
   #ifdef USE_DEBUG
      fprintf(stderr,"cluster(%i): add-syncing value +%i... \n",global_pid,value);
   #endif
   if (global_pid>0) {
      writeall(tomaster,(void*)&value,sizeof(int));
      readall(frommaster,(void*)&sum,sizeof(int));
   } else {
      t=0;
      sum=value;
      while (t<global_pnum) {
         readall(fromslave[t],(void*)&tt,sizeof(int));
	 sum+=tt;
         t++;
      }
      t=0;
      while (t<global_pnum) {
         writeall(toslave[t],(void*)&sum,sizeof(int));
         t++;
      }
   }
   #ifdef USE_DEBUG
      fprintf(stderr,"cluster(%i): add-sync done, result: %i! \n",global_pid,sum);
   #endif
   
   return sum;
}

inline int cluster_sync_min(int value) {
/* return smallest from all processes */
   int t,tt,sum;

   if (global_pnum<1) return(value);

   if (global_pid<0) {
      fprintf(stderr,"Panic: Tried to sync_min while single threaded!\n");
      exit(-1);
   }
   #ifdef USE_DEBUG
      fprintf(stderr,"cluster(%i): min-syncing value <=%i... \n",global_pid,value);
   #endif
   if (global_pid>0) {
      writeall(tomaster,(void*)&value,sizeof(int));
      readall(frommaster,(void*)&sum,sizeof(int));
   } else {
      t=0;
      sum=value;
      while (t<global_pnum) {
         readall(fromslave[t],(void*)&tt,sizeof(int));
	 if (tt<sum) sum=tt;
         t++;
      }
      t=0;
      while (t<global_pnum) {
         writeall(toslave[t],(void*)&sum,sizeof(int));
         t++;
      }
   }
   #ifdef USE_DEBUG
      fprintf(stderr,"cluster(%i): min-sync done, result: %i! \n",global_pid,sum);
   #endif
   
   return sum;
}

inline int cluster_sync_max(int value) {
/* return biggest value from all processes */
   int t,tt,sum;

   if (global_pnum<1) return(value);

   if (global_pid<0) {
      fprintf(stderr,"Panic: Tried to sync_max while single threaded!\n");
      exit(-1);
   }
   #ifdef USE_DEBUG
      fprintf(stderr,"cluster(%i): max-syncing value >=%i... \n",global_pid,value);
   #endif
   if (global_pid>0) {
      writeall(tomaster,(void*)&value,sizeof(int));
      readall(frommaster,(void*)&sum,sizeof(int));
   } else {
      t=0;
      sum=value;
      while (t<global_pnum) {
         readall(fromslave[t],(void*)&tt,sizeof(int));
	 if (tt>sum) sum=tt;
         t++;
      }
      t=0;
      while (t<global_pnum) {
         writeall(toslave[t],(void*)&sum,sizeof(int));
         t++;
      }
   }
   #ifdef USE_DEBUG
      fprintf(stderr,"cluster(%i): max-sync done, result: %i! \n",global_pid,sum);
   #endif
   
   return sum;
}


inline int cluster_sync_data_tomaster(void* start,size_t length) {
/* syncs data between processes, each process
   writes length data at start to process 0 */
   if (global_pid<0) {
      fprintf(stderr,"Panic: Tried to sync_data_write while single threaded!\n");
      exit(-1);
   }
   writeall(tomaster,start,length);

   return 0;
}

inline int cluster_sync_data_fromslave(void* start,size_t length,int pnum) {
/* syncs data between processes, pid 0 reads those data */
   int t;
   if (global_pid<0) {
      fprintf(stderr,"Panic: Tried to sync_data_read while single threaded!\n");
      exit(-1);
   }
   if (pnum<1) {
      fprintf(stderr,"Panic: Tried to read data from pid 0 !\n");
      exit(-1);
   }
   if (pnum>global_pnum) {
      t=global_pnum-1;
   } else {
      t=pnum-1;
   }
   readall(fromslave[t],start,length);
   return 0;
}

inline int cluster_regroup(){
/* re-groups execution to a single thread */

   int t;

   if (global_pnum<1) return(0);

   if (global_pid<0) {
      fprintf(stderr,"Panic: Tried to regroup when already single threaded!\n");
      exit(-1);
   }
   #ifdef USE_DEBUG
      fprintf(stderr,"cluster(%i): regrouping to single threaded mode.\n",global_pid);
   #endif

   if (global_pid>0) {
      #ifdef USE_DEBUG
         fprintf(stderr,"cluster: process %i ends.\n",global_pid);
      #endif
      close(tomaster);
      close(frommaster);
      exit(0);
   } else {
      t=0;
      while (t<global_pnum) {
	 close(fromslave[t]);
	 close(toslave[t]);
	 t++;
      }
   }
   /* close all remeaning pipes and exit */
   global_pid=-1;
   return 0;
}

#endif

