#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h>
#include <strings.h>
#include <iostream.h>
#include <fstream.h>
#include <string>

#include "iotrace.h"
#include "Manager.h"
#include "NASD.h"
#include "stat.h"
#include "client.h"

#define XBLOCKNUM(offset, count)  (((offset) + (count) - 1)/BLOCKSIZE - (offset) / BLOCKSIZE + 1)

/*
int blocknum(unsigned offset, unsigned count)
{
  return((offset + count + BLOCKSIZE - 1)/BLOCKSIZE - offset / BLOCKSIZE);
}
*/

Manager * manager;
NasdServer * nserver[MAXSERVERNUM];
Client * nclient[MAXCLIENTNUM];
StorageServer * storageserver;  // a global back store  
ClientServer * clientserver;

FILE * tracefile;

unsigned server_num;
unsigned char server_mode;
unsigned server_size;
unsigned s_server_size;   // the size of global storage server
unsigned server_refratio; // the ratio of size of ref list to cache size, used in 1 Chance

unsigned trace_num;       // it may be different from client_num
unsigned client_num;
unsigned char client_mode;
unsigned client_size;
unsigned c_server_size;  // the size of global cache server
unsigned client_refratio;

unsigned network_ratio;  // multiple hops

// valid arguments:
//   simu1 [b|g|d] serversize [b|g|d|n] clientsize tracefilename
//
// return 1 means valid, return 0 means invalid
int scanparameter(int argc, char **argv)
{
  char tmp[64];
  unsigned utmp;

  if (argc != 9)
    {
      fprintf(stderr, "Invalid arguments\n");
      fprintf(stderr, "Usage: simu1 [b|g|d] serversize s_refratio [b|g|d|n] clientsize c_refratio, net_ratio, tracefilename\n");
      return 0;
    }
  
  // !!!! get client number and server number here
  tracefile = fopen(argv[8], "rb");
  if (tracefile == NULL)
    {
      fprintf(stderr, "Can not open trace file.\n");
      return 0;
    }
  
  fread(&server_num, sizeof(unsigned), 1, tracefile);
  fread(&trace_num, sizeof(unsigned), 1, tracefile);
  client_num = trace_num;

  printf("Trace:\n\n");

  for (int i = 0 ; i < trace_num ; i ++)
    {
      fread((void *)tmp, sizeof(char), 64, tracefile);
      printf("  %s", tmp);
      fread((void *)&utmp, sizeof(unsigned), 1, tracefile);
      printf(" %u", utmp);
      fread((void *)&utmp, sizeof(unsigned), 1, tracefile);
      printf(" %u\n", utmp);
    } // end of for

  server_size = atoi(argv[2]);

  switch(argv[1][0])
    {
    case 'a':
      server_mode = SERVER_SCHEME_GLOBAL;
      s_server_size = server_size * server_num;
      server_size = server_size;
      break;
    case 'b':
      server_mode = SERVER_SCHEME_BASE;
      break;
    case 'g':
      server_mode = SERVER_SCHEME_GLOBAL;
      s_server_size = server_size * server_num * 3 / 4;
      server_size = server_size / 4;
      break;
    case 'd':
      server_mode = SERVER_SCHEME_CHANCE1;
      break;
    case 'r':
      server_mode = SERVER_SCHEME_CHANCER;  // return on hig
      break;
    case 'h':
      server_mode = SERVER_SCHEME_CHANCEH;  // on hint
      break;
    case 'x':
      server_mode = SERVER_SCHEME_CHANCEX;  // random
      break;
    case 'z':
      server_mode = SERVER_SCHEME_CHANCEZ;  // the best distributed scheme
      break;
    case 'n':
      server_mode = SERVER_SCHEME_NONE;
      server_num = 0;
      break;
    default:
      fprintf(stderr, "Invalid server mode\n");
      return 0;
    } // end of switch

  server_refratio = atoi(argv[3]);

  client_size = atoi(argv[5]);

  switch(argv[4][0])
    {
    case 'a':
      client_mode = CLIENT_SCHEME_GLOBAL;
      c_server_size = client_size * client_num;
      client_size = client_size;
      break;
    case 'b':
      client_mode = CLIENT_SCHEME_BASE;
      break;
    case 'g':
      client_mode = CLIENT_SCHEME_GLOBAL;
      c_server_size = client_size * client_num * 3 / 4;
      client_size = client_size / 4;
      break;
    case 'd':
      client_mode = CLIENT_SCHEME_CHANCE1;
      break;
    case 'n':
      client_mode = CLIENT_SCHEME_NONE;
      client_num = 0;
      break;
    default:
      fprintf(stderr, "Invalid server mode\n");
      return 0;
    } // end of switch

  client_refratio = atoi(argv[6]);

  printf("\nConfiguration:\n\n");

  printf("  %d NASD servers, each with a %d * %d cache.\n", server_num, BLOCKSIZE, server_size);
  switch (server_mode)
    {
    case SERVER_SCHEME_NONE:
      printf("  Scheme: none.\n\n");
      break;
    case SERVER_SCHEME_BASE:
      printf("  Scheme: noncooperative.\n\n");
      break;
    case SERVER_SCHEME_GLOBAL:
      printf("  Scheme: global storage server with a %d * %d cache.\n\n", BLOCKSIZE, s_server_size);
      break;
    case SERVER_SCHEME_CHANCE1:
      printf("  Scheme: 1 Chance.  Forward entry ratio: %d\n\n", server_refratio);
      break;
    case SERVER_SCHEME_CHANCER:
      printf("  Scheme: 1 Chance + Return on Hit.  Forward entry ratio: %d \n\n", server_refratio);
      break;
    case SERVER_SCHEME_CHANCEH:
      printf("  Scheme: 1 Chance + Forwarding on Hints. Forward entry ratio: %d\n\n", server_refratio);
      break;
    case SERVER_SCHEME_CHANCEX:
      printf("  Scheme: 1 Chance + Random Forwarding. Forward entry ratio: %d\n\n", server_refratio);
      break;
    case SERVER_SCHEME_CHANCEZ:
      printf("  Scheme: 1 Chance + Best. Forward entry ratio: %d\n\n", server_refratio);
      break;
    } // end of switch

  printf("  %d clients, each with a %d * %d cache.\n", client_num, BLOCKSIZE, client_size);
  switch (client_mode)
    {
    case CLIENT_SCHEME_NONE:
      printf("  Scheme: none.\n\n");
      break;
    case CLIENT_SCHEME_BASE:
      printf("  Scheme: noncooperative.\n\n");
      break;
    case CLIENT_SCHEME_GLOBAL:
      printf("  Scheme: global storage server with a %d * %d cache.\n\n", BLOCKSIZE, c_server_size);
      break;
    case CLIENT_SCHEME_CHANCE1:
      printf("  Scheme: 1 Chance. Forward entry ratio: %d\n\n", client_refratio);
      break;
    } // end of switch

  network_ratio = atoi(argv[7]);

  return 1;
}

