#include <stdio.h>
#include <string.h>
#include "converse.h"
#include "trace.h"
#include <errno.h>
#include <stdlib.h>

#include "jacobi.h"

#if CMK_WHEN_PROCESSOR_IDLE_USLEEP
#include <sys/types.h>
#include <sys/time.h>
#endif

#if CMK_TIMER_USE_TIMES
#include <sys/times.h>
#include <limits.h>
#include <unistd.h>
#endif

#if CMK_TIMER_USE_GETRUSAGE
#include <sys/time.h>
#include <sys/resource.h>
#endif

#define MAX_PLANES 7
#define CELL_SIZE 16
#define CELL_DIM 16
#define FIELD_LEN 10

int shrinkRatio;
int cell_size;
int *array;
CpvDeclare(int, CWebPlateDataCollectionHandlerIndex);
int **plate[MAX_PLANES];
int **processorMap;
int totalNum[MAX_PLANES];
CpvDeclare(char **, localBuffer);
CpvDeclare(int, localBufferCount);
int currentTS;
CpvDeclare(int, x1);
CpvDeclare(int, y1);
CpvDeclare(int, x2);
CpvDeclare(int, y2);
CpvDeclare(int, newRefineFlag);
CpvDeclare(int, numLocalBuffered);

extern unsigned int appletIP;
extern unsigned int appletPort;

void CWebPlateRegisterCell()
{
  CpvAccess(numLocalBuffered)++;
}

void CWebPlateUnregisterCell()
{
  CpvAccess(numLocalBuffered)--;
}

#define POWER2(x) (1 << (x))

// int power2(int index)
// {
//   int i;
//   int ret = 1;

//   for(i = 0; i < index; i++)
//     ret = ret*2;

//   return(ret);
// }

void shrink(int **array, int **newarray, int rows, int columns, int ratio)
{
  int i,j,k,l;
  int jump = POWER2(ratio-1);
  int sum;

  for(i = 0; i < rows/jump; i++){
    for(j = 0; j < columns/jump; j++){
      sum = 0;
      for(k = i*jump; k < (i+1)*jump; k++)
        for(l = j*jump; l < (j+1)*jump; l++)
          sum += array[k][l];

      newarray[i][j] = (sum/(jump * jump));
    }
  }

}

void CWebPlateDataDeposit(int timestep, int cellx, 
			  int celly, int rows, int columns, int **data)
{
  char *msg;
  int *ptr;
  int i, j, k;
  int msgSize;
  int *len;

  shrink(data, data, rows, columns, shrinkRatio);
  rows /= shrinkRatio;
  columns /= shrinkRatio;

  msgSize = CmiMsgHeaderSizeBytes + (6 + rows*columns) * sizeof(int);
  msg = (char *)CmiAlloc(msgSize);
  ptr = (int *)(msg + CmiMsgHeaderSizeBytes);
  ptr[0] = timestep;
  ptr[1] = cellx;
  ptr[2] = celly;
  ptr[3] = rows;
  ptr[4] = columns;
  ptr[5] = CmiMyPe();
  ptr = ptr + 6;
  k = 0;
  for(i = 0; i < rows; i++)
    for(j = 0; j < columns; j++)
      ptr[k++] = data[i][j];
  
  CmiSetHandler(msg, CpvAccess(CWebPlateDataCollectionHandlerIndex));
  CpvAccess(localBuffer)[CpvAccess(localBufferCount)] = msg;
  CpvAccess(localBufferCount)++;
  if(CpvAccess(localBufferCount) == CpvAccess(numLocalBuffered)){
    len = (int *)malloc(sizeof(int) * CpvAccess(numLocalBuffered));
    for(i = 0; i < CpvAccess(numLocalBuffered); i++)
      len[i] = msgSize;
    CmiMultipleSend(0, CpvAccess(numLocalBuffered), len, 
		    CpvAccess(localBuffer));
    //CmiPrintf("NumLocalBuffered = %d\n", CpvAccess(numLocalBuffered));
    CpvAccess(localBufferCount) = 0;
  }
  
}

