#include <stdio.h>
#include <stdlib.h>
#include <sys/times.h>
#include <time.h>
#include "agents.h"

#define CYCLES 40  // maximum number of cycles allowed

#define SHOP 0
#define HOME 1
#define REST 2

#define DIMY         256
#define DIMX         256
#define HOMES_XDIM   50
#define CITY_YDIM    200
#define CITY_XDIM    100
#define CITY_YTOP    ((DIMY - CITY_YDIM) / 2)
#define CITY_XTOP    100

#define PEOPLE_NB    (DIMY * DIMX / 100)  // 1%
#define HOMES_NB     PEOPLE_NB
#define SHOPS_NB     (DIMY * DIMX / 100 / 4)  // .25%
#define BUILDINGS_NB (DIMY * DIMX / 100 / 2)  // .5%

#define SHOPID       HOMES_NB  // id of the first shop

#define MAXLOAD       3  // max load of people
#define MAXDOGGEDNESS 1  // max doggedness

typedef struct{
  int homeid;           // its home: type of potential of its home
  int goal;             // its goal: go to SHOP, HOME or REST
  int shopid;           // next shop to go (potential type: SHOPID..SHOPID+4)
  int visited[SHOPS_NB];// bool giving shops already visited
  int doggedness;       // doggedness to a resource (0 = no doggedness)
}person_t;

static clock_t time1, time2, time3;
static struct tms tmsbuff;

// home potential spreading
static int home (int load, int initload, int initpot)
{
  return initpot;
}

// shop potential spreading
static int shop (int load, int initload, int initpot)
{
  return initpot;  //  return load * initpot / initload;
}

/* follow potential of the resource of given type and return the new move:
 * if it finds the way to the shop, then
 *   find all the squares which bring it nearer to the shop
 *   if there are such squares and it can go inside one (they are not occupied)
 *     choose *randomly* among them
 *   else wait for the other agents to free the place
 * else move randomly
 */
static pmAction_t gotoshop (int shopid)
{
  int dir, maxpot = 0;
  int move[4], count = 0;
  pmAction_t act;
  // get squares with max potential where it can go
  for (dir=0 ; dir<4 ; dir++){
    int pot = pmldGetSquarePotential (shopid, dir);
    if (maxpot < pot){  // found a shorter way to resource
      maxpot = pot;
      count = 0;
    }
    if ((maxpot <= pot) && pmtdCanMove (dir))  // unoccupied square => remember it
      move[count++] = dir;
  }
  // do the appropriate action
  if (maxpot > 0){  // found the way to the shop
    if (count > 0){  // found unoccupied squares => choose one of them randomly
      act.type = MOVE;
      act.param = move[pmtmRand () % count];
    }else  // traffic jam, all the squares are occupied => wait
      act.type = STAY;
  }else  // did not find the way to the shop => move randomly
    act.type = MOVERANDOM;
  return act;
}

// return 1 if end of shopping, else 0
static int endshopping (person_t *mem)
{
  int id;
  for (id=0 ; id<SHOPS_NB ; id++)
    if (! mem->visited[id])
      return 0;
  return 1;
}

// man's behaviour function
// visit shops and persist to shopping order
static pmAction_t man (void *memory)
{
  pmAction_t act;
  person_t *mem = (person_t *)memory;
  if (mem->goal == SHOP){  // go shopping
    if ((pmlmGetSquareType () == RESSQ)
        && (pmlmGetSquareResType () == mem->shopid)){  // found the right shop => do shopping
      act.type = TAKE;  // do shopping
      act.param = 1;
      mem->visited[mem->shopid - SHOPID] = 1;  // has visited this shop
      mem->shopid ++;  // go to next shop (in order)
      if (mem->shopid >= SHOPID + SHOPS_NB)
	mem->shopid -= SHOPS_NB;
      if ((pmlmGetAgentLoad () + act.param == pmlmGetAgentMaxload ())
          || endshopping (mem))
        // is full or has finished the shopping => go home
        mem->goal = HOME;
    }else  // go to the wanted shop
      act = gotoshop (mem->shopid);
  }else if (mem->goal == HOME){ // go home
    if ((pmlmGetSquareType () == RESSQ)
        && (pmlmGetSquareResType () == mem->homeid)){  // found the home => rest
      act.type = DROP;  // leave its goods at home
      act.param = pmlmGetAgentLoad ();
      if (endshopping (mem))
        mem->goal = REST;  // and rest
      else
        mem->goal = SHOP;  // or continue shopping
    }else  // go home
      act = gotoshop (mem->homeid);
  }else  // rest endlessly
    act.type = STAY;
  return act;
}