int main(int argc, char **argv)
{
  struct tracerecord record;

  //unsigned Atime;
  TimeBD Atime;
  unsigned serverid;
  unsigned ncount;
  unsigned b_begin, b_count;
  unsigned recnum;

  // get the parameter
  if (scanparameter(argc, argv) == 0)
    return 0;

  manager = new Manager;

  for (int i = 0 ; i < server_num; i ++)
    {
      nserver[i] = new NasdServer(server_mode, i, server_size);
    }

  if (server_mode == SERVER_SCHEME_GLOBAL)
    storageserver = new StorageServer(s_server_size);
  else
    storageserver = NULL;

  for (int i = 0 ; i < client_num; i ++)
    {
      nclient[i] = new Client(client_mode, i, client_size);
    }

  if (client_mode == CLIENT_SCHEME_GLOBAL)
    clientserver = new ClientServer(c_server_size);
  else
    clientserver = NULL;

  NasdStat *nstat = new NasdStat(1024, 20);

  // the first round is for warm-up use
  for (int j = 0; j < 4; j++) 
    {

      if (j > 1)
	printf("Completed %d%%......\n", (j-1)*33);
      
      recnum = 0;

      fseek(tracefile, 2 * sizeof(unsigned) + trace_num * (2 * sizeof(unsigned) + 64), SEEK_SET);

      // read every trace record
      while (fread(&record, sizeof(struct tracerecord), 1, tracefile) != 0)  
	{
	  // init Atime;
	  Atime.memtime = 0;
	  Atime.nettime = 0;
	  Atime.disktime = 0;
      
	  //unsigned servertime;
      
	  switch (record.op) 
	    {
	
	      // read operation
	    case MYTRACE_READ:
	    case MYTRACE_WRITE:
	      {
		b_begin = record.offset / BLOCKSIZE;
		b_count = XBLOCKNUM(record.offset, record.count);

		if (client_num)  // there are clients
		  {
		    nclient[record.pid]->ReadData(record.pid, record.i_dev, record.i_num, b_begin, 
						  b_count, Atime, ncount, j);
		    //Atime += servertime;
		    
		    if (j > 0) // j == 0 means warm-up
		      {
			nstat->AddRequest(record.op, b_count, Atime, ncount);
		      }
		  }
		else // only servers
		  {
		    serverid = manager->GetCap(record.pid, record.i_dev, record.i_num, b_begin, b_count);
	
		    nserver[serverid]->ReadData(record.i_dev, record.i_num, b_begin, b_count, 
						Atime, ncount, j);
		    
		    // the transfer time to client
		    Atime.nettime += NETROUNDTRIP * network_ratio + 
		      b_count * NETTRANSFERTIME * network_ratio;   

		    if (j > 0) // j == 0 means warm-up
		      {
			nstat->AddRequest(record.op, b_count, Atime, ncount + 2);
		      }
		  }

		break;
	      }	

	      /* ignore other operations right now
		 // write operation
		 case MYTRACE_WRITE:
		 b_begin = record.offset / BLOCKSIZE;
		 b_count = XBLOCKNUM(record.offset, record.count);
		   
		 serverid = manager->GetCap(record.i_dev, record.i_num, b_begin, b_count);
		 
		 nserver[serverid]->WriteData(record.i_dev, record.i_num, b_begin, b_count, servertime, 
		 ncount, j);
		 Atime += NETROUNDTRIP +  servertime + b_count * NETTRANSFERTIME;
		 
		 if (j > 0) // j == 0 means warm-up
		 {
		 nstat->AddRequest(record.op, record.count, Atime);
		 netcount += 1 + ncount;
		 }
		 
		 break;
		 
		 
		 // open operation
		 case MYTRACE_OPEN:
		 serverid = manager->GetCap(record.i_dev, record.i_num, 0, 0);
		 Atime += NETROUNDTRIP;
		 if (j > 0) // j == 0 means warm-up
		 {
		 nstat->AddRequest(record.op, record.count, Atime);
		 netcount ++;
		 }
		 
		 break;
		 
		 // close operation
		 case MYTRACE_CLOSE:
		 serverid = manager->GetCap(record.i_dev, record.i_num, 0, 0);
		 Atime += NETROUNDTRIP;
		 if (j > 0) // j == 0 means warm-up
		 {
		 netcount ++;
		 nstat->AddRequest(record.op, record.count, Atime);
		 }
		 
		 break;
		 
		 // create operation
		 case MYTRACE_CREATE:
		 serverid = manager->GetCap(record.i_dev, record.i_num, 0, 0);
		 
		 nserver[serverid]->Create(record.i_dev, servertime, j);
		 
		 // one communication with manager, and one between manager and the server
		 Atime += 2 * NETROUNDTRIP +  servertime;
		 
		 if (j > 0) // j == 0 means warm-up
		 {
		 nstat->AddRequest(record.op, record.count, Atime);
		 netcount += 2;
		 }
		 break;
		 
		 case MYTRACE_LSEEK:
		 // suppose only local information is changed
		 break;
		 
		 case MYTRACE_STAT:
		 // suppose only get information from the manager
		 serverid = manager->GetCap(record.i_dev, record.i_num, 0, 0);
		 Atime += NETROUNDTRIP;
		 
		 if (j > 0) // j == 0 means warm-up
		 {
		 nstat->AddRequest(record.op, record.count, Atime);
		 netcount ++;
		 }
		 
		 break;
		 
		 case MYTRACE_FDOPEN:
		 case MYTRACE_FDCREATE:
		 case MYTRACE_FCNTL:
		 // don't understand
		 break;
		 
		 case MYTRACE_UNLINK:
		 serverid = manager->GetCap(record.i_dev, record.i_num, 0, 0);
		 
		 nserver[serverid]->Unlink(record.i_dev, record.i_num, servertime, j);
		 
		 // one communication with manager, and one between manager and the server
		 Atime += 2 * NETROUNDTRIP +  servertime;
		 
		 if (j > 0) // j == 0 means warm-up
		 {
		 nstat->AddRequest(record.op, record.count, Atime);
		 netcount += 2;
		 }
		 
		 break;
	      */
	      
	    default:
	      break;

	    } // end of switch
	  
	  if (j == 0)
	    {
	      recnum ++;

	      // shorten the warmming up
	      if (recnum >= 40000)
		break;
	    }

	} // end of while
      
      if (j == 0)
	printf("Completed warmming up......\n");
      
    } // end of for
  
  printf("Completed 100%%......\n\n");
  
  printf("Simulation Result: \n\n");

  // print result
  nstat->PrintResult();

  unsigned iototal, reftotal;
  unsigned mbtotal, rbtotal;
  unsigned rhittotal;

  iototal = 0; 
  reftotal = 0; 
  mbtotal = 0;
  rbtotal = 0;
  rhittotal = 0;

  printf("\n\n");

  printf("------------------Server side--------------------\n");

  for (int i = 0 ; i < server_num ; i ++)
    {
      printf("Server %d:", i);
      printf("      io count: %d, ref count: %d\n", nserver[i]->iocount, nserver[i]->refcount);
      printf("      Cache Request Miss Rate: %lf%%\n", 
	     100.0 * nserver[i]->iocount / nserver[i]->refcount);
      printf("      Cache Block Miss Rate: %lf%%\n", 
	     100.0 * nserver[i]->missblock / nserver[i]->totalblock);
      printf("      Cache Block Local Hit Rate: %lf%%\n", 
	     100.0 * (nserver[i]->totalblock - nserver[i]->remotehit - nserver[i]->missblock) 
	     / nserver[i]->totalblock);
      printf("      Cache Block Remote Hit Rate: %lf%%\n", 
	     100.0 * nserver[i]->remotehit / nserver[i]->totalblock);
      printf("      Cache Blocks Used: %d\n", nserver[i]->localused);

      iototal += nserver[i]->iocount;
      reftotal += nserver[i]->refcount;
      mbtotal += nserver[i]->missblock;
      rbtotal += nserver[i]->totalblock;
      rhittotal += nserver[i]->remotehit;
    }

  printf("\n\n");

  printf("The average request miss rate: %lf%%\n", 100.0 * iototal / reftotal);
  printf("The average block miss rate: %lf%%\n", 100.0 * mbtotal / rbtotal);
  printf("The average block remote hit rate: %lf%%\n", 100.0 * rhittotal / rbtotal);

  printf("\n\n");

  iototal = 0; 
  reftotal = 0; 
  mbtotal = 0;
  rbtotal = 0;
  rhittotal = 0;

  printf("------------------Client side--------------------\n");

  for (int i = 0 ; i < client_num ; i ++)
    {
      printf("Client %d:", i);
      printf("      server access count: %d, ref count: %d\n", nclient[i]->iocount, nclient[i]->refcount);
      printf("      Cache Request Miss Rate: %lf%%\n", 
	     100.0 * nclient[i]->iocount / nclient[i]->refcount);
      printf("      Cache Block Miss Rate: %lf%%\n", 
	     100.0 * nclient[i]->missblock / nclient[i]->totalblock);
      printf("      Cache Block Local Hit Rate: %lf%%\n", 
      	     100.0 * (nclient[i]->totalblock - nclient[i]->remotehit - nclient[i]->missblock)
	     / nclient[i]->totalblock);
      printf("      Cache Block Remote Hit Rate: %lf%%\n", 
      	     100.0 * nclient[i]->remotehit / nclient[i]->totalblock);
      printf("      Cache Blocks Used: %d\n", nclient[i]->localused);

      iototal += nclient[i]->iocount;
      reftotal += nclient[i]->refcount;
      mbtotal += nclient[i]->missblock;
      rbtotal += nclient[i]->totalblock;
      rhittotal += nclient[i]->remotehit;
   }

  printf("\n\n");

  printf("The average request miss rate: %lf%%\n", 100.0 * iototal / reftotal);
  printf("The average block miss rate: %lf%%\n", 100.0 * mbtotal / rbtotal);
  printf("The average block remote hit rate: %lf%%\n", 100.0 * rhittotal / rbtotal);
 
  printf("\n\n");

  fclose(tracefile);

  delete nstat;
  
  delete manager;

  for (int i = 0 ; i < server_num ; i ++)
    delete nserver[i];

  if (storageserver)
    delete storageserver;

  for (int i = 0 ; i < client_num ; i ++)
    delete nclient[i];

  if (clientserver)
    delete clientserver;
}