void CWebPlateDataCollectionHandler(char *msg)
{
  char *reply;
  char t[FIELD_LEN];
  int **currentPlate;
  int ts;
  int *ptr;
  int rows, columns;
  int cellx, celly;
  int i, j, k, m, num;
  int base = 10, max = 1, digit, temp;
  
  ptr = (int *)(msg + CmiMsgHeaderSizeBytes);
  ts = ptr[0];
  cellx = ptr[1];
  celly = ptr[2];
  rows = ptr[3];
  columns = ptr[4];
  processorMap[cellx][celly] = ptr[5];
  ptr = ptr + 6;
  currentPlate = plate[ts % MAX_PLANES];
  k = 0;

  for(i = cellx * cell_size; i < (cellx * cell_size + rows); i++)
    for(j = celly * cell_size; j < (celly * cell_size + columns); j++)
      currentPlate[i][j] = ptr[k++];

  totalNum[ts % MAX_PLANES] += rows*columns;
  if(totalNum[ts % MAX_PLANES] == CELL_DIM * CELL_DIM 
     * cell_size * cell_size){

    if(ts < currentTS){
      return;
    }

    /* Ignore if the client is not yet connected */
    if(appletIP == 0) {
      return;
    }
    
    /* Send data in row major order */
    /* Allocate data for 'jeff', ts, totalrows, totalcolumns data 
       and ending '\0' */
    reply = (char *)malloc(FIELD_LEN * sizeof(char) * 
			   (totalNum[ts % MAX_PLANES] + 4) + 1);
    sprintf(reply, "%s %d %d %d", "jeff" , ts, CELL_DIM * cell_size, 
	    CELL_DIM * cell_size);
    k = strlen(reply);

    for(i = 0; i < CELL_DIM * cell_size; i++){
      for(j = 0; j < CELL_DIM * cell_size; j++){
	num = currentPlate[i][j];
	sprintf(t, "%d", num);
	max = array[strlen(t) - 1];
	temp = num;
	reply[k++] = ' ';
	for(m = max; m > 1; (m = m/base)){
	  digit = temp / m;
	  temp = temp - (digit * m) ;
	  reply[k++] = (char)('0' + digit);
	  if((digit < 0) || (digit > 9)) printf("digit = %d\n", digit);
	}
	reply[k++] = (char)('0' + temp);
	if((temp < 0) || (temp > 9)) 
	  printf("temp = %d %d %d\n", temp, num, max);
	currentPlate[i][j] = 0;
      }
    }
    reply[k] = '\0';
    currentTS = ts;

#if CMK_USE_PERSISTENT_CCS
    CcsSendReplyFd(appletIP, appletPort, strlen(reply) + 1, reply);
#else
    CcsSendReply(appletIP, appletPort, strlen(reply) + 1, reply);
#endif
    free(reply);
    
    /* Send Processor Map in row major order */
    /* Allocate data for 'pmap', totalrows, totalcolumns data 
       and ending '\0' */
    reply = (char *)malloc(FIELD_LEN * sizeof(char) * 
			   (CELL_DIM * CELL_DIM + 3) + 1);
    sprintf(reply, "%s %d %d", "pmap" , CELL_DIM, CELL_DIM);
    k = strlen(reply);

    for(i = 0; i < CELL_DIM; i++){
      for(j = 0; j < CELL_DIM; j++){
	num = processorMap[i][j];
	sprintf(t, "%d", num);
	max = array[strlen(t) - 1];
	temp = num;
	reply[k++] = ' ';
	for(m = max; m > 1; (m = m/base)){
	  digit = temp / m;
	  temp = temp - (digit * m) ;
	  reply[k++] = (char)('0' + digit);
	  if((digit < 0) || (digit > 9)) printf("digit = %d\n", digit);
	}
	reply[k++] = (char)('0' + temp);
	if((temp < 0) || (temp > 9)) 
	  printf("temp = %d %d %d\n", temp, num, max);
	processorMap[i][j] = 0;
      }
    }
    reply[k] = '\0';

#if CMK_USE_PERSISTENT_CCS
    CcsSendReplyFd(appletIP, appletPort, strlen(reply) + 1, reply);
#else
    CcsSendReply(appletIP, appletPort, strlen(reply) + 1, reply);
#endif
    free(reply);
    
    totalNum[ts % MAX_PLANES] = 0;
  }
}