// woman's behaviour function
// visit shops but do not persist in shopping order
static pmAction_t woman (void *memory)
{
  int i, start;
  pmAction_t act;
  person_t *mem = (person_t *)memory;
  act = man (memory);  // the same as man, with one exception...
  if ((mem->goal == SHOP) && (act.type == STAY)){  // shopping; impatient, instead of long waiting, try another shop
    if (mem->doggedness == 0){  // do not stay
      start = pmtmRand ();
      for (i=0 ; i<SHOPS_NB ; i++){
	int id = (start + i) % SHOPS_NB;
        if (! mem->visited[id] && (mem->shopid != id+SHOPID)){  // try other shops
          act = gotoshop (id+SHOPID);
          if (act.type != STAY){  // no longer dogged
            mem->doggedness = MAXDOGGEDNESS;
            break;
          }
        }
      }
    }else
      mem->doggedness --;
  }else
    mem->doggedness = MAXDOGGEDNESS;
  return act;
}

// function called at the end of each cycle, controlling the end of simulation
static int runfunc (void)
{
  //  pmSaveChanges ();
  if (pmgGetCycle () == 0)
    time2 = times (&tmsbuff);
  if (pmgGetCycle () >= CYCLES)
    return STOP;
  return CONT;
}

int main (int argc, char *argv[])
{
  person_t mem;
  int i, y, x, menperc = -1, thnb = -1;
  long int mennb;
  // parameter parsing
  if (argc == 3){
    menperc = atoi (argv[1]);
    thnb = atoi (argv[2]);
  }
  if ((menperc < 0) || (menperc > 100) || (thnb == -1)){
    printf ("Usage: %s men%% thnb\n", argv[0]);
    return 1;
  }
  mennb = menperc * PEOPLE_NB / 100;  // men number
  //  printf ("%ld men, %ld women\n", mennb, PEOPLE_NB - mennb);
  // init environment
  pmInitSystem (0);
  pmCreateEnv (DIMY, DIMX, EDGES);
  pmSetResRefreshPeriod (0);
  pmSetPropagationMethod (ITERATIVE_ITERATIVE_DOMAIN);
  pmSetThreadsNo (thnb);
  // agent initialisation
  mem.goal = SHOP;
  for (i=0 ; i<SHOPS_NB ; i++)
    mem.visited[i] = 0;
  mem.doggedness = MAXDOGGEDNESS;  // for women
  // homes and agents
  for (i=0 ; i<HOMES_NB ; i++){
    do{
      y = rand() % DIMY;
      x = rand() % HOMES_XDIM;
    }while (pmgaGetSquareType(y, x) != FREESQ);
    pmPutResource (y, x, i, 0, 2*DIMY, home);
    mem.shopid = SHOPID + rand () % SHOPS_NB;
    mem.homeid = i;
    if ((rand() % 2) && (mennb > 0)){  // put man
      pmPutAgent (y, x, 1, NORM_PR, MAXLOAD, &mem, sizeof (mem), man);
      mennb --;
    }else  // put woman
      pmPutAgent (y, x, 1, NORM_PR, MAXLOAD, &mem, sizeof (mem), woman);
  }
  // shops
  for (i=0 ; i<SHOPS_NB ; i++){
    do{
      y = CITY_YTOP + rand() % CITY_YDIM;
      x = CITY_XTOP + rand() % CITY_XDIM;
    }while (pmgaGetSquareType(y, x) != FREESQ);
    pmPutResource (y, x, SHOPID+i, PEOPLE_NB, 2*DIMY, shop);
  }
  // obstacles
  for (i=0 ; i<BUILDINGS_NB ; i++){
    do{
      y = CITY_YTOP + rand() % CITY_YDIM;
      x = CITY_XTOP + rand() % CITY_XDIM;
    }while (pmgaGetSquareType(y, x) != FREESQ);
    pmPutObstacle (y, x);
  }
  // start simulation
  time1 = times (&tmsbuff);
  pmRunSystem (runfunc);
  time3 = times (&tmsbuff);
  pmEndSystem ();
  printf ("%d threads, prop=%.2f, sim=%.2f, total=%.2f\n", thnb,
	  ((double)(time2-time1))/CLK_TCK,
	  ((double)(time3-time2))/CLK_TCK,
	  ((double)(time3-time1))/CLK_TCK);
  return 0;
}