void CWebPlateLoadBalanceHandler(char *msg)
{
  CkPrintf("[%d] Balance button pressed\n",CkMyPe());

  //  BalSoonMsg *m = new BalSoonMsg;
  //  CProxy_strategy(strategyID).BalanceSoon(m);
}

void CWebPlateRefineHandler(char *msg)
{
  int msgSize;
  char *getStuffMsg;
  int i;
  int x1, y1, x2, y2;

  if(CcsIsRemoteRequest()) {
    char name[32];
    int nscanfread;

    nscanfread = sscanf(msg+CmiMsgHeaderSizeBytes, 
			"%s %d %d %d %d", name, &x1, &y1, &x2, &y2);
    if((nscanfread == 5) && (strcmp(name, "refinedata") == 0)){
      CpvAccess(newRefineFlag) = 1;
      CpvAccess(x1) = x1;
      CpvAccess(y1) = y1;
      CpvAccess(x2) = x2;
      CpvAccess(y2) = y2;
      /* DEBUGGING */
      CmiPrintf("%d] Coords = %d %d %d %d\n", CmiMyPe(), 
		CpvAccess(x1), CpvAccess(y1), CpvAccess(x2), CpvAccess(y2));
    }
    else{
      CmiPrintf("incorrect command:%s received, len=%d\n",name,strlen(name));
    }
  }
  else{
    /* Ordinary converse message - Incorrect*/
    CmiPrintf("Error : 'refinedata' message in Processor %d\n", CmiMyPe());
  }
}

int CWebPlateCheckRefine(int *ptr)
{
  int temp = CpvAccess(newRefineFlag);
  
  ptr[0] = CpvAccess(x1);
  ptr[1] = CpvAccess(y1);
  ptr[2] = CpvAccess(x2);
  ptr[3] = CpvAccess(y2);
  CpvAccess(newRefineFlag) = 0;
  return(temp);
}

void CWebPlateInit(int ratio)
{
  int i, j, k;
  int base = 1;

  shrinkRatio = ratio;
  cell_size = CELL_SIZE/shrinkRatio;

  for(i = 0; i < MAX_PLANES; i++){
    plate[i] = (int **)malloc(CELL_DIM * cell_size* sizeof(int *));
    totalNum[i] = 0;
    for(j = 0; j < CELL_DIM * cell_size; j++)
      plate[i][j] = (int *)malloc(CELL_DIM * cell_size * sizeof(int));
  }
  
  /* Initialize array */
  array = (int *)malloc(sizeof(int) * (FIELD_LEN - 1));
  for(i = 0; i < FIELD_LEN - 1; i++){
    array[i] = base;
    base = base * 10;
  }

  processorMap = (int **)malloc(CELL_DIM * sizeof(int *));
  for(i = 0; i < CELL_DIM; i++)
    processorMap[i] = (int *)malloc(CELL_DIM * sizeof(int));

  CpvInitialize(int, CWebPlateDataCollectionHandlerIndex);
  CpvAccess(CWebPlateDataCollectionHandlerIndex) = 
    CmiRegisterHandler((CmiHandler)CWebPlateDataCollectionHandler);
  CpvInitialize(int, newRefineFlag);
  CpvInitialize(int, numLocalBuffered);
  CpvInitialize(char **, localBuffer);
  CpvInitialize(int, localBufferCount);
  CpvInitialize(int, x1);
  CpvInitialize(int, y1);
  CpvInitialize(int, x2);
  CpvInitialize(int, y2);
  
  CpvAccess(newRefineFlag) = 0;
  CpvAccess(numLocalBuffered) = 0;
  CpvAccess(localBuffer) = (char **)malloc(CELL_DIM*CELL_DIM * sizeof(char *));
  CpvAccess(localBufferCount) = 0;
  CpvAccess(x1) = 0;
  CpvAccess(y1) = 0;
  CpvAccess(x2) = 0;
  CpvAccess(y2) = 0;

  CcsRegisterHandler("RefineHandler", (CmiHandler)CWebPlateRefineHandler);
  CcsRegisterHandler("LoadBalanceHandler", (CmiHandler)CWebPlateLoadBalanceHandler);
}
